LocalMind
Chat client for local LLMs served by LM Studio, on Android, iOS and web. Every message stays on your network: no cloud.
📌 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
/v1API. - 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 | with at least one model downloaded; local server on port 1234 by default |
| Run target | Expo Go on a device, an Android/iOS emulator, or a browser |
1. Start the model server (LM Studio)
- Open LM Studio, download a model and go to the Local Server tab (the
<->icon). - Load the model, enable CORS and keep the port at
1234(or note the one you choose). - Click Start Server.
2. Run the app
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
- Open Settings (gear icon in the chat header).
- Fill Host IP / Address and Port Number. On a physical device the host must be your machine's LAN address (
192.168.x.x), notlocalhost. - 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
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).useLMStudioexposes send, regenerate, stop and model fetching. lmStudioApibuilds the base URL and streams the response:XMLHttpRequestprogress events on native,fetchwithReadableStreamon web.- Streaming uses
stream: true; SSE lines withchoices[0].delta.contentare appended to the last message. GET /v1/modelsfeeds 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: notes on the LM Studio API (/api/v0,/v1, SDKs, CLI) and ideas, in Portuguese.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.
activeModelIdandavailableModelsare not persisted, so you select a model again after a restart.constants/Models.ts(a Qwen model catalog) and thereact-native-ssedependency are not used by any code.app.jsonand the storage key still carry the previous project name (LocalAiApp,localaiapp-storage).- Both
package-lock.jsonandpnpm-lock.yamlare committed. - No tests and no CI. The previous README reported
npx tsc --noEmitclean andnpx expo lintwith 0 errors and 3 warnings; Android and iOS builds, and the nativeXMLHttpRequeststreaming 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.
📄 License
No license defined — all rights reserved.