diff --git a/.gitignore b/.gitignore index 574e835..1352886 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules .pnpm-store dist .DS_Store +*.tsbuildinfo diff --git a/AGENTS.md b/AGENTS.md index ccd8220..108f7c6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,7 +28,7 @@ This means NovAI should gradually move away from a simple "single prompt -> sing For implementation reference and comparative study, keep this external repository available next to the NovAI repo: -- `/Users/honlnk/project/claude-code-sound` +- `/Users/honlnk/project/claude-code` This repository is intentionally cloned outside the NovAI git repository so that: diff --git a/CLAUDE.md b/CLAUDE.md index bb17f54..0d8741b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,11 +31,11 @@ packages/core @novai/core → 业务逻辑层(无框架依赖,纯 TypeScri **数据流:** `Vue Component → Pinia Store → Service → Core Domain` -- **app/src/stores/** — 三个 Pinia store:`project`(项目/文件状态)、`chat`(Agent 会话/事件流)、`settings`(配置/模型) -- **core/src/services/** — Service 层桥接 core 类型和 UI 视图类型,管理内存中的项目运行时注册表 -- **core/src/core/agent/** — Agent 循环:组装消息 → LLM 流式调用 → 解析 tool_calls → 执行工具 → 循环(最多 8 轮) -- **core/src/core/tools/** — 7 个文件工具(ReadFile/EditFile/CreateFile/RenameFile/DeleteFile/ListDirectory/FindFiles),全部基于浏览器 File System Access API -- **core/src/core/rag/** — RAG 管线(规划中/部分实现) +- **packages/app/src/stores/** — 三个 Pinia store:`project`(项目/文件状态)、`chat`(Agent 会话/事件流)、`settings`(配置/模型) +- **packages/core/src/services/** — Service 层桥接 core 类型和 UI 视图类型,管理内存中的项目运行时注册表 +- **packages/core/src/core/agent/** — Agent 循环:组装消息 → LLM 流式调用 → 解析 tool_calls → 执行工具 → 循环(最多 8 轮) +- **packages/core/src/core/tools/** — 7 个文件工具(ReadFile/EditFile/CreateFile/RenameFile/DeleteFile/ListDirectory/FindFiles),全部基于浏览器 File System Access API;Agent 额外暴露 `RagSearch` 作为只读检索工具 +- **packages/core/src/core/rag/** — RAG 管线(IndexedDB 记录 + 手写 cosineSimilarity 的过渡召回,已接入可选 Rerank;正式 Orama 召回待补齐) ## Key Patterns @@ -43,6 +43,7 @@ packages/core @novai/core → 业务逻辑层(无框架依赖,纯 TypeScri - **API 代理:** Vite dev server 内置 `/api-proxy` 中间件,通过 `x-target-base` header 转发请求到 LLM/Embedding API,解决浏览器 CORS 限制。 - **@novai/core 无构建步骤:** app 直接通过源码导入 core(见 core/package.json exports map),不需要先构建 core。 - **Agent 事件流:** AgentService 发出 `AgentUiEvent` 流供 UI 消费,实现流式响应展示。 +- **Agent LLM fallback:** 流式 tool_calls 解析异常或流式响应空闲超时时,会回退到非流式请求;相关行为已有 Vitest 覆盖。 ## Commit Convention @@ -57,4 +58,4 @@ packages/core @novai/core → 业务逻辑层(无框架依赖,纯 TypeScri - Node.js 18+, pnpm 10+ - 浏览器需支持 File System Access API(仅 Chromium) -- `docs/` 是 git submodule,指向 NovAI-document 仓库 +- `docs/` 是 git submodule,指向 NovAI-document 仓库;文档按 `product/`、`architecture/`、`decisions/`、`project/` 分层维护 diff --git a/INTERFACE.md b/INTERFACE.md index ad712be..b74e035 100644 --- a/INTERFACE.md +++ b/INTERFACE.md @@ -1,6 +1,6 @@ # NovAI UI 接口说明 -最后更新:2026-05-01 +最后更新:2026-06-12 本文档说明当前 NovAI 代码中 UI 层应如何使用项目能力。它面向前端协作者,重点回答三个问题: @@ -14,16 +14,16 @@ 约定如下: -- UI 页面优先使用 `src/stores/*`。 -- Store 通过 `src/services/*` 调用 core 能力。 -- 页面和组件不要直接 import `src/core/*`。 +- UI 页面优先使用 `packages/app/src/stores/*`。 +- Store 通过 `packages/core/src/services/*` 调用 core 能力。 +- 页面和组件不要直接 import `packages/core/src/core/*`。 - UI 不直接持有 `FileSystemDirectoryHandle` 或 `ProjectSnapshot`。 -- 项目、文件、会话、Agent 事件都通过 `src/services/types.ts` 中的 view type 暴露。 +- 项目、文件、会话、Agent 事件都通过 `packages/core/src/services/types.ts` 中的 view type 暴露。 当前已经完成迁移验证: -- `src/views/SessionTestView.vue` -- `src/views/TestLabView.vue` +- `packages/app/src/views/SessionTestView.vue` +- `packages/app/src/views/TestLabView.vue` ## 推荐调用路径 @@ -148,7 +148,7 @@ const result = await chatStore.runServiceTurn({ ## Service 入口 -所有 service 都在 `src/services/` 下。 +所有 service 都在 `packages/core/src/services/` 下。 | 文件 | 职责 | |:-----|:-----| @@ -170,7 +170,7 @@ const result = await chatStore.runServiceTurn({ 核心接口类型统一放在: ```txt -src/services/types.ts +packages/core/src/services/types.ts ``` 常用类型: @@ -227,7 +227,7 @@ type AgentUiEvent = - 用户消息 - 上下文摘要 -- assistant 流式输出 +- assistant 文本消息 - 工具调用记录 - 工具结果记录 - 文件变更 @@ -275,6 +275,7 @@ chatStore.syncDefaultTarget(project.id, projectStore.activeFile?.path) ```txt chapters/chapter-001.md +chapters/第001章.txt prompts/system.md elements/characters/foo.md ``` @@ -315,15 +316,16 @@ function resolvePreferredPath(changes: ChangedFileView[]) { - 文件 diff 预览尚未实现。 - `changedFiles` 仍是 service 层推导,不是工具层结构化输出。 - Agent 会话还没有持久化。 -- RAG 调试接口已存在,但 `RagSearch` 尚未作为正式 Agent 工具接入。 -- 要素提取当前仍是预览/占位能力。 +- `RagSearch` 已作为正式 Agent 工具接入,但真实创作任务中的触发率和召回质量仍需验证。 +- 要素提取已能规则型提取并写入 `elements/`,但还不是 LLM 结构化提取,也缺少用户确认、去重合并和覆盖策略。 +- 旧的 `pendingFileChange / confirmPendingFileChange` 确认流程代码仍有残留,但当前主工作流没有接入。 ## 协作规则 为了减少多人协作冲突,建议遵守: - UI 页面只依赖 stores 和 services。 -- 新 UI 组件不要直接 import `src/core/*`。 +- 新 UI 组件不要直接 import `packages/core/src/core/*`。 - 新增 UI 需要的数据,优先扩展 `services/types.ts` 和 mapper。 - core 内部结构变化时,尽量只修改 service / mapper,不让页面跟着改。 - 如果需要新增业务动作,优先新增 service,再由 store 适配 UI 状态。 @@ -331,5 +333,5 @@ function resolvePreferredPath(changes: ChangedFileView[]) { 更详细的设计记录见: ```txt -docs/UI协作接口契约设计.md +docs/architecture/UI协作接口契约设计.md ``` diff --git a/README.md b/README.md index 2f1ba14..16751b5 100644 --- a/README.md +++ b/README.md @@ -53,10 +53,12 @@ NovAI 的思路是换一条路: 尚未完整落地的核心能力包括: - 写工具执行前确认 +- 文件 diff 预览、Agent 停止运行与工具调用边界控制 - LLM 结构化要素抽取、要素去重合并与覆盖确认 - RAG 在真实创作任务中的召回质量验证与使用策略调优 - 自动增量索引、索引过期提醒与更完整的降级策略 - 近期章节上下文拼装 +- Agent 会话持久化与上下文压缩 - Rerank 精排与更完整的创作工作流 ## MVP 目标 @@ -82,6 +84,7 @@ NovAI 采用“一个文件夹就是一个小说项目”的思路。当前默 ├── elements/ │ ├── characters/ │ ├── locations/ +│ ├── entities/ │ ├── timeline/ │ ├── plots/ │ └── worldbuilding/ @@ -185,15 +188,20 @@ pnpm typecheck ## 文档 -项目文档主要放在 [`docs/`](./docs): +项目文档主要放在 [`docs/`](./docs),当前按用途分为五类: + +- [`docs/product/`](./docs/product):产品愿景、AI 功能需求、UI 设计 +- [`docs/architecture/`](./docs/architecture):技术架构、Agent Loop、工具系统、RAG、UI 接口契约 +- [`docs/decisions/`](./docs/decisions):阶段性技术和产品决策记录 +- [`docs/plans/`](./docs/plans):阶段性功能计划和重构计划 +- [`docs/project/`](./docs/project):路线图、MVP、当前进度、开发日志 + +常用入口: -- [`docs/NovAI产品规划.md`](./docs/NovAI产品规划.md):产品定位与核心思路 -- [`docs/AI功能需求说明书.md`](./docs/AI功能需求说明书.md):AI 功能需求 -- [`docs/UI设计文档.md`](./docs/UI设计文档.md):界面与交互设计 -- [`docs/技术架构设计.md`](./docs/技术架构设计.md):技术选型与架构说明 -- [`docs/project/项目总览.md`](./docs/project/项目总览.md):项目阶段与目标 -- [`docs/project/MVP清单.md`](./docs/project/MVP清单.md):MVP 范围与执行顺序 - [`docs/project/当前进度.md`](./docs/project/当前进度.md):开发推进情况 +- [`docs/project/项目总览.md`](./docs/project/项目总览.md):项目阶段与目标 +- [`docs/product/产品愿景.md`](./docs/product/产品愿景.md):产品定位与核心思路 +- [`docs/architecture/技术架构设计.md`](./docs/architecture/技术架构设计.md):技术选型与架构说明 - [`INTERFACE.md`](./INTERFACE.md):当前 UI 协作接口、stores/services 使用入口 ## 仓库目标 diff --git a/docs b/docs index 04768ae..1e1b571 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 04768ae19ef26aab28221f23808ed91248a79ee6 +Subproject commit 1e1b571ed26967e46e2e4f0406c1e687d1141788 diff --git a/packages/app/src/app/router.ts b/packages/app/src/app/router.ts index 77dfbae..24ee20a 100644 --- a/packages/app/src/app/router.ts +++ b/packages/app/src/app/router.ts @@ -3,8 +3,6 @@ import { createRouter, createWebHistory } from 'vue-router' import HomeView from '../views/HomeView.vue' import ProjectView from '../views/ProjectView.vue' import SettingsView from '../views/SettingsView.vue' -import SessionTestView from '../views/SessionTestView.vue' -import TestLabView from '../views/TestLabView.vue' export const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), @@ -24,15 +22,5 @@ export const router = createRouter({ name: 'settings', component: SettingsView, }, - { - path: '/test', - name: 'test', - component: TestLabView, - }, - { - path: '/session-test', - name: 'session-test', - component: SessionTestView, - }, ], }) diff --git a/packages/app/src/components/layout/ChatPanel.vue b/packages/app/src/components/layout/ChatPanel.vue index 2baf617..e3841e9 100644 --- a/packages/app/src/components/layout/ChatPanel.vue +++ b/packages/app/src/components/layout/ChatPanel.vue @@ -1,6 +1,7 @@ @@ -62,6 +102,13 @@ function handleSelectProject(projectId: string) {
+
+ {{ projectStore.errorMessage }} +
+
-

{{ project.name }}

-

{{ project.chapterCount }} 章

+
+
+

{{ project.name }}

+

{{ project.chapterCount }} 章

+
+ +
@@ -167,5 +227,46 @@ function handleSelectProject(projectId: string) { + + +
+
+

移除最近项目

+

+ 要从最近项目中移除「{{ projectPendingDelete.name }}」吗? +

+ +
+ + +
+
+
diff --git a/packages/app/src/views/ProjectView.vue b/packages/app/src/views/ProjectView.vue index 7f703b1..75c8a30 100644 --- a/packages/app/src/views/ProjectView.vue +++ b/packages/app/src/views/ProjectView.vue @@ -26,7 +26,8 @@ onMounted(async () => { // 如果没有当前项目,尝试恢复 if (!projectStore.currentProject) { await projectStore.loadLastProjectSummary() - const restored = await projectStore.restoreLastOpenedProject() + const restored = await projectStore.openRecentProject(projectId.value) + || await projectStore.restoreLastOpenedProject() if (!restored || restored.id !== projectId.value) { router.push('/') return diff --git a/packages/app/src/views/SessionTestView.vue b/packages/app/src/views/SessionTestView.vue deleted file mode 100644 index 7062384..0000000 --- a/packages/app/src/views/SessionTestView.vue +++ /dev/null @@ -1,398 +0,0 @@ - - -