mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
feat: 语音输入合并到截图输入框,版本更新至 0.49.0
- 将语音输入功能合并到截图输入框(pill-shaped 浮动栏) - 添加麦克风按钮(32x32px)支持点击录音 - 支持录音、转写、润色三种状态显示 - 修复讯飞 ASR Broken pipe 错误 - 清理旧的语音输入 UI 文件 - 修复 DropdownMenu 组件支持受控模式 - 修复 StreamResponse 导出问题 注:部分编译错误待后续修复
This commit is contained in:
@@ -34,6 +34,7 @@ AI Agent 专用文档目录,提供模块级别的详细说明。
|
||||
|
||||
### Aster 集成
|
||||
- `aster-integration.md` - **Aster 框架集成方案**
|
||||
- `workspace.md` - **Workspace 设计文档**(工作目录管理)
|
||||
|
||||
## 使用方式
|
||||
|
||||
@@ -48,6 +49,9 @@ AI Agent 在处理特定模块时,应先阅读对应的 aiprompts 文档:
|
||||
|
||||
# 处理 Aster Agent 集成
|
||||
→ 先读 docs/aiprompts/aster-integration.md
|
||||
|
||||
# 处理 Workspace 相关任务
|
||||
→ 先读 docs/aiprompts/workspace.md
|
||||
```
|
||||
|
||||
## 更新提醒
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
# Workspace 设计文档
|
||||
|
||||
## 概述
|
||||
|
||||
Workspace 是 ProxyCast 应用层的概念,用于组织和管理 AI Agent 的工作上下文。它是对 Aster 框架 `Session.working_dir` 的命名和配置包装,不修改 Aster 框架本身。
|
||||
|
||||
## 设计背景
|
||||
|
||||
### 行业调研
|
||||
|
||||
基于对 Cursor、Claude Code、Manus、AI21 等产品的调研,总结出以下关键洞察:
|
||||
|
||||
| 来源 | 核心观点 |
|
||||
|------|---------|
|
||||
| Manus (philschmid.de) | "Share memory by communicating, don't communicate by sharing memory" |
|
||||
| AI21 | "Agents that only read can share an environment. Agents that write need isolation" |
|
||||
| Cursor | Shadow Workspace 实现后台 AI 迭代,不影响用户体验 |
|
||||
| Claude Code | 通过 `--add-dir` 支持多目录,`CLAUDE.md` 实现层级配置 |
|
||||
|
||||
### 核心原则
|
||||
|
||||
1. **读共享,写隔离** - 只读操作可以共享环境,写操作需要隔离
|
||||
2. **最小有效 context** - 只传递必要的 context,避免 context pollution
|
||||
3. **Workspace = 边界** - 文件系统边界 + context 边界 + 配置边界
|
||||
|
||||
## 架构设计
|
||||
|
||||
### 层级关系
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ProxyCast (应用层) │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐│
|
||||
│ │ Workspace 管理 ││
|
||||
│ │ - WorkspaceManager: CRUD 操作 ││
|
||||
│ │ - WorkspaceSettings: workspace 级配置 ││
|
||||
│ │ - 通过 working_dir 关联 Aster Session ││
|
||||
│ └─────────────────────────────────────────────────────────────┘│
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Aster (框架层) - 不修改 │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐│
|
||||
│ │ Session 管理 ││
|
||||
│ │ - SessionManager: Session CRUD ││
|
||||
│ │ - Session.working_dir: 工作目录 ││
|
||||
│ │ - Conversation: 对话历史 ││
|
||||
│ │ - EnhancedContextManager: context 压缩 ││
|
||||
│ └─────────────────────────────────────────────────────────────┘│
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 与 Aster Session 的关系
|
||||
|
||||
- Workspace 通过 `root_path` 与 Aster `Session.working_dir` 关联
|
||||
- 一个 Workspace 可以包含多个 Session(同一 working_dir)
|
||||
- ProxyCast 按 Workspace 分组显示 Session 列表
|
||||
|
||||
|
||||
|
||||
## 数据模型
|
||||
|
||||
### Workspace 类型定义
|
||||
|
||||
```rust
|
||||
// proxycast/src-tauri/src/workspace/types.rs
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
/// Workspace 唯一标识
|
||||
pub type WorkspaceId = String;
|
||||
|
||||
/// Workspace 类型
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum WorkspaceType {
|
||||
#[default]
|
||||
Persistent, // 持久化 workspace
|
||||
Temporary, // 临时 workspace(自动清理)
|
||||
}
|
||||
|
||||
/// Workspace 元数据
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Workspace {
|
||||
pub id: WorkspaceId,
|
||||
pub name: String,
|
||||
pub workspace_type: WorkspaceType,
|
||||
pub root_path: PathBuf, // 对应 Aster Session.working_dir
|
||||
pub is_default: bool,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub settings: WorkspaceSettings,
|
||||
}
|
||||
|
||||
/// Workspace 级别设置
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct WorkspaceSettings {
|
||||
pub mcp_config: Option<serde_json::Value>, // workspace 级 MCP 配置
|
||||
pub default_provider: Option<String>, // 默认 provider
|
||||
pub auto_compact: bool, // 自动压缩 context
|
||||
}
|
||||
```
|
||||
|
||||
### 数据库 Schema
|
||||
|
||||
```sql
|
||||
-- proxycast 应用数据库
|
||||
CREATE TABLE workspaces (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
workspace_type TEXT NOT NULL DEFAULT 'persistent',
|
||||
root_path TEXT NOT NULL UNIQUE, -- 对应 Aster Session.working_dir
|
||||
is_default BOOLEAN DEFAULT FALSE,
|
||||
settings_json TEXT DEFAULT '{}',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_workspaces_root_path ON workspaces(root_path);
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 核心接口
|
||||
|
||||
### WorkspaceManager
|
||||
|
||||
```rust
|
||||
// proxycast/src-tauri/src/workspace/manager.rs
|
||||
|
||||
impl WorkspaceManager {
|
||||
/// 创建新 workspace
|
||||
pub async fn create(&self, name: String, root_path: PathBuf) -> Result<Workspace>;
|
||||
|
||||
/// 获取 workspace
|
||||
pub async fn get(&self, id: &WorkspaceId) -> Result<Workspace>;
|
||||
|
||||
/// 列出所有 workspace
|
||||
pub async fn list(&self) -> Result<Vec<Workspace>>;
|
||||
|
||||
/// 更新 workspace
|
||||
pub async fn update(&self, id: &WorkspaceId, updates: WorkspaceUpdate) -> Result<Workspace>;
|
||||
|
||||
/// 删除 workspace
|
||||
pub async fn delete(&self, id: &WorkspaceId) -> Result<()>;
|
||||
|
||||
/// 设置默认 workspace
|
||||
pub async fn set_default(&self, id: &WorkspaceId) -> Result<()>;
|
||||
|
||||
/// 获取默认 workspace
|
||||
pub async fn get_default(&self) -> Result<Option<Workspace>>;
|
||||
|
||||
/// 获取 workspace 下的所有 sessions(通过 working_dir 关联)
|
||||
pub async fn list_sessions(&self, workspace_id: &WorkspaceId) -> Result<Vec<Session>> {
|
||||
let workspace = self.get(workspace_id).await?;
|
||||
|
||||
// 使用 Aster 的 SessionManager,按 working_dir 过滤
|
||||
let all_sessions = aster::session::SessionManager::list_sessions().await?;
|
||||
|
||||
Ok(all_sessions
|
||||
.into_iter()
|
||||
.filter(|s| s.working_dir == workspace.root_path)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// 在 workspace 中创建新 session
|
||||
pub async fn create_session(
|
||||
&self,
|
||||
workspace_id: &WorkspaceId,
|
||||
name: String
|
||||
) -> Result<Session> {
|
||||
let workspace = self.get(workspace_id).await?;
|
||||
|
||||
// 使用 Aster 的 SessionManager
|
||||
aster::session::SessionManager::create_session(
|
||||
workspace.root_path.clone(),
|
||||
name,
|
||||
aster::session::SessionType::User,
|
||||
).await
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Tauri 命令
|
||||
|
||||
```rust
|
||||
// proxycast/src-tauri/src/commands/workspace_cmd.rs
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn workspace_create(name: String, root_path: String) -> Result<Workspace, String>;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn workspace_list() -> Result<Vec<Workspace>, String>;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn workspace_get(id: String) -> Result<Workspace, String>;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn workspace_update(id: String, updates: WorkspaceUpdate) -> Result<Workspace, String>;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn workspace_delete(id: String) -> Result<(), String>;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn workspace_set_default(id: String) -> Result<(), String>;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn workspace_list_sessions(workspace_id: String) -> Result<Vec<SessionInfo>, String>;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn workspace_create_session(workspace_id: String, name: String) -> Result<String, String>;
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
~/.aster/ # Aster 框架目录(不修改)
|
||||
├── config/
|
||||
├── data/
|
||||
│ └── sessions/
|
||||
│ └── sessions.db # Aster 管理的 session 数据库
|
||||
└── state/
|
||||
|
||||
~/Library/Application Support/proxycast/ # ProxyCast 应用目录
|
||||
├── proxycast.db # 应用数据库(包含 workspaces 表)
|
||||
├── credentials/ # 凭证文件
|
||||
└── workspaces/ # workspace 级配置
|
||||
├── default/
|
||||
│ └── mcp.json # workspace 级 MCP 配置
|
||||
└── my-project/
|
||||
└── mcp.json
|
||||
```
|
||||
|
||||
## 前端组件
|
||||
|
||||
### WorkspaceSelector
|
||||
|
||||
```typescript
|
||||
// proxycast/src/components/workspace/WorkspaceSelector.tsx
|
||||
|
||||
interface Workspace {
|
||||
id: string;
|
||||
name: string;
|
||||
rootPath: string;
|
||||
isDefault: boolean;
|
||||
workspaceType: 'persistent' | 'temporary';
|
||||
}
|
||||
|
||||
export function WorkspaceSelector() {
|
||||
const [workspaces, setWorkspaces] = useState<Workspace[]>([]);
|
||||
const [current, setCurrent] = useState<Workspace | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
invoke('workspace_list').then(setWorkspaces);
|
||||
invoke('workspace_get_default').then(setCurrent);
|
||||
}, []);
|
||||
|
||||
const switchWorkspace = async (id: string) => {
|
||||
await invoke('workspace_set_default', { id });
|
||||
const ws = workspaces.find(w => w.id === id);
|
||||
setCurrent(ws || null);
|
||||
// 触发 session 列表刷新
|
||||
};
|
||||
|
||||
return (
|
||||
<Select value={current?.id} onValueChange={switchWorkspace}>
|
||||
{workspaces.map(ws => (
|
||||
<SelectItem key={ws.id} value={ws.id}>
|
||||
<FolderIcon /> {ws.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
<SelectItem value="__new__">
|
||||
<PlusIcon /> 添加工作目录...
|
||||
</SelectItem>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 实现优先级
|
||||
|
||||
### Phase 1: 基础功能
|
||||
1. 数据库 schema 迁移
|
||||
2. WorkspaceManager 核心 CRUD
|
||||
3. Tauri 命令实现
|
||||
4. WorkspaceSelector 组件
|
||||
|
||||
### Phase 2: 集成功能
|
||||
1. Session 列表按 Workspace 分组
|
||||
2. 创建 Session 时自动关联当前 Workspace
|
||||
3. Workspace 级别的 MCP 配置
|
||||
|
||||
### Phase 3: 高级功能
|
||||
1. 临时 Workspace 支持
|
||||
2. Workspace 导入/导出
|
||||
3. Workspace 级别的 Provider 配置
|
||||
|
||||
## 设计决策
|
||||
|
||||
| 决策点 | 选择 | 理由 |
|
||||
|--------|------|------|
|
||||
| Workspace 存储位置 | ProxyCast 应用层 | 不修改 Aster 框架,保持框架通用性 |
|
||||
| 与 Session 关系 | 通过 working_dir 关联 | 利用 Aster 现有字段,无需修改框架 |
|
||||
| 配置存储 | 独立目录 + 数据库 | 配置文件便于编辑,元数据存数据库 |
|
||||
| 默认 Workspace | 支持 | 新 Session 自动关联默认 Workspace |
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [Context Engineering for AI Agents](https://www.philschmid.de/context-engineering-part-2) - Manus 团队经验
|
||||
- [Scaling State-Modifying AI Agents with MCP Workspaces](https://www.ai21.com/blog/stateful-agent-workspaces-mcp) - AI21 的 MCP Workspace 扩展
|
||||
- [Iterating with shadow workspaces](https://www.cursor.com/blog/shadow-workspace) - Cursor 的 Shadow Workspace 实现
|
||||
- [Managing Claude Code's Context](https://www.cometapi.com/managing-claude-codes-context/) - Claude Code 的 Context 管理
|
||||
Reference in New Issue
Block a user