diff --git a/README.md b/README.md index 69da2cc..c33907e 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,177 @@ # LocalMind -![React Native](https://img.shields.io/badge/React_Native-20232A?style=for-the-badge&logo=react&logoColor=61DAFB) ![Expo](https://img.shields.io/badge/expo-1C1E24?style=for-the-badge&logo=expo&logoColor=#D04A37) ![Zustand](https://img.shields.io/badge/Zustand-443E38?style=for-the-badge&logo=react&logoColor=white) ![LMStudio](https://img.shields.io/badge/Powered_by-LMStudio-5E5DF0?style=for-the-badge) +**Chat client for local LLMs served by LM Studio. React Native + Expo app (Android, iOS and web) where every message stays on your own network: no cloud, no API key, no telemetry.** -> A fully local, privacy-first AI companion app built with React Native and Expo. Your data never leaves your device. +![React Native](https://img.shields.io/badge/React_Native-0.81.5-20232A?logo=react&logoColor=61DAFB) +![Expo](https://img.shields.io/badge/Expo_SDK-54-1C1E24?logo=expo&logoColor=white) +![TypeScript](https://img.shields.io/badge/TypeScript-5.9-3178C6?logo=typescript&logoColor=white) +![Zustand](https://img.shields.io/badge/Zustand-5-443E38?logo=react&logoColor=white) +![LM Studio](https://img.shields.io/badge/LM_Studio-OpenAI--compatible_API-5E5DF0) -This application connects directly to your own local AI models, completely offline. Currently in alpha, it pairs flawlessly with the [LMStudio](https://lmstudio.ai) local inference API. We developed and tested it against **Qwen3.5 (without thinking)** to guarantee a smooth, zero-latency experience. +## About -*Roadmap: Future updates will introduce direct in-app model downloads and support for external APIs.* +Running a model locally in LM Studio is easy; having a decent chat UI for it on the phone is not. LocalMind is that client: it talks straight to the LM Studio local server (the OpenAI-compatible `/v1` endpoints) over your LAN or `localhost`, streams the answer token by token, keeps chat sessions on the device and renders Markdown with code blocks. ---- +It is an alpha project. There is no backend, no account and no server to deploy: the app is only the client, and the model runs on your machine. -## Features +## How it works -- **100% Offline Inference:** Complete privacy. Zero cloud dependency. -- **Plug & Play LMStudio:** Instantly detects and fetches active models from your local server. -- **Persistent Sessions:** Chat histories and settings are saved locally using Zustand and `AsyncStorage`. -- **Developer-Friendly UI:** Real-time markdown rendering with syntax highlighting and 1-click code copying. -- **Minimalist Dark Mode:** High-contrast, distraction-free interface built on NativeWind principles. +``` +Expo app (Android / iOS / web) + app/chat, app/settings ......... screens (expo-router, file based) + hooks/useLMStudio .............. sendMessage / regenerate / stop, stream callbacks + services/lmStudioApi ........... HTTP + SSE client + store/useAppStore .............. Zustand + AsyncStorage (sessions, settings, theme) + | + | GET /v1/models + | POST /v1/chat/completions (stream: true -> SSE) + v + LM Studio local server (default http://localhost:1234) +``` ---- +- The connection settings (host, port, optional API key) live in the Zustand store and are persisted with `AsyncStorage`. +- `services/lmStudioApi.ts` builds the base URL as `http(s)://:/v1` and streams the response: `XMLHttpRequest` progress events on native platforms, `fetch` + `ReadableStream` on web. +- Each request carries a fixed system prompt (`constants/SystemPrompt.ts`); `temperature` is `0.7` and `max_tokens` is `2048`. +- Every finished exchange is saved as a chat session (title taken from the first user message), so History can reload it later. -## Quick Start +## Stack -To run this application, you will need both the frontend development environment and a local AI engine running. +| Layer | Choice | +|---|---| +| Runtime | Expo SDK 54, React Native 0.81.5, React 19.1 | +| Routing | expo-router 6 (typed routes experiment on, React Compiler experiment on) | +| State | Zustand 5 with `persist` + `@react-native-async-storage/async-storage` | +| Animation / gestures | react-native-reanimated 4, react-native-gesture-handler | +| Icons | lucide-react-native | +| Markdown | `@ronradtke/react-native-markdown-display` | +| Streaming | XMLHttpRequest (native) / fetch ReadableStream (web) | +| Tooling | TypeScript 5.9 (`strict`), ESLint 9 flat config (`eslint-config-expo`) | +| Model server | LM Studio local server, OpenAI-compatible `/v1` API | -### 1. Model Server (LMStudio) +## Requirements -1. Extract and open [LMStudio](https://lmstudio.ai/). -2. Download a compatible model (e.g., Llama 3, Qwen2.5, Mistral). -3. Open the **Local Server** tab (`<->`). -4. Enable **CORS** and set the port (default: `1234`). -5. Click **Start Server**. +- Node.js `>= 20.19.4` (required by React Native 0.81.5; verified here on Node 24.20.0) +- npm (or pnpm) to install dependencies +- [LM Studio](https://lmstudio.ai) with a model downloaded, the local server started and **CORS enabled** (Local Server tab, default port `1234`) +- To run the app: [Expo Go](https://expo.dev/go) on a physical device, an Android/iOS emulator, or a browser for the web target -> **Note:** If testing on a physical mobile device, locate your machine's local IPv4 address (e.g., `192.168.1.X`). - -### 2. Mobile Client (App) - -Ensure you have [Node.js](https://nodejs.org/) (v18+) and the [Expo Go](https://expo.dev/go) app installed. +## Quick start ```bash -git clone https://github.com/evandrodevbr/LocalAiApp.git -cd LocalAiApp +git clone https://github.com/evandrodevbr/LocalMind.git +cd LocalMind npm install npx expo start ``` -Scan the QR code with Expo Go, or type `i`/`a` to launch a simulator. +Then pick a target from the Expo CLI: `w` for web, `a` for Android, `i` for iOS, or scan the QR code with Expo Go. -### 3. Connection +Start the model side first: -1. Open the app and tap the **Settings** icon. -2. Enter your LMStudio server IP and Port (e.g., `192.168.1.X:1234`). -3. Tap **Test Connection**. +1. Open LM Studio, download a model and go to the **Local Server** tab (the `<->` icon). +2. Enable **CORS** and keep the port at `1234` (or note whatever you change it to). +3. Click **Start Server**. -Once connected, your available models will load automatically. +Finally, in the app: ---- +1. Open **Settings** (gear icon in the chat header). +2. Fill **Host IP / Address** and **Port Number**. On a physical device this must be your machine's LAN address (`192.168.x.x`), not `localhost`. +3. Tap **Test Connection**. The badge turns to *Connected* and the model list becomes available in the chat header selector. -## Bug Reports & Contributing +## Usage -Found an issue or have a suggestion? We welcome community involvement! +| Screen | What it does | +|---|---| +| Chat (`/chat`) | Conversation list with Markdown rendering, streaming answers, copy code blocks, edit / delete / regenerate a message, stop generation while it streams, model selector in the header, "new chat" button. | +| History (`/chat/history`) | Saved sessions, most recent first, with message count and date; tap to reload, trash icon to delete. | +| Settings (`/settings`) | Host, port and optional API key, connection test and status badge, light/dark toggle, app info. | -πŸ‘‰ **[Report an Issue or Request a Feature](https://github.com/evandrodevbr/LocalAiApp/issues)** +## API -When creating an issue, please include: +The app is a client of the LM Studio OpenAI-compatible API. Endpoints actually used: -- A clear description of the bug. -- Steps to reproduce. -- Relevant screenshots or environment details. +| Call | Where | Notes | +|---|---|---| +| `GET /v1/models` | `testConnection()`, `getModels()` | The connection test aborts after 5 s; the model list feeds the header selector. | +| `POST /v1/chat/completions` | `chatCompletion()`, `chatCompletionStream()` | Streaming (`stream: true`) is the path used by the chat; SSE lines with `choices[0].delta.content` are appended to the last message. | ---- +- Base URL: `http://:/v1` (`http://` is prepended when the host has no scheme). +- An `Authorization: Bearer ` header is sent only when an API key is filled in. +- `GET /v1/models` responds with the OpenAI shape `{ "data": [ { "id": ... } ] }`. -## Acknowledgments +## Web target -Thank you for exploring this project! Our mission is to share knowledge and foster an open-source collaborative ecosystem around offline AI solutions. All feedback, testing, and code contributions are highly appreciated. +The same codebase exports a static web build: -
-
+```bash +npx expo export --platform web # writes dist/ +npx serve dist # or any static server that maps /chat -> chat/index.html +``` + +The export generates `/`, `/chat`, `/chat/history`, `/settings`, `/_sitemap` and `/+not-found`. Use a static server that resolves extension-less URLs (`npx serve`, nginx `try_files $uri $uri.html $uri/ =404`); point unknown paths at `+not-found.html` for the app's 404 screen. + +## Project structure + +``` +app/ +β”œβ”€β”€ _layout.tsx root stack, theme provider, splash screen +β”œβ”€β”€ index.tsx redirects to /chat +β”œβ”€β”€ +not-found.tsx 404 screen +β”œβ”€β”€ chat/ +β”‚ β”œβ”€β”€ _layout.tsx chat header (model selector, history, new chat, settings) +β”‚ β”œβ”€β”€ index.tsx conversation screen, prompt suggestions, edit/regenerate +β”‚ └── history.tsx saved sessions +└── settings/index.tsx server connection, appearance, about +components/ +β”œβ”€β”€ chat/ ChatInput, MessageBubble, MessageActions, ModelSelector, TypingIndicator +└── ui/ Button, GlassCard, ConnectionBadge, ScreenWrapper, icons +constants/ Colors, theme, SystemPrompt (system prompt sent to the model) +hooks/ useLMStudio (chat flow), useThemeColor, useColorScheme +services/ lmStudioApi (HTTP/SSE), types +store/ useAppStore (Zustand + AsyncStorage) +docs/ LM Studio API notes and design reference +.agent/ AI development tooling (agents, skills, rules), not app code +``` + +## Verification + +There is no automated test suite in this repository. What exists and was actually run on Linux (Node 24.20.0, npm 11.19.0): + +| Command | Result | +|---|---| +| `npm install` | exit 0 | +| `npx tsc --noEmit` | exit 0, no type errors | +| `npx expo lint` | exit 0, 0 errors and 3 warnings (`no-require-imports` twice and one `exhaustive-deps` in `app/chat/index.tsx`) | +| `npx expo export --platform web` | exit 0, 6 static routes in `dist/` | +| `npx expo start --web` | dev server serves the app on `http://localhost:8083`, no browser errors | + +End-to-end run of the exported web build in headless Chromium, against a local mock of the LM Studio `/v1` API: + +- `GET /v1/models` and `POST /v1/chat/completions` (SSE) reached the app: Test Connection returned *Connection successful*, and the header listed the mock model; +- a message sent from the chat was answered by the mock and rendered incrementally: `Ola! Sou um modelo local rodando via mock do LM Studio. Teste E2E concluido com sucesso.`; +- the session was persisted (`localStorage` key `localaiapp-storage`) and showed up in History with `2 msgs`; +- zero uncaught page errors after the `import.meta` fix described below. + +Not verified in this environment: Android and iOS builds (no emulator or device available), and therefore the native streaming path via `XMLHttpRequest`. + +## Current state and limitations + +- Alpha. It was built against models served by LM Studio and has no fallback to any cloud API. +- No auto-connect on launch: the app only checks the server from Settings or when the model selector is refreshed. Until then the chat shows *Server offline*. +- `activeModelId` and `availableModels` are not persisted, so after restarting the app you have to select a model again. +- `constants/Models.ts` (a Qwen model catalog) is not referenced anywhere in the app. +- `react-native-sse` is declared in `package.json` but no code imports it. +- Both `package-lock.json` and `pnpm-lock.yaml` are committed. +- `app.json` still carries the previous project name (`LocalAiApp` / slug `localaiapp`) and the persisted storage key is `localaiapp-storage`. +- No CI, no tests, no license file. + +## Documentation + +| Document | Content | +|---|---| +| [`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 roadmap ideas (in Portuguese) | +| [`docs/evandro-design-system.html`](docs/evandro-design-system.html) | Visual reference of the design system used in the UI | + +## License + +No license file is present in this repository. Without an explicit license the code is "all rights reserved" by default, so adding one (MIT, for example) is a pending decision of the project owner.