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
8 changes: 6 additions & 2 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ name: Verify
#
# better_sqlite3.node, vec0.so/.dylib/.dll, skia.*.node
#
# `lint` is deliberately not part of this gate: the repository currently has 6
# pre-existing renderer react-hooks errors, unrelated to build correctness.
# `lint` is deliberately not part of this gate: it reports no errors but still
# carries a large pre-existing `@typescript-eslint/no-explicit-any` baseline,
# unrelated to build correctness.

on:
pull_request:
Expand Down Expand Up @@ -51,6 +52,9 @@ jobs:
- name: Typecheck
run: npm run typecheck

- name: Unit tests
run: npm test

# Runs `npm run build` first, then electron-builder --dir, so the bundle and
# the package are both covered by this one step.
- name: Build and package (unpacked)
Expand Down
25 changes: 14 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ npm run dev
| Command | What it does |
| ------------------------ | ------------------------------------------------------------- |
| `npm run dev` | Start the app in development with HMR. |
| `npm run typecheck` | Typecheck the main/preload and renderer projects. |
| `npm run typecheck` | Typecheck the main/preload, renderer and test projects. |
| `npm test` | Run the Node test suite (`test/**/*.test.ts`). |
| `npm run lint` | ESLint (see the note on the current baseline below). |
| `npm run format` | Prettier over the whole repository. |
| `npm run build` | Typecheck, then bundle with electron-vite. |
Expand All @@ -55,6 +56,7 @@ npm run dev

```bash
npm run typecheck # required — this is a CI gate
npm test # required — this is a CI gate
npm run build
```

Expand All @@ -75,21 +77,22 @@ against the real `app.asar`, so it is the step that actually catches these.

Two caveats:

- **`lint` is not a gate.** `main` currently reports 6 pre-existing
`react-hooks` errors and 156 warnings. Don't fix them in an unrelated PR, and
don't add new ones.
- **`lint` is not a gate.** `npm run lint` has no errors, but `main` still
reports ~118 pre-existing `@typescript-eslint/no-explicit-any` warnings.
Don't add new ones, and don't mass-refactor them in an unrelated PR.
- **Don't run `npm run format` on the whole repository.** It rewrites every
file, and one pre-existing file (`src/main/services/FileParserService.ts`) is
not prettier-clean, so a repo-wide format pulls unrelated changes into your
diff. Format only what you touched:
file, and a repo-wide format pulls unrelated changes into your diff. Format
only what you touched:

```bash
npx prettier --write <files>
```

There is **no automated test suite yet**. `test/fixtures/` contains sample
documents for manual parser checks. Describe your manual verification in the PR
instead — which platform, which workflow, what you observed.
Tests live in `test/**/*.test.ts` and run on Node's built-in test runner via
`npm test` (no test framework dependency). `test/fixtures/` additionally
contains sample documents for manual parser checks. For anything not covered by
a test, describe your manual verification in the PR instead — which platform,
which workflow, what you observed.

## Conventions

Expand All @@ -109,7 +112,7 @@ welcome when it is meaningful: `fix(mac): ...`.
src/main/ Electron main process
db/ Drizzle schema, migrations, queries
services/ Document parsing, RAG, item and note storage
providers/ LLM provider abstraction and registry
models/ Model Connection resolution and API protocol adapters
src/preload/ IPC bridge
src/renderer/ React UI
src/shared/ Types and utilities used by more than one process
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ It’s still early, but the core idea works — and I’m excited to share it an

- 📚 Build a local knowledge base from documents and notes
- 💬 Chat, summarize, and reason with your content using LLMs
- 🔌 Provider-based LLM design (OpenAI, DeepSeek, Ollama, and more)
- 🔌 Bring your own API: any OpenAI-, Anthropic- or Google-compatible endpoint, or a local server
- 🔍 RAG-powered retrieval with precise source traceability
- 🖥️ Desktop app built with Electron — no Docker, no server setup

Expand Down Expand Up @@ -83,7 +83,7 @@ KnowNote is for you if:
### 🤖 AI-Powered Q&A

- Retrieval-Augmented Generation (RAG)
- Multiple LLM providers
- Any OpenAI / Anthropic / Google-compatible endpoint, or local models
- Answers with precise source references

### 🔒 Local-First by Design
Expand Down Expand Up @@ -123,7 +123,7 @@ Feedback and suggestions are very welcome.

### ✅ Completed

- AI LLM conversation with multiple providers
- AI LLM conversation with any compatible endpoint, local or remote
- Structured note generation
- One-click mind map generation
- RAG-based document retrieval
Expand Down Expand Up @@ -177,7 +177,7 @@ KnowNote/
│ ├── main/ # Electron main process
│ │ ├── db/ # Database configuration and schema
│ │ ├── services/ # Core logic (document parsing, RAG, etc.)
│ │ └── providers/ # LLM provider abstraction
│ │ └── models/ # Model connection resolution and API protocol adapters
│ ├── renderer/ # React renderer process
│ ├── preload/ # Electron preload scripts
│ └── shared/ # Shared types and utilities
Expand All @@ -196,7 +196,7 @@ If you have ideas about:

- learning workflows
- knowledge visualization
- model/provider abstraction
- model connection / API protocol abstraction

I’d love to hear them.

Expand Down
10 changes: 5 additions & 5 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@

- 📚 从文档和笔记构建本地知识库
- 💬 使用 LLM 对内容进行聊天、总结和推理
- 🔌 基于提供商的 LLM 设计(OpenAI、DeepSeek、Ollama 等)
- 🔌 自带 API:任何兼容 OpenAI、Anthropic 或 Google 协议的端点,或本地部署
- 🔍 基于 RAG 的检索,具有精确的来源追溯能力
- 🖥️ 使用 Electron 构建的桌面应用——无需 Docker,无需服务器设置

Expand Down Expand Up @@ -83,7 +83,7 @@
### 🤖 AI 驱动的问答

- 检索增强生成(RAG)
- 多种 LLM 提供商
- 任何兼容 OpenAI / Anthropic / Google 协议的端点,或本地模型
- 带有精确来源引用的答案

### 🔒 本地优先设计
Expand Down Expand Up @@ -123,7 +123,7 @@ KnowNote 是一个早期阶段的项目。

### ✅ 已完成

- 支持多个提供商的 AI LLM 对话
- 支持任意兼容端点的 AI LLM 对话(本地或远程)
- 结构化笔记生成
- 一键生成思维导图
- 基于 RAG 的文档检索
Expand Down Expand Up @@ -177,7 +177,7 @@ KnowNote/
│ ├── main/ # Electron 主进程
│ │ ├── db/ # 数据库配置和架构
│ │ ├── services/ # 核心逻辑(文档解析、RAG 等)
│ │ └── providers/ # LLM 提供商抽象
│ │ └── models/ # 模型连接解析与 API 协议适配器
│ ├── renderer/ # React 渲染进程
│ ├── preload/ # Electron 预加载脚本
│ └── shared/ # 共享类型和工具
Expand All @@ -196,7 +196,7 @@ KnowNote/

- 学习工作流程
- 知识可视化
- 模型/提供商抽象
- 模型连接 / API 协议抽象

我很乐意听取。

Expand Down
53 changes: 17 additions & 36 deletions package-lock.json

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

9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
"lint": "eslint --cache .",
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
"typecheck": "npm run typecheck:node && npm run typecheck:web",
"typecheck:test": "tsc -p tsconfig.test.json",
"typecheck": "npm run typecheck:node && npm run typecheck:web && npm run typecheck:test",
"test": "node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --test \"test/**/*.test.ts\"",
"start": "electron-vite preview",
"dev": "electron-vite dev",
"build": "npm run typecheck && electron-vite build",
Expand Down Expand Up @@ -90,8 +92,8 @@
"rather than in app.asar."
],
"devDependencies": {
"@ai-sdk/alibaba": "^0.0.31",
"@ai-sdk/deepseek": "^1.0.33",
"@ai-sdk/anthropic": "^2.0.105",
"@ai-sdk/google": "^2.0.99",
"@ai-sdk/openai": "^2.0.89",
"@ai-sdk/openai-compatible": "^1.0.31",
"@dagrejs/dagre": "^1.1.8",
Expand Down Expand Up @@ -161,7 +163,6 @@
"nanoid": "^3.3.11",
"next-themes": "^0.4.6",
"officeparser": "^5.2.2",
"ollama-ai-provider-v2": "^1.5.5",
"postcss": "^8.5.6",
"prettier": "^3.8.1",
"react": "^19.2.4",
Expand Down
90 changes: 90 additions & 0 deletions src/main/config/ConnectionConfigManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* ConnectionConfigManager
* 持久化 Model Connection 配置(apiKey 使用 safeStorage 加密)。
*/

import type Store from 'electron-store'
import type { ConnectionMap, ModelCapability, ModelConnection } from '../../shared/types'
import { MODEL_CAPABILITIES } from '../../shared/types'
import type { StoreSchema } from './types'
import { encryptObjectFields, decryptObjectFields, isEncryptionAvailable } from './secureStorage'
import Logger from '../../shared/utils/logger'

const SENSITIVE_FIELDS = ['apiKey']

export class ConnectionConfigManager {
private getStore: () => Promise<Store<StoreSchema>>

constructor(getStore: () => Promise<Store<StoreSchema>>) {
this.getStore = getStore
}

/**
* 读取全部连接(自动解密 apiKey)
*/
async getConnections(): Promise<ConnectionMap> {
const store = await this.getStore()
const stored = store.get('connections', {}) as ConnectionMap
const result: ConnectionMap = {}

for (const capability of MODEL_CAPABILITIES) {
const connection = stored?.[capability]
if (connection) {
result[capability] = decryptObjectFields({ ...connection }, SENSITIVE_FIELDS)
}
}

return result
}

/**
* 读取单个连接
*/
async getConnection(capability: ModelCapability): Promise<ModelConnection | null> {
const connections = await this.getConnections()
return connections[capability] ?? null
}

/**
* 保存单个连接(覆盖同一 capability 的已有连接)
*/
async saveConnection(capability: ModelCapability, connection: ModelConnection): Promise<void> {
const store = await this.getStore()
const connections = { ...((store.get('connections', {}) as ConnectionMap) || {}) }

let stored: ModelConnection = { ...connection }
if (isEncryptionAvailable()) {
stored = encryptObjectFields(stored, SENSITIVE_FIELDS)
} else {
Logger.warn(
'ConnectionConfigManager',
`Encryption not available, storing ${capability} apiKey in plain text`
)
}

connections[capability] = stored
store.set('connections', connections)
Logger.info('ConnectionConfigManager', `Saved ${capability} connection`)
}

/**
* 删除单个连接
*/
async deleteConnection(capability: ModelCapability): Promise<void> {
const store = await this.getStore()
const connections = { ...((store.get('connections', {}) as ConnectionMap) || {}) }
delete connections[capability]
store.set('connections', connections)
Logger.info('ConnectionConfigManager', `Deleted ${capability} connection`)
}

/**
* 监听连接配置变化
*/
async onConnectionsChange(callback: (connections: ConnectionMap) => void): Promise<() => void> {
const store = await this.getStore()
return store.onDidChange('connections', () => {
void this.getConnections().then(callback)
})
}
}
Loading
Loading