Files
LocalMind/README.md
T

7.9 KiB

LocalMind

Chat client for local LLMs served by LM Studio, on Android, iOS and web. Every message stays on your network: no cloud.

Quickstart · Architecture · Issues

Stack TypeScript Status License


📌 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 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)

  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

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

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

🛣️ 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.

📄 License

No license defined — all rights reserved.