Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,10 @@ MIMO_API_KEY=
MIMO_TTS_VOICE=茉莉
NEO_CITY=Beijing
NEO_SERVER_PORT=10103

# Embedding provider (Phase 3) — separate from chat; DeepSeek has no embeddings.
# The desktop UI pushes these to the sidecar via PUT /api/knowledge/embedding-config;
# these env vars are a fallback for headless/CI runs.
EMBEDDING_API_KEY=
EMBEDDING_BASE_URL=https://api.openai.com
EMBEDDING_MODEL=text-embedding-3-small
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ Vue 3 Frontend

## Current State Notes

- The **knowledge workspace** UI exists but is backed by a front-end mock (`useKnowledgeMock.ts`). The real SQLite-backed API, FTS5 full-text search, and `sqlite-vec` vector retrieval described in the architecture doc are **planned for v2**, not yet implemented.
- The **knowledge workspace** is backed by SQLite CRUD, transactional FTS5 indexing, optional `sqlite-vec` hybrid retrieval, file mirror import/export, and cited AI Ask/Chat flows. `useKnowledgeMock.ts` remains only as a preview fallback when the authenticated sidecar is unavailable.
- Every Sidecar REST and WebSocket request requires the shared `APP_AUTH_TOKEN`; use the root `pnpm dev` / `pnpm dev:tauri` launchers so development processes receive the same ephemeral token.
- The codebase still uses `pet`/`companion` identifiers in many places (`components/pet/`, `usePetState.ts`, `pet.css`). The product-facing terminology is **assistant**, but a full code-level rename is out of scope for documentation-only work.
- The sidecar runs on `http://127.0.0.1:10103` by default (configurable via `NEO_SERVER_PORT`).

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ NeoCompanion 的能力由浅入深分为四层:
| 数据库 | **SQLite** (Drizzle ORM) |
| AI | 聊天模型适配器 + OpenAI-compatible Embedding Adapter |

架构核心:Tauri (Rust) 提供系统级能力 → Fastify (TypeScript) 处理业务逻辑与 AI 调度 → Vue 提供 UI → SQLite 统一存储业务数据。v2 计划引入 FTS5 全文索引与 sqlite-vec 向量索引,当前版本尚未实现
架构核心:Tauri (Rust) 提供系统级能力与系统钥匙链 → Fastify (TypeScript) 处理业务逻辑、知识索引与 AI 调度 → Vue 提供 UI → SQLite 统一存储业务数据。知识工作空间已接入 FTS5 全文索引、可选 sqlite-vec 混合检索、文件镜像与带来源的 RAG

详见 [**系统架构设计**](docs/ARCHITECTURE.md)。

Expand Down
161 changes: 160 additions & 1 deletion apps/desktop/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions apps/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ tauri-plugin-dialog = "2"
tauri-plugin-window-state = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
keyring = "3"

[target.'cfg(target_os = "windows")'.dependencies]
tauri-plugin-wallpaper = "3.0.0"
40 changes: 40 additions & 0 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,40 @@ use tauri::menu::{Menu, MenuItem};
use tauri::tray::{TrayIconBuilder, TrayIconEvent};
use tauri::{LogicalSize, Manager, Size, WindowEvent};

#[tauri::command]
fn get_app_auth_token() -> Result<String, String> {
std::env::var("APP_AUTH_TOKEN").map_err(|_| "APP_AUTH_TOKEN is required".to_string())
}

const KEYRING_SERVICE: &str = "com.neocompanion.desktop";
const EMBEDDING_KEY_ACCOUNT: &str = "embedding-api-key";

fn embedding_key_entry() -> Result<keyring::Entry, String> {
keyring::Entry::new(KEYRING_SERVICE, EMBEDDING_KEY_ACCOUNT).map_err(|e| e.to_string())
}

#[tauri::command]
fn set_embedding_api_key(api_key: String) -> Result<(), String> {
embedding_key_entry()?.set_password(&api_key).map_err(|e| e.to_string())
}

#[tauri::command]
fn get_embedding_api_key() -> Result<Option<String>, String> {
match embedding_key_entry()?.get_password() {
Ok(value) => Ok(Some(value)),
Err(keyring::Error::NoEntry) => Ok(None),
Err(error) => Err(error.to_string()),
}
}

#[tauri::command]
fn delete_embedding_api_key() -> Result<(), String> {
match embedding_key_entry()?.delete_credential() {
Ok(()) | Err(keyring::Error::NoEntry) => Ok(()),
Err(error) => Err(error.to_string()),
}
}

pub fn run() {
let builder = tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
Expand All @@ -12,6 +46,12 @@ pub fn run() {
let builder = builder.plugin(tauri_plugin_wallpaper::init());

builder
.invoke_handler(tauri::generate_handler![
get_app_auth_token,
set_embedding_api_key,
get_embedding_api_key,
delete_embedding_api_key
])
.setup(|app| {
let show = MenuItem::with_id(app, "show", "显示", true, None::<&str>)?;
let quit = MenuItem::with_id(app, "quit", "退出", true, None::<&str>)?;
Expand Down
Loading
Loading