Compare commits

5 Commits
Author SHA1 Message Date
evandro a8321ea498 docs(readme): redesenha o README com hero em SVG [skip ci] 2026-10-24 04:57:51 -03:00
evandro c246e68edd docs(readme): padroniza README (hero, badges, quickstart, arquitetura) 2026-10-24 04:13:01 +00:00
Evandro Fonseca Junior 1e0490b1f7 docs: rewrite README with verified setup, web deploy and limitations
Documents only what was executed on Linux (Node 24, npm): install, tsc, expo lint,
web export and a headless-browser E2E against a mock of the LM Studio /v1 API.
Also fixes the clone URL, which pointed at the old LocalAiApp repository name.
2026-09-14 07:55:37 -03:00
Evandro Fonseca Junior 7c624cdce8 chore: drop committed junk files and ignore python caches
- remove the stray '1.2.0' file at the root, an npm install log committed by accident.
- remove the committed __pycache__/*.pyc binaries and ignore __pycache__/ and *.pyc.
- add .gitattributes marking .agent/** as vendored/documentation so GitHub stops
  reporting the repository as Python when the app is TypeScript.
2026-09-14 07:55:33 -03:00
Evandro Fonseca Junior 4cc1addb3c fix: make the web bundle run and repair broken route declarations
- add babel.config.js with unstable_transformImportMeta: zustand/middleware ships
  import.meta.env, which the Metro web bundle (a classic script) cannot parse, so
  every page threw 'SyntaxError: Cannot use import.meta outside a module' and the
  app never hydrated after the static export.
- declare the settings screen as 'settings/index' in the root stack: the old name
  'settings' matched no route, so the modal presentation and the 'Settings' title
  were silently dropped (expo-router logged 'No route named settings exists').
- add the missing app/+not-found.tsx that the root layout already referenced.
- update the About label from LocalAiApp to LocalMind.
2026-09-14 07:55:29 -03:00
11 changed files with 160 additions and 60 deletions

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
# O diretorio .agent/ contem ferramentas de apoio ao desenvolvimento (agentes,
# skills e rules de IA), nao faz parte do app React Native.
.agent/** linguist-vendored
.agent/** linguist-documentation
+4
View File
@@ -36,6 +36,10 @@ yarn-error.*
# typescript
*.tsbuildinfo
# python (scripts de apoio em .agent/)
__pycache__/
*.pyc
app-example
# generated native folders
-12
View File
@@ -1,12 +0,0 @@
added 885 packages, changed 29 packages, and audited 915 packages in 22s
166 packages are looking for funding
run `npm fund` for details
4 vulnerabilities (1 moderate, 3 high)
To address all issues, run:
npm audit fix
Run `npm audit` for details.
+50 -46
View File
@@ -1,79 +1,83 @@
# LocalMind
<div align="center">
![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)
<img src="assets/readme/hero.svg" width="100%" alt="LocalMind: chat with your LM Studio models from a phone or browser on your own network">
> A fully local, privacy-first AI companion app built with React Native and Expo. Your data never leaves your device.
![React Native 0.81](https://img.shields.io/badge/React_Native-0.81-61DAFB?style=flat-square&logo=react&logoColor=black)
![Expo 54](https://img.shields.io/badge/Expo-54-1C1E24?style=flat-square&logo=expo&logoColor=white)
![TypeScript 5.9](https://img.shields.io/badge/TypeScript-5.9-3178C6?style=flat-square&logo=typescript&logoColor=white)
![Platforms](https://img.shields.io/badge/platforms-Android_%7C_iOS_%7C_Web-555?style=flat-square)
![Status](https://img.shields.io/badge/status-alpha-orange?style=flat-square)
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.
[Quick start](#quick-start) · [Issues](https://git.evandro.dev.br/evandro/LocalMind/issues)
*Roadmap: Future updates will introduce direct in-app model downloads and support for external APIs.*
</div>
---
## What it is
## Features
LocalMind is a chat client for the models you run in [LM Studio](https://lmstudio.ai). It is an Expo app for Android, iOS and the web that talks to LM Studio's OpenAI-compatible `/v1` API over your LAN or `localhost`. It is only the client: there is no backend, no account and no server to deploy.
- **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.
## Why use it
---
- **Your messages stay on your network.** The app only talks to the LM Studio address you set in Settings. There is no cloud API and no fallback to one.
- **Real streaming on every target.** Requests go out with `stream: true` and each SSE `choices[0].delta.content` is appended as it arrives: through `XMLHttpRequest` progress events on native, through `fetch` with a `ReadableStream` on the web. You can stop generation mid-stream.
- **The chat basics are there.** Markdown with copyable code blocks, edit, delete and regenerate, a model selector fed by LM Studio, history saved on the device, and light, dark or system theme.
## Quick Start
**When not to use it:** if you want a cloud model, a server other than LM Studio (the app was built against LM Studio only), or a verified phone build. The Android and iOS builds and the native streaming path have not been verified yet.
To run this application, you will need both the frontend development environment and a local AI engine running.
## How it works
### 1. Model Server (LMStudio)
- Screens are file-based routes (`expo-router`): `app/chat`, `app/chat/history` and `app/settings`. `hooks/useLMStudio` exposes send, regenerate, stop and model fetching.
- `services/lmStudioApi` builds `http(s)://<host>:<port>/v1`, calls `GET /v1/models` for the selector and the connection test, and streams `POST /v1/chat/completions`.
- `store/useAppStore` (Zustand) persists theme, server settings, chat sessions and the current chat in `AsyncStorage`. A finished exchange becomes a session titled from the first user message.
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**.
## Quick start
> **Note:** If testing on a physical mobile device, locate your machine's local IPv4 address (e.g., `192.168.1.X`).
You need Node.js 20.19.4 or newer (required by React Native 0.81; not declared in `package.json`), npm, and LM Studio with at least one model downloaded. To run the app, use [Expo Go](https://expo.dev/go) on a device, an Android or iOS emulator, or a browser.
### 2. Mobile Client (App)
**1. Start LM Studio's server.** Open the **Local Server** tab (the `<->` icon), load a model, enable **CORS**, keep the port at `1234` (or note yours) and click **Start Server**.
Ensure you have [Node.js](https://nodejs.org/) (v18+) and the [Expo Go](https://expo.dev/go) app installed.
**2. Run the app.**
```bash
git clone https://github.com/evandrodevbr/LocalAiApp.git
cd LocalAiApp
git clone https://git.evandro.dev.br/evandro/LocalMind.git
cd LocalMind
npm install
npx expo start
npm run start # Expo CLI: press w (web), a (Android), i (iOS) or scan the QR code with Expo Go
```
Scan the QR code with Expo Go, or type `i`/`a` to launch a simulator.
`npm run android`, `npm run ios` and `npm run web` open a target directly. Do not run `npm run reset-project`: it is the Expo template script and moves or deletes `app/`, `components/`, `hooks/`, `constants/` and `scripts/`.
### 3. Connection
**3. Point the app at LM Studio.** Open **Settings** (gear icon in the chat header), fill **Host IP / Address** and **Port Number**, then tap **Test Connection**. On a physical device the host must be your computer's LAN address (`192.168.x.x`), not `localhost`. The badge turns to *Connected* and the loaded models appear in the header. Until then the chat shows *Server offline*: the app does not connect on launch.
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**.
## Configuration
Once connected, your available models will load automatically.
The app reads no environment variables. The server address is a runtime setting stored on the device:
---
| Setting | Default | Notes |
|---|---|---|
| Host IP / Address | `localhost` | `http://` is prepended when the value does not start with `http` |
| Port Number | `1234` | LM Studio's default port |
| API Key (optional) | empty | Sent as `Authorization: Bearer <key>` only when filled in |
## Bug Reports & Contributing
Everything else lives in code: `temperature: 0.7` and `max_tokens: 2048` in `services/lmStudioApi.ts` (the connection test gives up after 5 s), the fixed system prompt in `constants/SystemPrompt.ts`, and the persisted state under the `localaiapp-storage` key in `store/useAppStore.ts`.
Found an issue or have a suggestion? We welcome community involvement!
## Project status
👉 **[Report an Issue or Request a Feature](https://github.com/evandrodevbr/LocalAiApp/issues)**
Alpha, with no tests and no CI. Known gaps:
When creating an issue, please include:
- The selected model and the model list are not persisted, so you pick 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.
- A clear description of the bug.
- Steps to reproduce.
- Relevant screenshots or environment details.
Before opening a PR, run `npm run lint` (Expo's ESLint config) and `npx tsc --noEmit`. Notes on the LM Studio API, in Portuguese, are in [`docs/`](docs/).
---
## License
## Acknowledgments
No published license: all rights reserved.
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.
<div align="center">
<br>
<br>
<sub>Made by <a href="https://evandro.dev.br">Evandro Fonseca Junior</a> · <a href="https://git.evandro.dev.br/evandro">git.evandro.dev.br</a></sub>
</div>
+38
View File
@@ -0,0 +1,38 @@
import { Link, Stack } from 'expo-router';
import { StyleSheet, Text, View } from 'react-native';
export default function NotFoundScreen() {
return (
<>
<Stack.Screen options={{ title: 'Oops!' }} />
<View style={styles.container}>
<Text style={styles.title}>This screen does not exist.</Text>
<Link href="/" style={styles.link}>
<Text style={styles.linkText}>Go to home screen</Text>
</Link>
</View>
</>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 20,
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
link: {
marginTop: 15,
paddingVertical: 15,
},
linkText: {
fontSize: 14,
color: '#2e78b7',
},
});
+1 -1
View File
@@ -28,7 +28,7 @@ export default function RootLayout() {
<Stack>
<Stack.Screen name="index" options={{ headerShown: false }} />
<Stack.Screen name="chat" options={{ headerShown: false }} />
<Stack.Screen name="settings" options={{ presentation: 'modal', title: 'Settings' }} />
<Stack.Screen name="settings/index" options={{ presentation: 'modal', title: 'Settings' }} />
<Stack.Screen name="+not-found" />
</Stack>
<StatusBar style={isDark ? 'light' : 'dark'} />
+1 -1
View File
@@ -215,7 +215,7 @@ export default function SettingsScreen() {
<View style={[styles.card, { backgroundColor: colors.cardBackground, borderColor: colors.border }]}>
<View style={styles.rowGroup}>
<Info size={18} color={colors.textMuted} style={styles.infoIcon} />
<Text style={[styles.infoText, { color: colors.text }]}>LocalAiApp - Cross Platform Version</Text>
<Text style={[styles.infoText, { color: colors.text }]}>LocalMind - Cross Platform Version</Text>
</View>
<View style={[styles.rowGroup, { marginTop: 8 }]}>
<MonitorCog size={18} color={colors.textMuted} style={styles.infoIcon} />
+46
View File
@@ -0,0 +1,46 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="420" viewBox="0 0 1200 420" role="img" aria-labelledby="title desc">
<title id="title">LocalMind</title>
<desc id="desc">LocalMind: a phone chat screen marked Connected talks over the local network to LM Studio running on your computer at port 1234, path /v1.</desc>
<rect width="1200" height="420" rx="24" fill="#09090B"/>
<g id="title-block" font-family="system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif">
<text x="64" y="170" font-size="64" font-weight="700" fill="#FAFAFA">LocalMind</text>
<text x="64" y="226" font-size="28" fill="#FAFAFA">Chat with your LM Studio models</text>
<text x="64" y="264" font-size="28" fill="#FAFAFA">from a phone or browser on your LAN.</text>
<text x="64" y="322" font-size="20" fill="#A1A1AA" font-family="ui-monospace, 'SF Mono', Consolas, monospace">React Native 0.81 · Expo 54 · TypeScript</text>
</g>
<g id="phone" font-family="system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif">
<rect x="644" y="44" width="216" height="332" rx="34" fill="#18181B" stroke="#3F3F46" stroke-width="3"/>
<rect x="722" y="58" width="60" height="8" rx="4" fill="#3F3F46"/>
<rect x="662" y="82" width="134" height="32" rx="16" fill="#052E16"/>
<circle cx="680" cy="98" r="5" fill="#BBF7D0"/>
<text x="692" y="104" font-size="18" fill="#BBF7D0">Connected</text>
<rect x="696" y="132" width="146" height="40" rx="16" fill="#FAFAFA"/>
<text x="769" y="158" font-size="20" fill="#09090B" text-anchor="middle">Explain SSE</text>
<rect x="662" y="188" width="164" height="104" rx="16" fill="#27272A"/>
<rect x="680" y="208" width="128" height="10" rx="5" fill="#A1A1AA"/>
<rect x="680" y="232" width="112" height="10" rx="5" fill="#A1A1AA"/>
<rect x="680" y="256" width="72" height="10" rx="5" fill="#A1A1AA"/>
<rect x="760" y="252" width="10" height="18" rx="2" fill="#FAFAFA"/>
<rect x="662" y="314" width="180" height="40" rx="20" fill="#09090B" stroke="#3F3F46" stroke-width="2"/>
<text x="682" y="340" font-size="18" fill="#A1A1AA">Message</text>
<circle cx="822" cy="334" r="13" fill="#FAFAFA"/>
<path d="M822 340 v-12 M816 333 l6 -6 l6 6" fill="none" stroke="#09090B" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g id="lan-link" font-family="system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif">
<path d="M872 206 h80" stroke="#A1A1AA" stroke-width="2" stroke-dasharray="8 6"/>
<text x="912" y="190" font-size="18" fill="#A1A1AA" text-anchor="middle">LAN</text>
</g>
<g id="computer" font-family="system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif">
<rect x="960" y="140" width="176" height="124" rx="12" fill="#18181B" stroke="#3F3F46" stroke-width="3"/>
<text x="1048" y="194" font-size="20" font-weight="700" fill="#FAFAFA" text-anchor="middle">LM Studio</text>
<text x="1048" y="228" font-size="20" fill="#A1A1AA" text-anchor="middle" font-family="ui-monospace, 'SF Mono', Consolas, monospace">:1234/v1</text>
<rect x="1032" y="264" width="32" height="24" fill="#3F3F46"/>
<rect x="1004" y="288" width="88" height="10" rx="4" fill="#3F3F46"/>
<text x="1048" y="334" font-size="18" fill="#A1A1AA" text-anchor="middle">your machine</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

+16
View File
@@ -0,0 +1,16 @@
module.exports = function (api) {
api.cache(true);
return {
presets: [
[
'babel-preset-expo',
{
// Dependencias (zustand/middleware) usam `import.meta.env`, que nao existe
// no bundle web do Metro (script classico) e quebrava a hidratacao do app.
// O polyfill do Expo troca por `globalThis.__ExpoImportMetaRegistry`.
unstable_transformImportMeta: true,
},
],
],
};
};