159 lines
7.9 KiB
Markdown
159 lines
7.9 KiB
Markdown
<div align="center">
|
|
|
|
<h1>LocalMind</h1>
|
|
|
|
<p><strong>Chat client for local LLMs served by LM Studio, on Android, iOS and web. Every message stays on your network: no cloud.</strong></p>
|
|
|
|
<a href="#-quickstart">Quickstart</a> ·
|
|
<a href="#-architecture">Architecture</a> ·
|
|
<a href="https://git.evandro.dev.br/evandro/LocalMind/issues">Issues</a>
|
|
|
|

|
|

|
|

|
|

|
|
|
|
</div>
|
|
|
|
<hr />
|
|
|
|
## 📌 The problem
|
|
|
|
Running a model locally in LM Studio is easy; having a decent chat UI for it on your phone is not. Hosted chat apps send every message to a cloud service, which defeats the point of running the model yourself.
|
|
|
|
LocalMind is only the client. There is no backend, no account and no server to deploy: the model runs on your machine and the app talks to it over your LAN or `localhost`.
|
|
|
|
## ✨ What it does
|
|
|
|
- Connects to the LM Studio local server through its OpenAI-compatible `/v1` API.
|
|
- Streams answers token by token and lets you stop generation while it streams.
|
|
- Renders Markdown with code blocks, with a copy button for code.
|
|
- Edits, deletes and regenerates messages in the conversation.
|
|
- Lists the models loaded in LM Studio in a header selector.
|
|
- Saves every finished exchange as a chat session on the device; History reloads or deletes it.
|
|
- Tests the server connection from Settings and shows a Connected / Disconnected badge.
|
|
- Switches between light, dark and system themes.
|
|
|
|
## 🚀 Quickstart
|
|
|
|
| Tool | Minimum version |
|
|
|---|---|
|
|
| Node.js | `>= 20.19.4` (required by React Native 0.81.5; not declared in `package.json`) |
|
|
| npm | any recent version (`package-lock.json` is committed; `pnpm-lock.yaml` too) |
|
|
| [LM Studio](https://lmstudio.ai) | with at least one model downloaded; local server on port `1234` by default |
|
|
| Run target | [Expo Go](https://expo.dev/go) on a device, an Android/iOS emulator, or a browser |
|
|
|
|
### 1. Start the model server (LM Studio)
|
|
|
|
1. Open LM Studio, download a model and go to the **Local Server** tab (the `<->` icon).
|
|
2. Load the model, enable **CORS** and keep the port at `1234` (or note the one you choose).
|
|
3. Click **Start Server**.
|
|
|
|
### 2. Run the app
|
|
|
|
```bash
|
|
git clone https://git.evandro.dev.br/evandro/LocalMind.git
|
|
cd LocalMind
|
|
npm install
|
|
npm run start # Expo CLI: press w (web), a (Android), i (iOS) or scan the QR with Expo Go
|
|
# or go straight to a target:
|
|
npm run android
|
|
npm run ios
|
|
npm run web
|
|
```
|
|
|
|
### 3. Point the app at LM Studio
|
|
|
|
1. Open **Settings** (gear icon in the chat header).
|
|
2. Fill **Host IP / Address** and **Port Number**. On a physical device the host must be your machine's LAN address (`192.168.x.x`), not `localhost`.
|
|
3. Tap **Test Connection**. The badge turns to *Connected* and the loaded models appear in the header selector.
|
|
|
|
Until you do this the chat shows *Server offline*: the app does not connect on launch.
|
|
|
|
### Network configuration
|
|
|
|
The app has no `.env.example` and reads no environment variables. The server address is a runtime setting stored on the device.
|
|
|
|
| Setting | Required | Description |
|
|
|---|---|---|
|
|
| Host IP / Address | Yes | Default `localhost` (`DEFAULT_SERVER_CONFIG` in `services/types.ts`). `http://` is prepended when the value does not start with `http`. |
|
|
| Port Number | Yes | Default `1234`, the LM Studio default. |
|
|
| API Key (optional) | No | Empty by default. Sent as `Authorization: Bearer <key>` only when filled in. |
|
|
|
|
The resulting base URL is `http(s)://<host>:<port>/v1`. Defaults for a local LM Studio: `http://localhost:1234/v1`.
|
|
|
|
Other configuration lives in code, not in the UI:
|
|
|
|
| Where | What |
|
|
|---|---|
|
|
| `services/lmStudioApi.ts` | `temperature: 0.7`, `max_tokens: 2048`; the connection test aborts after 5 s |
|
|
| `constants/SystemPrompt.ts` | Fixed system prompt sent with every request |
|
|
| `app.json` | App name `LocalAiApp`, scheme `localaiapp`, `newArchEnabled`, `web.output: static`, `typedRoutes` and `reactCompiler` experiments |
|
|
| `store/useAppStore.ts` | Persisted under the `localaiapp-storage` key: theme, server config, chat sessions, current messages |
|
|
|
|
## 🧱 Architecture
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
Screens["Screens: app/chat, app/settings"] --> Hook["hooks/useLMStudio"]
|
|
Hook --> Api["services/lmStudioApi"]
|
|
Hook --> Store["store/useAppStore"]
|
|
Store --> Storage[("AsyncStorage")]
|
|
Api -->|"GET /v1/models, POST /v1/chat/completions"| LM["LM Studio local server"]
|
|
```
|
|
|
|
- Screens are file-based routes (`expo-router`). `useLMStudio` exposes send, regenerate, stop and model fetching.
|
|
- `lmStudioApi` builds the base URL and streams the response: `XMLHttpRequest` progress events on native, `fetch` with `ReadableStream` on web.
|
|
- Streaming uses `stream: true`; SSE lines with `choices[0].delta.content` are appended to the last message.
|
|
- `GET /v1/models` feeds the model selector and the connection test.
|
|
- The Zustand store is persisted with `AsyncStorage`; a finished exchange becomes a session titled from the first user message.
|
|
|
|
## 🗂️ Structure
|
|
|
|
```
|
|
app/ expo-router screens: chat/, chat/history, settings/, +not-found
|
|
components/ chat/ (ChatInput, MessageBubble, ModelSelector...) and ui/
|
|
constants/ Colors, theme, SystemPrompt, Models (unused)
|
|
hooks/ useLMStudio, useThemeColor, useColorScheme
|
|
services/ lmStudioApi (HTTP + SSE), types (ServerConfig, defaults)
|
|
store/ useAppStore (Zustand + AsyncStorage)
|
|
docs/ LM Studio API notes, design system reference
|
|
.agent/ AI development tooling, not app code
|
|
```
|
|
|
|
## 🧪 Scripts
|
|
|
|
| Command | What it does |
|
|
|---|---|
|
|
| `npm run start` | `expo start`: Expo dev server, pick a target from the CLI |
|
|
| `npm run android` | `expo start --android` |
|
|
| `npm run ios` | `expo start --ios` |
|
|
| `npm run web` | `expo start --web` |
|
|
| `npm run lint` | `expo lint` (ESLint 9 flat config, `eslint-config-expo`) |
|
|
| `npm run reset-project` | `node ./scripts/reset-project.js`: Expo template script that moves or deletes `app/`, `components/`, `hooks/`, `constants/` and `scripts/`. **Do not run it on this project.** |
|
|
|
|
Not scripts, but used by this project: `npx tsc --noEmit` (strict TypeScript check) and `npx expo export --platform web` (static build into `dist/`; serve it with a static server that resolves extension-less URLs, such as `npx serve dist`).
|
|
|
|
## 📚 Documentation
|
|
|
|
- [`docs/Documentação de Implementaçoes Futuras.md`](docs/Documenta%C3%A7%C3%A3o%20de%20Implementa%C3%A7oes%20Futuras.md): notes on the LM Studio API (`/api/v0`, `/v1`, SDKs, CLI) and ideas, in Portuguese.
|
|
- [`docs/evandro-design-system.html`](docs/evandro-design-system.html): visual reference of the design system used in the UI.
|
|
|
|
## 🛣️ Limitations and next steps
|
|
|
|
- Alpha. Built against models served by LM Studio; no fallback to any cloud API.
|
|
- No auto-connect on launch: the server is only checked from Settings or when the model selector refreshes.
|
|
- `activeModelId` and `availableModels` are not persisted, so you select a model again after a restart.
|
|
- `constants/Models.ts` (a Qwen model catalog) and the `react-native-sse` dependency are not used by any code.
|
|
- `app.json` and the storage key still carry the previous project name (`LocalAiApp`, `localaiapp-storage`).
|
|
- Both `package-lock.json` and `pnpm-lock.yaml` are committed.
|
|
- No tests and no CI. The previous README reported `npx tsc --noEmit` clean and `npx expo lint` with 0 errors and 3 warnings; Android and iOS builds, and the native `XMLHttpRequest` streaming path, were never verified.
|
|
|
|
## 🤝 Contributing
|
|
|
|
There is no test suite. Before opening a PR, make sure `npm run lint` and `npx tsc --noEmit` pass, and open issues on the [issue tracker](https://git.evandro.dev.br/evandro/LocalMind/issues).
|
|
|
|
## 📄 License
|
|
|
|
No license defined — all rights reserved.
|