feat: 语音输入合并到截图输入框,版本更新至 0.49.0

- 将语音输入功能合并到截图输入框(pill-shaped 浮动栏)
- 添加麦克风按钮(32x32px)支持点击录音
- 支持录音、转写、润色三种状态显示
- 修复讯飞 ASR Broken pipe 错误
- 清理旧的语音输入 UI 文件
- 修复 DropdownMenu 组件支持受控模式
- 修复 StreamResponse 导出问题

注:部分编译错误待后续修复
This commit is contained in:
coso
2026-01-31 00:01:20 +08:00
parent 2a7ed06d21
commit 562464f53f
94 changed files with 8961 additions and 196 deletions
+4
View File
@@ -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
```
## 更新提醒
+317
View File
@@ -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 管理