feat(permission): 实现 WebSocket 权限确认机制

重构权限系统,将终端 UI 代码从 core 模块移除,实现基于 WebSocket 的权限确认流程:

Core 模块清理:
- 删除 permission/prompt.ts 和 file-prompt.ts(终端交互)
- 删除 diff.ts 中的 chalk 渲染函数
- 删除 config.ts 中的 inquirer 交互
- 移除 chalk 依赖

Server 权限处理:
- 新增 permission/handler.ts,实现 WebSocket 权限请求/响应
- 更新 agent/adapter.ts 设置权限回调
- 更新 ws.ts 处理 permission_response 消息

Web 权限组件:
- 新增 PermissionDialog 组件,显示权限请求详情和 Diff
- 更新 useChat hook 管理权限状态
- 更新 Chat 页面集成权限弹窗
This commit is contained in:
2025-12-13 01:09:35 +08:00
parent 5d4afecd48
commit 1d69fd876d
20 changed files with 739 additions and 1560 deletions
-161
View File
@@ -223,164 +223,3 @@ export function saveConfig(config: Partial<StoredConfig>): void {
fs.writeFileSync(CONFIG_FILE, JSON.stringify(newConfig, null, 2));
}
// 初始化配置向导
export async function initConfig(): Promise<void> {
const { default: inquirer } = await import('inquirer');
console.log('\n🔧 初始化 AI Terminal Assistant 配置\n');
// 选择 provider
const { provider } = await inquirer.prompt([
{
type: 'list',
name: 'provider',
message: '选择 AI 服务商:',
choices: [
{ name: 'Anthropic (Claude)', value: 'anthropic' },
{ name: 'OpenAI (GPT)', value: 'openai' },
{ name: 'OpenAI 兼容服务 (阿里云百炼、Azure 等)', value: 'openai-compatible' },
{ name: 'DeepSeek', value: 'deepseek' },
],
default: 'anthropic',
},
]);
// 是否是 OpenAI 兼容服务
const isOpenAICompatible = provider === 'openai-compatible';
const actualProvider = isOpenAICompatible ? 'openai' : provider;
// 如果是 OpenAI 兼容服务,询问 base URL
let baseUrl: string | undefined;
if (isOpenAICompatible) {
const { customBaseUrl } = await inquirer.prompt([
{
type: 'input',
name: 'customBaseUrl',
message: '请输入 API 基础 URL (如: https://dashscope.aliyuncs.com/compatible-mode/v1):',
validate: (input: string) => {
if (!input) return 'Base URL 不能为空';
try {
new URL(input);
return true;
} catch {
return '请输入有效的 URL';
}
},
},
]);
baseUrl = customBaseUrl;
}
// 根据 provider 显示不同的模型选项
let modelChoices: Array<{ name: string; value: string }>;
let allowCustomModel = false;
if (actualProvider === 'anthropic') {
modelChoices = [
{ name: 'Claude Sonnet 4 (推荐,平衡性能和成本)', value: 'claude-sonnet-4-20250514' },
{ name: 'Claude Opus 4 (最强,成本较高)', value: 'claude-opus-4-20250514' },
{ name: 'Claude 3.5 Haiku (快速,成本低)', value: 'claude-3-5-haiku-20241022' },
];
} else if (actualProvider === 'openai') {
if (isOpenAICompatible) {
// OpenAI 兼容服务允许自定义模型名称
modelChoices = [
{ name: 'qwen-plus (通义千问)', value: 'qwen-plus' },
{ name: 'qwen-turbo (通义千问快速版)', value: 'qwen-turbo' },
{ name: 'qwen-max (通义千问最强版)', value: 'qwen-max' },
{ name: 'gpt-4o', value: 'gpt-4o' },
{ name: '自定义模型名称...', value: '__custom__' },
];
allowCustomModel = true;
} else {
modelChoices = [
{ name: 'GPT-4o (推荐,支持 vision)', value: 'gpt-4o' },
{ name: 'GPT-4o mini (快速,成本低)', value: 'gpt-4o-mini' },
{ name: 'GPT-4 Turbo', value: 'gpt-4-turbo' },
{ name: 'o1 (推理增强)', value: 'o1' },
{ name: 'o1-mini (推理,成本低)', value: 'o1-mini' },
];
}
} else {
modelChoices = [
{ name: 'DeepSeek Chat (推荐)', value: 'deepseek-chat' },
{ name: 'DeepSeek Reasoner (推理增强)', value: 'deepseek-reasoner' },
];
}
const apiKeyMessageMap: Record<string, string> = {
anthropic: '请输入你的 Anthropic API Key:',
openai: '请输入你的 OpenAI API Key:',
deepseek: '请输入你的 DeepSeek API Key:',
};
// 分开询问 API Key
const { apiKey } = await inquirer.prompt([
{
type: 'password',
name: 'apiKey',
message: isOpenAICompatible ? '请输入你的 API Key:' : apiKeyMessageMap[actualProvider],
validate: (input: string) => input.length > 0 || 'API Key 不能为空',
},
]);
// 询问模型配置
const { model: selectedModel } = await inquirer.prompt([
{
type: 'list',
name: 'model',
message: '选择默认模型:',
choices: modelChoices,
default: DEFAULT_MODELS[actualProvider as ProviderType],
},
]);
// 如果选择自定义模型,询问模型名称
let finalModel = selectedModel;
if (allowCustomModel && selectedModel === '__custom__') {
const { customModel } = await inquirer.prompt([
{
type: 'input',
name: 'customModel',
message: '请输入模型名称:',
validate: (input: string) => input.length > 0 || '模型名称不能为空',
},
]);
finalModel = customModel;
}
// 询问 token 配置
const { maxTokens } = await inquirer.prompt([
{
type: 'number',
name: 'maxTokens',
message: '最大输出 token 数:',
default: 4096,
},
]);
// 根据 provider 构建配置对象
const configToSave: Partial<StoredConfig> = {
provider: actualProvider as ProviderType,
model: finalModel,
maxTokens,
};
// 存储 API Key 到对应字段
if (actualProvider === 'anthropic') {
configToSave.apiKey = apiKey;
} else if (actualProvider === 'openai') {
configToSave.openaiApiKey = apiKey;
} else if (actualProvider === 'deepseek') {
configToSave.deepseekApiKey = apiKey;
}
// 存储 base URL
if (baseUrl) {
configToSave.baseUrl = baseUrl;
}
saveConfig(configToSave);
console.log('\n✅ 配置已保存到', CONFIG_FILE);
console.log('现在可以运行 ai-assist 开始使用了!\n');
}