mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
feat: release v0.85.0 with full pending changes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
- `develop/`:开发流程与协作规范
|
||||
- `plugins/`:插件与扩展相关文档
|
||||
- `tests/`:测试策略与用例文档
|
||||
- `iteration-notes/`:迭代备忘与下版本建议(暂不进入当前发布范围的问题)
|
||||
- `images/`:文档图片资源
|
||||
- `TECH_SPEC.md`:技术规格文档
|
||||
- `develop/execution-tracker-technical-plan.md`:统一执行追踪(Execution Tracker)专项技术规划
|
||||
|
||||
+41
-37
@@ -8,13 +8,13 @@ ProxyCast 是一个桌面端 AI API 代理工具,将各种大模型客户端 A
|
||||
|
||||
基于 pubcast 项目技术栈:
|
||||
|
||||
| 类别 | 技术 |
|
||||
|------|------|
|
||||
| 框架 | Tauri 2.0 (Rust + Web) |
|
||||
| 前端 | React 18 + TypeScript |
|
||||
| 构建 | Vite 5 |
|
||||
| UI | Tailwind CSS + Radix UI |
|
||||
| 图标 | Lucide React |
|
||||
| 类别 | 技术 |
|
||||
| ---- | ----------------------- |
|
||||
| 框架 | Tauri 2.0 (Rust + Web) |
|
||||
| 前端 | React 18 + TypeScript |
|
||||
| 构建 | Vite 5 |
|
||||
| UI | Tailwind CSS + Radix UI |
|
||||
| 图标 | Lucide React |
|
||||
|
||||
### 核心依赖
|
||||
|
||||
@@ -32,15 +32,15 @@ ProxyCast 是一个桌面端 AI API 代理工具,将各种大模型客户端 A
|
||||
|
||||
参考 AIClient-2-API,需支持以下渠道:
|
||||
|
||||
| Provider | 协议 | 说明 |
|
||||
|----------|------|------|
|
||||
| `claude-kiro-oauth` | OpenAI/Claude | Kiro OAuth 访问 Claude Sonnet 4.5 |
|
||||
| `gemini-cli-oauth` | OpenAI/Claude/Gemini | Gemini CLI OAuth |
|
||||
| `openai-qwen-oauth` | OpenAI/Claude | 通义千问 OAuth |
|
||||
| `openai-custom` | OpenAI | 自定义 OpenAI 兼容 API |
|
||||
| `claude-custom` | Claude | 自定义 Claude API |
|
||||
| `gemini-antigravity` | Gemini | Antigravity 协议 |
|
||||
| `openaiResponses-custom` | OpenAI Responses | 结构化对话 |
|
||||
| Provider | 协议 | 说明 |
|
||||
| ------------------------ | -------------------- | --------------------------------- |
|
||||
| `claude-kiro-oauth` | OpenAI/Claude | Kiro OAuth 访问 Claude Sonnet 4.5 |
|
||||
| `gemini-cli-oauth` | OpenAI/Claude/Gemini | Gemini CLI OAuth |
|
||||
| `openai-qwen-oauth` | OpenAI/Claude | 通义千问 OAuth |
|
||||
| `openai-custom` | OpenAI | 自定义 OpenAI 兼容 API |
|
||||
| `claude-custom` | Claude | 自定义 Claude API |
|
||||
| `gemini-antigravity` | Gemini | Antigravity 协议 |
|
||||
| `openaiResponses-custom` | OpenAI Responses | 结构化对话 |
|
||||
|
||||
## 核心功能模块
|
||||
|
||||
@@ -74,9 +74,9 @@ src/
|
||||
│ ├── TokenManager.tsx # Token 管理
|
||||
│ ├── LogViewer.tsx # 日志查看
|
||||
│ └── ui/ # 通用 UI 组件
|
||||
├── hooks/
|
||||
│ └── useTauri.ts # Tauri API hooks
|
||||
├── hooks/ # 领域 Hook(不再承载统一 Tauri 聚合层)
|
||||
└── lib/
|
||||
├── api/ # 前端 API 网关
|
||||
└── utils.ts
|
||||
```
|
||||
|
||||
@@ -91,12 +91,12 @@ http://localhost:8999/{provider}/v1/messages
|
||||
|
||||
### 支持的端点
|
||||
|
||||
| 端点 | 协议 | 说明 |
|
||||
|------|------|------|
|
||||
| `/v1/chat/completions` | OpenAI | 聊天补全 |
|
||||
| `/v1/messages` | Claude | Anthropic 消息 |
|
||||
| `/v1/models` | OpenAI | 模型列表 |
|
||||
| `/health` | - | 健康检查 |
|
||||
| 端点 | 协议 | 说明 |
|
||||
| ---------------------- | ------ | -------------- |
|
||||
| `/v1/chat/completions` | OpenAI | 聊天补全 |
|
||||
| `/v1/messages` | Claude | Anthropic 消息 |
|
||||
| `/v1/models` | OpenAI | 模型列表 |
|
||||
| `/health` | - | 健康检查 |
|
||||
|
||||
## 配置文件结构
|
||||
|
||||
@@ -139,12 +139,12 @@ http://localhost:8999/{provider}/v1/messages
|
||||
|
||||
## Token 凭证路径
|
||||
|
||||
| 服务 | 默认路径 |
|
||||
|------|----------|
|
||||
| Kiro | `~/.aws/sso/cache/kiro-auth-token.json` |
|
||||
| Gemini | `~/.gemini/oauth_creds.json` |
|
||||
| Qwen | `~/.qwen/oauth_creds.json` |
|
||||
| Antigravity | `~/.antigravity/oauth_creds.json` |
|
||||
| 服务 | 默认路径 |
|
||||
| ----------- | --------------------------------------- |
|
||||
| Kiro | `~/.aws/sso/cache/kiro-auth-token.json` |
|
||||
| Gemini | `~/.gemini/oauth_creds.json` |
|
||||
| Qwen | `~/.qwen/oauth_creds.json` |
|
||||
| Antigravity | `~/.antigravity/oauth_creds.json` |
|
||||
|
||||
## 协议转换
|
||||
|
||||
@@ -156,13 +156,13 @@ OpenAI <---> Claude <---> Gemini
|
||||
|
||||
### 转换矩阵
|
||||
|
||||
| 输入协议 | 输出 Provider | 说明 |
|
||||
|----------|---------------|------|
|
||||
| OpenAI | kiro | OpenAI -> CodeWhisperer |
|
||||
| OpenAI | gemini | OpenAI -> Gemini |
|
||||
| Claude | kiro | Claude -> CodeWhisperer |
|
||||
| Claude | gemini | Claude -> Gemini |
|
||||
| Claude | openai | Claude -> OpenAI |
|
||||
| 输入协议 | 输出 Provider | 说明 |
|
||||
| -------- | ------------- | ----------------------- |
|
||||
| OpenAI | kiro | OpenAI -> CodeWhisperer |
|
||||
| OpenAI | gemini | OpenAI -> Gemini |
|
||||
| Claude | kiro | Claude -> CodeWhisperer |
|
||||
| Claude | gemini | Claude -> Gemini |
|
||||
| Claude | openai | Claude -> OpenAI |
|
||||
|
||||
## UI 功能
|
||||
|
||||
@@ -175,21 +175,25 @@ OpenAI <---> Claude <---> Gemini
|
||||
## 开发计划
|
||||
|
||||
### Phase 1: 基础框架
|
||||
|
||||
- [ ] Tauri 项目初始化
|
||||
- [ ] 基础 UI 布局
|
||||
- [ ] 配置管理
|
||||
|
||||
### Phase 2: Kiro Provider
|
||||
|
||||
- [ ] Kiro OAuth Token 读取
|
||||
- [ ] CodeWhisperer API 调用
|
||||
- [ ] OpenAI/Claude 协议支持
|
||||
|
||||
### Phase 3: 其他 Provider
|
||||
|
||||
- [ ] Gemini CLI OAuth
|
||||
- [ ] Qwen OAuth
|
||||
- [ ] OpenAI/Claude Custom
|
||||
|
||||
### Phase 4: 高级功能
|
||||
|
||||
- [ ] Provider Pool 管理
|
||||
- [ ] 自动 Token 刷新
|
||||
- [ ] 请求日志/统计
|
||||
|
||||
@@ -10,6 +10,7 @@ AI Agent 专用文档目录,提供模块级别的详细说明。
|
||||
## 文件索引
|
||||
|
||||
### 核心系统
|
||||
|
||||
- `overview.md` - 项目架构概览
|
||||
- `governance.md` - **治理第一原则**(新旧并存、迁移收口、禁止回流)
|
||||
- `providers.md` - Provider 系统(OAuth/API Key 认证)
|
||||
@@ -18,26 +19,31 @@ AI Agent 专用文档目录,提供模块级别的详细说明。
|
||||
- `server.md` - HTTP 服务器(API 端点)
|
||||
|
||||
### 前端模块
|
||||
|
||||
- `components.md` - React 组件系统
|
||||
- `hooks.md` - 自定义 React Hooks
|
||||
- `lib.md` - 工具库和 API 封装
|
||||
|
||||
### 后端模块
|
||||
|
||||
- `services.md` - 业务服务层
|
||||
- `commands.md` - Tauri 命令
|
||||
- `database.md` - 数据库层(SQLite)
|
||||
|
||||
### 功能模块
|
||||
|
||||
- `terminal.md` - 内置终端
|
||||
- `mcp.md` - MCP 服务器管理
|
||||
- `plugins.md` - 插件系统
|
||||
- `playwright-e2e.md` - Playwright MCP 续测与 E2E 指南
|
||||
|
||||
### Aster 集成
|
||||
|
||||
- `aster-integration.md` - **Aster 框架集成方案**
|
||||
- `workspace.md` - **Workspace 设计文档**(工作目录管理)
|
||||
|
||||
### 内容创作
|
||||
|
||||
- `content-creator.md` - **内容创作系统**(write_file 标签、画布联动)
|
||||
|
||||
## 使用方式
|
||||
@@ -50,6 +56,7 @@ AI Agent 在处理特定模块时,应先阅读对应的 aiprompts 文档:
|
||||
|
||||
# 处理新旧并存、迁移、重构、架构收口
|
||||
→ 先读 docs/aiprompts/governance.md
|
||||
→ 再执行 npm run governance:legacy-report
|
||||
|
||||
# 处理凭证池相关任务
|
||||
→ 先读 docs/aiprompts/credential-pool.md
|
||||
|
||||
@@ -82,7 +82,7 @@ ProxyCast 已完整集成 aster-rust 框架,包括凭证池桥接。
|
||||
|
||||
### 使用方式
|
||||
|
||||
> 治理约定:前端业务层不要直接 `invoke('aster_*')`,统一通过 `src/lib/api/agentRuntime.ts` 调用现役 Aster API。
|
||||
> 治理约定:前端业务层不要直接 `invoke('aster_*')`,统一通过 `src/lib/api/agentRuntime.ts` 调用现役 Aster API。历史 `src/lib/api/agentCompat.ts` 已删除。
|
||||
|
||||
```typescript
|
||||
import {
|
||||
|
||||
+57
-13
@@ -2,7 +2,50 @@
|
||||
|
||||
## 概述
|
||||
|
||||
Tauri 命令是前端与 Rust 后端通信的桥梁,通过 `invoke` 调用。
|
||||
Tauri 命令是前端与 Rust 后端通信的边界,但前端业务代码**不应直接散落 `invoke`**。
|
||||
|
||||
推荐路径是:
|
||||
|
||||
`组件 / Hook -> src/lib/api/* 网关 -> safeInvoke -> Rust command`
|
||||
|
||||
这样做的目的不是“多包一层”,而是确保:
|
||||
|
||||
- 前端只有一个可治理的调用出口
|
||||
- Rust 命令可以按 `current / compat / deprecated` 分类演进
|
||||
- 新旧命令并存时,迁移边界清晰,不会继续扩散
|
||||
|
||||
## 治理约束
|
||||
|
||||
- 新的前端功能,禁止在页面、组件、普通 Hook 中直接调用 `invoke`。
|
||||
- 新的 Rust 命令,必须同时落一个对应的 `src/lib/api/*` 网关文件或收口到现有网关。
|
||||
- 旧命令如果暂时不能删,必须明确标记为 `compat` 或 `deprecated`,只允许保兼容,不允许继续长新逻辑。
|
||||
- 当前端已经迁到新网关后,要继续用 ESLint、脚本或日志告警封住旧入口,避免 AI 回流。
|
||||
|
||||
## 当前事实源
|
||||
|
||||
- 聊天主命令:`chat_*`
|
||||
- 旧 `general_chat_*` 前端 compat 网关与 Rust 命令已删除
|
||||
- 当前剩余治理重点:统计、记忆等旁路仍在读取 `general_chat_*` 历史表
|
||||
|
||||
## 治理案例:记忆系统
|
||||
|
||||
以当前仓库里的记忆能力为例:
|
||||
|
||||
- `unified_memory_*`:现役统一记忆主链路,后续功能优先往这里收
|
||||
- `memory_runtime_*`:现役 runtime / 上下文记忆主入口
|
||||
- `memory_get_*` / `memory_toggle_auto`:当前仍在使用的治理配置入口
|
||||
- `switch_prompt`:旧 prompt 切换命令已移除,统一使用 `enable_prompt`
|
||||
- `get_legacy_api_key_credentials` 等迁移命令:前端与 Tauri 入口都已移除,避免 UI/AI 再接入历史迁移链路
|
||||
|
||||
这类场景下,AI 不应该再做一套“第三套记忆命令”,而应该:
|
||||
|
||||
1. 先判断当前需求属于主链路、兼容层,还是治理配置
|
||||
2. 如果是统一沉淀记忆,优先补到 `unified_memory_*`
|
||||
3. 如果是 runtime / 上下文记忆视图,优先补到 `memory_runtime_*`
|
||||
4. 如果存在旧命令又无任何调用,就直接删掉命令注册、桥接和 mock,不要继续保留空兼容壳
|
||||
|
||||
同理,对话系统也不应该重新引回已经删除的 `general_chat_*` 命令;
|
||||
后续如需扩展聊天能力,应继续收敛到 `chat_*` 与对应网关。
|
||||
|
||||
## 目录结构
|
||||
|
||||
@@ -73,22 +116,23 @@ async fn clear_flow_records(before: Option<i64>) -> Result<u64, String>;
|
||||
|
||||
## 前端调用
|
||||
|
||||
推荐写法不是在业务层直接 `invoke`,而是在 API 网关里集中调用:
|
||||
|
||||
```typescript
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
// src/lib/api/serverRuntime.ts
|
||||
import { safeInvoke } from "@/lib/dev-bridge";
|
||||
|
||||
// 添加凭证
|
||||
const credential = await invoke<CredentialInfo>('add_credential', {
|
||||
provider: 'kiro',
|
||||
filePath: '/path/to/credential.json',
|
||||
});
|
||||
export async function getServerStatus() {
|
||||
return safeInvoke<ServerStatus>("get_server_status");
|
||||
}
|
||||
```
|
||||
|
||||
// 获取服务器状态
|
||||
const status = await invoke<ServerStatus>('get_server_status');
|
||||
业务层只消费 API 网关:
|
||||
|
||||
// 查询流量记录
|
||||
const records = await invoke<PagedResult<FlowRecord>>('get_flow_records', {
|
||||
query: { page: 1, pageSize: 20 },
|
||||
});
|
||||
```typescript
|
||||
import { getServerStatus } from "@/lib/api/serverRuntime";
|
||||
|
||||
const status = await getServerStatus();
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
@@ -11,7 +11,7 @@ src/components/
|
||||
├── ui/ # 基础 UI 组件 (shadcn/ui)
|
||||
├── provider-pool/ # 凭证池管理
|
||||
├── flow-monitor/ # 流量监控
|
||||
├── general-chat/ # 通用对话
|
||||
├── general-chat/ # 兼容画布桥接(非对话主入口)
|
||||
├── terminal/ # 内置终端
|
||||
├── mcp/ # MCP 服务器
|
||||
├── settings/ # 设置页面
|
||||
@@ -27,15 +27,15 @@ src/components/
|
||||
```tsx
|
||||
// src/components/AppSidebar.tsx
|
||||
export function AppSidebar() {
|
||||
return (
|
||||
<aside className="w-14 bg-sidebar">
|
||||
<nav className="flex flex-col items-center gap-2">
|
||||
<SidebarItem icon={Home} to="/" />
|
||||
<SidebarItem icon={MessageSquare} to="/chat" />
|
||||
<SidebarItem icon={Settings} to="/settings" />
|
||||
</nav>
|
||||
</aside>
|
||||
);
|
||||
return (
|
||||
<aside className="w-14 bg-sidebar">
|
||||
<nav className="flex flex-col items-center gap-2">
|
||||
<SidebarItem icon={Home} to="/" />
|
||||
<SidebarItem icon={MessageSquare} to="/chat" />
|
||||
<SidebarItem icon={Settings} to="/settings" />
|
||||
</nav>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -46,14 +46,14 @@ export function AppSidebar() {
|
||||
```tsx
|
||||
// src/components/provider-pool/ProviderPoolPanel.tsx
|
||||
export function ProviderPoolPanel() {
|
||||
const { credentials, addCredential, removeCredential } = useProviderPool();
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<CredentialList credentials={credentials} onRemove={removeCredential} />
|
||||
<AddCredentialDialog onAdd={addCredential} />
|
||||
</div>
|
||||
);
|
||||
const { credentials, addCredential, removeCredential } = useProviderPool();
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<CredentialList credentials={credentials} onRemove={removeCredential} />
|
||||
<AddCredentialDialog onAdd={addCredential} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -64,15 +64,15 @@ export function ProviderPoolPanel() {
|
||||
```tsx
|
||||
// src/components/flow-monitor/FlowMonitorPanel.tsx
|
||||
export function FlowMonitorPanel() {
|
||||
const { records, stats, query } = useFlowMonitor();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<FlowStats stats={stats} />
|
||||
<FlowTable records={records} />
|
||||
<FlowPagination query={query} />
|
||||
</div>
|
||||
);
|
||||
const { records, stats, query } = useFlowMonitor();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<FlowStats stats={stats} />
|
||||
<FlowTable records={records} />
|
||||
<FlowPagination query={query} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -89,22 +89,18 @@ export function FlowMonitorPanel() {
|
||||
```tsx
|
||||
// 标准组件结构
|
||||
interface Props {
|
||||
// props 定义
|
||||
// props 定义
|
||||
}
|
||||
|
||||
export function ComponentName({ prop1, prop2 }: Props) {
|
||||
// hooks
|
||||
const [state, setState] = useState();
|
||||
|
||||
// handlers
|
||||
const handleClick = () => {};
|
||||
|
||||
// render
|
||||
return (
|
||||
<div>
|
||||
{/* JSX */}
|
||||
</div>
|
||||
);
|
||||
// hooks
|
||||
const [state, setState] = useState();
|
||||
|
||||
// handlers
|
||||
const handleClick = () => {};
|
||||
|
||||
// render
|
||||
return <div>{/* JSX */}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ AI 返回带 <write_file> 标签的响应
|
||||
↓
|
||||
StreamingRenderer 解析标签 → 调用 onWriteFile
|
||||
↓
|
||||
AgentChatPage.handleWriteFile → 更新画布状态
|
||||
AgentChatPage.handleWriteFile → 映射为社媒 harness 产物 / 版本链
|
||||
↓
|
||||
右侧画布自动打开,显示文档内容
|
||||
```
|
||||
@@ -44,8 +44,10 @@ src/components/
|
||||
│ │ └── MessageList.tsx # 消息列表
|
||||
│ └── index.tsx # AgentChatPage
|
||||
└── general-chat/
|
||||
└── store/
|
||||
└── useGeneralChatStore.ts # 通用对话 Store
|
||||
├── bridge.ts # 兼容桥接层
|
||||
├── canvas/
|
||||
│ └── CanvasPanel.tsx # 复用画布面板
|
||||
└── types.ts # CanvasState / DEFAULT_CANVAS_STATE
|
||||
```
|
||||
|
||||
## 核心组件
|
||||
@@ -217,6 +219,10 @@ const handleWriteFile = useCallback(
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 社媒主题已不再把 `write_file` 仅视为“文件覆盖”,而是映射为带阶段语义的版本链产物
|
||||
- `brief / draft / polished / platform variant / publish package` 应分别作为不同产物语义处理
|
||||
- 日志、运行轨迹、正文产物三层分离:`harness` 产生命名事件,日志只做投影,正文仍由画布/产物承载
|
||||
|
||||
### Aster 框架限制
|
||||
|
||||
Aster 框架的 `SessionConfig` 不支持 session 级别的 system prompt,因此采用**消息注入**方案:
|
||||
|
||||
@@ -83,6 +83,32 @@
|
||||
- CI 阻止新代码继续引用废弃路径
|
||||
- 脚本扫描旧表、旧 DAO、旧命令、旧 Hook 的新增使用点
|
||||
|
||||
当前仓库可直接运行:
|
||||
|
||||
```bash
|
||||
npm run governance:legacy-report
|
||||
```
|
||||
|
||||
它会扫描:
|
||||
|
||||
- 已经被判定为 `deprecated` / `dead-candidate` 的前端入口(按真实 import 解析 `@/` 与相对路径)
|
||||
- 旧 Tauri 命令是否仍然只收口在指定 API 网关
|
||||
- 哪些兼容壳层已经零引用,可以进入删除候选
|
||||
|
||||
当前项目的最新治理状态可以概括为:
|
||||
|
||||
- 旧 `components/chat`、`general-chat` 页面 / Hook / Store / compat API 已删除
|
||||
- Rust `general_chat_*` 兼容命令已删除
|
||||
- `src/lib/api/agentCompat.ts` 已删除,Aster 前端只保留现役 runtime / stream API
|
||||
- General Chat 历史数据迁移已接入数据库初始化;新治理优先推动“启动期迁移”,而不是长期保留运行时 fallback
|
||||
- 下一阶段重点不再是页面和命令,而是统计、记忆等旁路对 `general_chat_*` 历史表的依赖
|
||||
|
||||
进一步治理时,建议坚持一个更细的边界规则:
|
||||
|
||||
- “迁移是否完成”的判断收口在 `Repository / Database` 边界
|
||||
- 业务服务层只消费 `pending_*` 语义接口
|
||||
- 不要在多个 service 里重复写 `is_migrated` 分支
|
||||
|
||||
原则只有一句:
|
||||
|
||||
**不是鼓励走新路,而是封住老路。**
|
||||
@@ -184,6 +210,8 @@
|
||||
3. 必须显式说明当前改动属于 `current`、`compat`、`deprecated`、`dead` 中哪一类。
|
||||
4. 如果发现主链路与旁路系统割裂,必须指出,不得假装治理已经完成。
|
||||
5. 如果无法在本次改动中完成收口,至少要建立守卫,阻止问题继续扩散。
|
||||
6. 一旦历史数据迁移已接入启动流程,运行时必须按“迁移完成标记”短路旧表读取;旧表只允许服务迁移、审计与回放,不再参与主链路查询。
|
||||
7. 过渡期对外暴露的命名必须体现“迁移态”语义,例如 `pending_*`,不要继续让业务层直接看见 `legacy_*` 模块名与函数名。
|
||||
|
||||
## 一句话总结
|
||||
|
||||
|
||||
+91
-59
@@ -2,7 +2,7 @@
|
||||
|
||||
## 概述
|
||||
|
||||
自定义 Hooks 封装业务逻辑,通过 Tauri invoke 与后端通信。
|
||||
自定义 Hooks 封装业务逻辑;新代码应优先通过 `src/lib/api/*` 网关与后端通信,而不是直接在 Hook 中散落 `invoke`。
|
||||
|
||||
## 目录结构
|
||||
|
||||
@@ -15,10 +15,15 @@ src/hooks/
|
||||
├── useFlowEvents.ts # 流量事件
|
||||
├── useMcpServers.ts # MCP 服务器
|
||||
├── useDeepLink.ts # Deep Link 处理
|
||||
├── useSound.ts # 音效管理
|
||||
└── useTauri.ts # Tauri 通用
|
||||
└── useSound.ts # 音效管理
|
||||
```
|
||||
|
||||
## 治理约束
|
||||
|
||||
- 新的前端能力优先落在 `src/lib/api/*`,再由 Hook 或组件消费。
|
||||
- 历史 `useTauri.ts` 兼容聚合层已删除,不要重新引入新的“大一统 API Hook”。
|
||||
- 旧聊天链路优先迁移到 `@/hooks/useUnifiedChat`,不要继续扩散 `useChat` / compat Hook。
|
||||
|
||||
## 核心 Hooks
|
||||
|
||||
### useUnifiedChat(统一对话)
|
||||
@@ -39,8 +44,22 @@ const { messages, sendMessage, stopGeneration } = useUnifiedChat({
|
||||
const creatorChat = useUnifiedChat({
|
||||
mode: "creator",
|
||||
systemPrompt: "你是内容创作助手...",
|
||||
onCanvasUpdate: (path, content) => { /* 更新画布 */ },
|
||||
onWriteFile: (content, fileName) => { /* 文件写入 */ },
|
||||
harnessConfig: {
|
||||
theme: "social-media",
|
||||
artifactMode: "version-chain",
|
||||
},
|
||||
onHarnessEvent: (event) => {
|
||||
/* 接收阶段推进、产物创建等语义事件 */
|
||||
},
|
||||
onArtifactUpdate: (artifact) => {
|
||||
/* 接收产物快照 */
|
||||
},
|
||||
onCanvasUpdate: (path, content) => {
|
||||
/* 更新画布 */
|
||||
},
|
||||
onWriteFile: (content, fileName) => {
|
||||
/* 文件写入 */
|
||||
},
|
||||
});
|
||||
|
||||
// General 模式 - 纯文本对话
|
||||
@@ -48,6 +67,7 @@ const generalChat = useUnifiedChat({ mode: "general" });
|
||||
```
|
||||
|
||||
**返回值**:
|
||||
|
||||
- `session` - 当前会话
|
||||
- `messages` - 消息列表
|
||||
- `isLoading` / `isSending` - 状态
|
||||
@@ -55,7 +75,13 @@ const generalChat = useUnifiedChat({ mode: "general" });
|
||||
- `sendMessage()` / `stopGeneration()` - 消息操作
|
||||
- `configureProvider()` - Provider 配置
|
||||
|
||||
**补充说明**:
|
||||
|
||||
- Creator 模式现在支持 `harnessConfig`、`onHarnessEvent`、`onArtifactUpdate`
|
||||
- 社媒内容推荐把 `<write_file>` 结果投影为“版本链产物”,而不是只按文件名覆盖
|
||||
|
||||
**相关文件**:
|
||||
|
||||
- 类型定义:`src/types/chat.ts`
|
||||
- API 封装:`src/lib/api/unified-chat.ts`
|
||||
- 架构文档:`docs/prd/chat-architecture-redesign.md`
|
||||
@@ -64,29 +90,31 @@ const generalChat = useUnifiedChat({ mode: "general" });
|
||||
|
||||
```typescript
|
||||
export function useProviderPool() {
|
||||
const [credentials, setCredentials] = useState<Credential[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const refresh = async () => {
|
||||
setLoading(true);
|
||||
const list = await invoke<Credential[]>('list_credentials');
|
||||
setCredentials(list);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const addCredential = async (provider: string, path: string) => {
|
||||
await invoke('add_credential', { provider, filePath: path });
|
||||
await refresh();
|
||||
};
|
||||
|
||||
const removeCredential = async (id: string) => {
|
||||
await invoke('remove_credential', { id });
|
||||
await refresh();
|
||||
};
|
||||
|
||||
useEffect(() => { refresh(); }, []);
|
||||
|
||||
return { credentials, loading, addCredential, removeCredential, refresh };
|
||||
const [credentials, setCredentials] = useState<Credential[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const refresh = async () => {
|
||||
setLoading(true);
|
||||
const list = await invoke<Credential[]>("list_credentials");
|
||||
setCredentials(list);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const addCredential = async (provider: string, path: string) => {
|
||||
await invoke("add_credential", { provider, filePath: path });
|
||||
await refresh();
|
||||
};
|
||||
|
||||
const removeCredential = async (id: string) => {
|
||||
await invoke("remove_credential", { id });
|
||||
await refresh();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
}, []);
|
||||
|
||||
return { credentials, loading, addCredential, removeCredential, refresh };
|
||||
}
|
||||
```
|
||||
|
||||
@@ -94,17 +122,19 @@ export function useProviderPool() {
|
||||
|
||||
```typescript
|
||||
export function useFlowEvents() {
|
||||
const [records, setRecords] = useState<FlowRecord[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = listen<FlowEvent>('flow-event', (event) => {
|
||||
setRecords(prev => [event.payload.data, ...prev].slice(0, 100));
|
||||
});
|
||||
|
||||
return () => { unlisten.then(fn => fn()); };
|
||||
}, []);
|
||||
|
||||
return { records };
|
||||
const [records, setRecords] = useState<FlowRecord[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = listen<FlowEvent>("flow-event", (event) => {
|
||||
setRecords((prev) => [event.payload.data, ...prev].slice(0, 100));
|
||||
});
|
||||
|
||||
return () => {
|
||||
unlisten.then((fn) => fn());
|
||||
};
|
||||
}, []);
|
||||
|
||||
return { records };
|
||||
}
|
||||
```
|
||||
|
||||
@@ -112,17 +142,19 @@ export function useFlowEvents() {
|
||||
|
||||
```typescript
|
||||
export function useDeepLink() {
|
||||
useEffect(() => {
|
||||
const unlisten = listen<string>('deep-link', async (event) => {
|
||||
const url = new URL(event.payload);
|
||||
|
||||
if (url.pathname === '/oauth/callback') {
|
||||
await handleOAuthCallback(url.searchParams);
|
||||
}
|
||||
});
|
||||
|
||||
return () => { unlisten.then(fn => fn()); };
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const unlisten = listen<string>("deep-link", async (event) => {
|
||||
const url = new URL(event.payload);
|
||||
|
||||
if (url.pathname === "/oauth/callback") {
|
||||
await handleOAuthCallback(url.searchParams);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
unlisten.then((fn) => fn());
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -138,15 +170,15 @@ export function useDeepLink() {
|
||||
```typescript
|
||||
// 返回对象,包含状态和操作
|
||||
return {
|
||||
// 状态
|
||||
data,
|
||||
loading,
|
||||
error,
|
||||
|
||||
// 操作
|
||||
refresh,
|
||||
add,
|
||||
remove,
|
||||
// 状态
|
||||
data,
|
||||
loading,
|
||||
error,
|
||||
|
||||
// 操作
|
||||
refresh,
|
||||
add,
|
||||
remove,
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
@@ -17,11 +17,14 @@ src-tauri/src/services/
|
||||
├── usage_service.rs # 使用量统计
|
||||
├── backup_service.rs # 备份服务
|
||||
├── update_check_service.rs # 自动更新检查
|
||||
└── general_chat/ # 通用对话服务
|
||||
```
|
||||
|
||||
## 核心服务
|
||||
|
||||
> 注意:`general_chat/` 兼容壳已删除。
|
||||
> 新功能与新治理都应直接落到 unified chat / `chat_*` 体系,不要重新引回旧入口。
|
||||
> `ProviderPoolService::select_credential_with_fallback_legacy` 也已删除,凭证选择统一走现役 `select_credential_with_fallback`。
|
||||
|
||||
### ProviderPoolService
|
||||
|
||||
```rust
|
||||
|
||||
Reference in New Issue
Block a user