feat: 升级至 v0.2.2 — 工作空间路径注入、Provider 切换修复、编码与样式优化

功能增强:
- System Prompt 注入当前工作空间路径到动态区(LLM 可使用绝对路径调用工具)
- ContextBuilder.buildSystemPrompt 新增 workspacePath 参数

Provider 切换修复:
- reloadAdapter 返回 boolean,失败时 sendMessage 中止发送
- 切换 Provider 时自动清空 apiKey(不同 Provider key 不通用)
- apiKey 为空时显示警告提示
- Provider 切换事件加 sessionId 字段

编码修复:
- run_command 使用 encoding: 'buffer' 获取原始字节
- 新增 decodeBuffer 智能解码:UTF-8 严格 → GBK → UTF-8 宽松
- 修复不响应 chcp 的 Windows 命令中文乱码问题

样式优化:
- 代码块底色改用主题变量(灰底主题色文字,双主题适配)
- 表格标题 th/td 底色和文字色改用主题变量
- CodeBlock 组件 pre 背景从 secondary.main 改为 background.default
- 设置面板已自动执行工具行 opacity 0.8→0.15(淡绿底,文字清晰)

版本号 0.2.1 → 0.2.2
This commit is contained in:
thzxx
2026-07-12 13:46:09 +08:00
parent 6e49c44742
commit aa13a98f63
9 changed files with 96 additions and 32 deletions
+2 -2
View File
@@ -169,7 +169,7 @@ function CodeBlock({ language, code }: { language: string; code: string }): Reac
return (
<Box sx={{ position: 'relative', my: 1, '&:hover .copy-btn': { opacity: 1 } }}>
<Stack direction="row" sx={{ px: 1.5, py: 0.75, borderTopLeftRadius: 8, borderTopRightRadius: 8, borderBottom: '1px solid', borderColor: 'divider', bgcolor: 'background.default', fontSize: 11, color: 'text.secondary', justifyContent: 'space-between', alignItems: 'center' }}>
<Stack direction="row" sx={{ px: 1.5, py: 0.75, borderTopLeftRadius: 8, borderTopRightRadius: 8, borderBottom: '1px solid', borderColor: 'divider', bgcolor: 'background.paper', fontSize: 11, color: 'text.secondary', justifyContent: 'space-between', alignItems: 'center' }}>
<span>{language}</span>
<Tooltip title={copied ? '已复制' : '复制'}>
<IconButton className="copy-btn" size="small" onClick={handleCopy} sx={{ opacity: 0, transition: 'opacity 150ms', color: 'text.secondary' }}>
@@ -177,7 +177,7 @@ function CodeBlock({ language, code }: { language: string; code: string }): Reac
</IconButton>
</Tooltip>
</Stack>
<Box component="pre" sx={{ m: 0, bgcolor: 'secondary.main', border: '1px solid', borderColor: 'divider', borderTop: 'none', borderBottomLeftRadius: 8, borderBottomRightRadius: 8, p: 1.5, overflowX: 'auto', fontSize: 13, lineHeight: 1.6 }}>
<Box component="pre" sx={{ m: 0, bgcolor: 'background.default', border: '1px solid', borderColor: 'divider', borderTop: 'none', borderBottomLeftRadius: 8, borderBottomRightRadius: 8, p: 1.5, overflowX: 'auto', fontSize: 13, lineHeight: 1.6, color: 'text.primary' }}>
<code className={language ? `language-${language}` : ''}>{code}</code>
</Box>
</Box>
+18 -8
View File
@@ -7,6 +7,7 @@ import { Dialog, DialogContent, DialogTitle, Button, TextField, Select, MenuItem
import { X, Settings, Bot, Wrench, Server, Palette, FileText, Eye, EyeOff, FolderOpen, Globe } from 'lucide-react';
import { useUIStore, type ThemeMode } from '@renderer/stores/ui-store';
import { useAgentStore } from '@renderer/stores/agent-store';
import { PROVIDER_LABELS } from '@renderer/lib/constants';
type SettingsTab = 'workspace' | 'llm' | 'agent' | 'tools' | 'mcp' | 'searxng' | 'appearance' | 'logs';
const TABS: { id: SettingsTab; label: string; icon: typeof Settings }[] = [
@@ -118,11 +119,13 @@ function LLMSettings() {
}
}, [provider, numCtx]);
// 切换 Provider 时自动填充默认 URL + 清空 Ollama 的 apiKey
// 切换 Provider 时自动填充默认 URL + 清空 apiKey(不同 Provider 的 key 不通用)
const handleProviderChange = (newProvider: string) => {
const oldProvider = provider;
setProvider(newProvider);
// Ollama 不需要 API Key,切换到 Ollama 时清空旧 key
if (newProvider === 'ollama' && apiKey) {
// 切换 Provider 时清空 apiKey(不同 Provider 的 key 格式不同,避免用旧 key 调用新 API 导致 401
// Ollama 不需要 apiKey,也一并清空
if (oldProvider !== newProvider && apiKey) {
setApiKey('');
}
// 自动填充默认 URL(仅在 URL 为空或与旧 provider 默认 URL 匹配时)
@@ -146,9 +149,16 @@ function LLMSettings() {
<TextField size="small" label="API Base URL" value={baseURL} onChange={(e) => setBaseURL(e.target.value)} placeholder="如 https://api.deepseek.com" />
<TextField size="small" label="模型名称" value={model} onChange={(e) => setModel(e.target.value)} placeholder="如 deepseek-v4-pro、qwen3:latest" />
{provider !== 'ollama' && (
<TextField size="small" label="API Key" type={showKey ? 'text' : 'password'} value={apiKey} onChange={(e) => setApiKey(e.target.value)} placeholder="sk-..."
slotProps={{ input: { endAdornment: <IconButton size="small" onClick={() => setShowKey(!showKey)}>{showKey ? <EyeOff size={14} /> : <Eye size={14} />}</IconButton> } }}
/>
<>
<TextField size="small" label="API Key" type={showKey ? 'text' : 'password'} value={apiKey} onChange={(e) => setApiKey(e.target.value)} placeholder="sk-..."
slotProps={{ input: { endAdornment: <IconButton size="small" onClick={() => setShowKey(!showKey)}>{showKey ? <EyeOff size={14} /> : <Eye size={14} />}</IconButton> } }}
/>
{!apiKey && (
<Typography variant="caption" sx={{ color: 'warning.main', fontSize: 11 }}>
{PROVIDER_LABELS[provider] ?? provider} API Key
</Typography>
)}
</>
)}
{provider === 'ollama' && (
<TextField
@@ -292,9 +302,9 @@ function ToolsSettings() {
</Typography>
) : (
autoExecToolDetails.map((t) => (
<Stack key={t.name} direction="row" sx={{ py: 1, px: 1.5, borderRadius: 1.5, bgcolor: 'success.main', opacity: 0.8, justifyContent: 'space-between', alignItems: 'center' }}>
<Stack key={t.name} direction="row" sx={{ py: 1, px: 1.5, borderRadius: 1.5, bgcolor: 'success.main', opacity: 0.15, justifyContent: 'space-between', alignItems: 'center' }}>
<Stack direction="row" spacing={1} sx={{ alignItems: 'center', minWidth: 0, flex: 1 }}>
<Typography variant="body2" sx={{ fontFamily: 'monospace', fontSize: 12 }}>{t.name}</Typography>
<Typography variant="body2" sx={{ fontFamily: 'monospace', fontSize: 12, color: 'text.primary' }}>{t.name}</Typography>
<Chip label="自动" size="small" color="success" sx={{ height: 16, fontSize: 9 }} />
</Stack>
<Button size="small" color="warning" variant="outlined" sx={{ fontSize: 10, minWidth: 70 }} onClick={() => handleSetAutoExec(t.name, false)}>
+6 -6
View File
@@ -216,17 +216,17 @@ code, pre, .font-mono { font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', mo
}
.prose-metona pre {
background: #1e1e2e;
border: 1px solid #2a2d3a;
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 16px;
overflow-x: auto;
margin-bottom: 1em;
color: #cdd6f4;
color: var(--text-primary);
}
.prose-metona pre code {
color: #cdd6f4;
color: var(--text-primary);
background: transparent;
padding: 0;
font-size: 13px;
@@ -260,8 +260,8 @@ code, pre, .font-mono { font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', mo
.prose-metona li { margin-bottom: 0.25em; }
.prose-metona table { width: 100%; border-collapse: collapse; margin-bottom: 1em; font-size: 13px; }
.prose-metona th { text-align: left; padding: 8px 12px; background: #252836; border-bottom: 1px solid #2a2d3a; color: #64748b; font-weight: 600; }
.prose-metona td { padding: 8px 12px; border-bottom: 1px solid #2a2d3a; }
.prose-metona th { text-align: left; padding: 8px 12px; background: var(--bg-tertiary); border-bottom: 1px solid var(--border-color); color: var(--text-primary); font-weight: 600; }
.prose-metona td { padding: 8px 12px; border-bottom: 1px solid var(--border-color); color: var(--text-primary); }
.prose-metona hr { border: none; height: 1px; background: #2a2d3a; margin: 1.5em 0; }
.prose-metona img { max-width: 100%; border-radius: 8px; }