Compare commits
5
Commits
wip/2026-10-20
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8321ea498 | ||
|
|
c246e68edd | ||
|
|
1e0490b1f7 | ||
|
|
7c624cdce8 | ||
|
|
4cc1addb3c |
No files matched your search
Binary file not shown.
BIN
Binary file not shown.
@@ -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
|
||||||
@@ -36,6 +36,10 @@ yarn-error.*
|
|||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# python (scripts de apoio em .agent/)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
app-example
|
app-example
|
||||||
|
|
||||||
# generated native folders
|
# generated native folders
|
||||||
|
|||||||
@@ -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.
|
|
||||||
@@ -1,79 +1,83 @@
|
|||||||
# LocalMind
|
<div align="center">
|
||||||
|
|
||||||
   
|
<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.
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
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.
|
## Why use it
|
||||||
- **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.
|
|
||||||
|
|
||||||
---
|
- **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/).
|
## Quick start
|
||||||
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**.
|
|
||||||
|
|
||||||
> **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
|
```bash
|
||||||
git clone https://github.com/evandrodevbr/LocalAiApp.git
|
git clone https://git.evandro.dev.br/evandro/LocalMind.git
|
||||||
cd LocalAiApp
|
cd LocalMind
|
||||||
npm install
|
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.
|
## Configuration
|
||||||
2. Enter your LMStudio server IP and Port (e.g., `192.168.1.X:1234`).
|
|
||||||
3. Tap **Test Connection**.
|
|
||||||
|
|
||||||
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.
|
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/).
|
||||||
- Steps to reproduce.
|
|
||||||
- Relevant screenshots or environment details.
|
|
||||||
|
|
||||||
---
|
## 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>
|
<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>
|
||||||
<br>
|
|
||||||
|
</div>
|
||||||
@@ -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
@@ -28,7 +28,7 @@ export default function RootLayout() {
|
|||||||
<Stack>
|
<Stack>
|
||||||
<Stack.Screen name="index" options={{ headerShown: false }} />
|
<Stack.Screen name="index" options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="chat" 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.Screen name="+not-found" />
|
||||||
</Stack>
|
</Stack>
|
||||||
<StatusBar style={isDark ? 'light' : 'dark'} />
|
<StatusBar style={isDark ? 'light' : 'dark'} />
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ export default function SettingsScreen() {
|
|||||||
<View style={[styles.card, { backgroundColor: colors.cardBackground, borderColor: colors.border }]}>
|
<View style={[styles.card, { backgroundColor: colors.cardBackground, borderColor: colors.border }]}>
|
||||||
<View style={styles.rowGroup}>
|
<View style={styles.rowGroup}>
|
||||||
<Info size={18} color={colors.textMuted} style={styles.infoIcon} />
|
<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>
|
||||||
<View style={[styles.rowGroup, { marginTop: 8 }]}>
|
<View style={[styles.rowGroup, { marginTop: 8 }]}>
|
||||||
<MonitorCog size={18} color={colors.textMuted} style={styles.infoIcon} />
|
<MonitorCog size={18} color={colors.textMuted} style={styles.infoIcon} />
|
||||||
|
|||||||
@@ -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 |
@@ -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,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in new issue
Block a user