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:
@@ -25,6 +25,7 @@ interface ContextBuildParams {
|
|||||||
history?: MetonaMessage[];
|
history?: MetonaMessage[];
|
||||||
memories?: MetonaMemoryItem[];
|
memories?: MetonaMemoryItem[];
|
||||||
workspaceFiles?: WorkspaceFiles;
|
workspaceFiles?: WorkspaceFiles;
|
||||||
|
workspacePath?: string;
|
||||||
contextWindow?: number;
|
contextWindow?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,10 +44,11 @@ export class ContextBuilder {
|
|||||||
history = [],
|
history = [],
|
||||||
memories = [],
|
memories = [],
|
||||||
workspaceFiles,
|
workspaceFiles,
|
||||||
|
workspacePath,
|
||||||
contextWindow = 128_000,
|
contextWindow = 128_000,
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
const systemPrompt = this.buildSystemPrompt(workspaceFiles);
|
const systemPrompt = this.buildSystemPrompt(workspaceFiles, workspacePath);
|
||||||
const estimatedTokens = this.estimateTokens(history, memories, availableTools, userInput, systemPrompt);
|
const estimatedTokens = this.estimateTokens(history, memories, availableTools, userInput, systemPrompt);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -76,7 +78,7 @@ export class ContextBuilder {
|
|||||||
* 4. MEMORY.md(记忆)— 动态区
|
* 4. MEMORY.md(记忆)— 动态区
|
||||||
* 5. 内置安全准则 — 尾部锚定
|
* 5. 内置安全准则 — 尾部锚定
|
||||||
*/
|
*/
|
||||||
buildSystemPrompt(workspaceFiles?: WorkspaceFiles): MetonaSystemPrompt {
|
buildSystemPrompt(workspaceFiles?: WorkspaceFiles, workspacePath?: string): MetonaSystemPrompt {
|
||||||
let staticUserProfile = '';
|
let staticUserProfile = '';
|
||||||
|
|
||||||
// ===== 静态区:用户画像(变化不频繁,放入静态区利用 LLM 缓存)=====
|
// ===== 静态区:用户画像(变化不频繁,放入静态区利用 LLM 缓存)=====
|
||||||
@@ -99,6 +101,11 @@ export class ContextBuilder {
|
|||||||
// ===== 动态区:记忆 =====
|
// ===== 动态区:记忆 =====
|
||||||
const dynamicParts: string[] = [];
|
const dynamicParts: string[] = [];
|
||||||
|
|
||||||
|
// 注入当前工作空间路径(动态区,路径可能切换故不放入静态区)
|
||||||
|
if (workspacePath) {
|
||||||
|
dynamicParts.push(`## Current Workspace\nWorkspace root path: \`${workspacePath}\`\n\nAll relative paths in tool calls are resolved against this workspace root. Use this path when absolute paths are required (e.g., in run_command).`);
|
||||||
|
}
|
||||||
|
|
||||||
if (workspaceFiles?.memory) {
|
if (workspaceFiles?.memory) {
|
||||||
const memoryContent = this.extractContent(workspaceFiles.memory);
|
const memoryContent = this.extractContent(workspaceFiles.memory);
|
||||||
if (memoryContent) {
|
if (memoryContent) {
|
||||||
|
|||||||
@@ -24,6 +24,26 @@ import type { SandboxManager } from '../../sandbox/sandbox';
|
|||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
|
// ===== Windows 编码智能解码 =====
|
||||||
|
// 某些 Windows 程序(dir、systeminfo、ipconfig 等)不响应 chcp 65001,
|
||||||
|
// 仍按系统代码页(GBK/CP936)输出字节流。
|
||||||
|
// 策略:先尝试严格 UTF-8 解码,失败则用 GBK 解码,最后降级为 UTF-8 宽松模式。
|
||||||
|
function decodeBuffer(buf: Buffer): string {
|
||||||
|
if (buf.length === 0) return '';
|
||||||
|
try {
|
||||||
|
// 严格模式:包含非法 UTF-8 字节序列时抛错
|
||||||
|
return new TextDecoder('utf-8', { fatal: true }).decode(buf);
|
||||||
|
} catch {
|
||||||
|
// UTF-8 解码失败 → 用 GBK/CP936 解码(Windows 中文系统默认代码页)
|
||||||
|
try {
|
||||||
|
return new TextDecoder('gbk').decode(buf);
|
||||||
|
} catch {
|
||||||
|
// 最终降级:UTF-8 宽松模式(用 替换字符 代替非法字节)
|
||||||
|
return buf.toString('utf-8');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ===== 9. run_command =====
|
// ===== 9. run_command =====
|
||||||
|
|
||||||
export class RunCommandTool implements IMetonaTool {
|
export class RunCommandTool implements IMetonaTool {
|
||||||
@@ -100,10 +120,13 @@ export class RunCommandTool implements IMetonaTool {
|
|||||||
? `chcp 65001 >nul 2>&1 && ${command}`
|
? `chcp 65001 >nul 2>&1 && ${command}`
|
||||||
: command;
|
: command;
|
||||||
|
|
||||||
|
// 使用 encoding: 'buffer' 获取原始字节,手动智能解码
|
||||||
|
// 部分程序(如 dir、systeminfo)不响应 chcp 65001,仍输出 GBK 字节流
|
||||||
const { stdout, stderr } = await execAsync(finalCommand, {
|
const { stdout, stderr } = await execAsync(finalCommand, {
|
||||||
cwd: resolvedWorkdir,
|
cwd: resolvedWorkdir,
|
||||||
timeout,
|
timeout,
|
||||||
maxBuffer: 1024 * 1024, // 1MB
|
maxBuffer: 1024 * 1024, // 1MB
|
||||||
|
encoding: 'buffer', // 返回 Buffer 而非字符串,便于智能解码
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
NODE_ENV: 'production',
|
NODE_ENV: 'production',
|
||||||
@@ -118,18 +141,18 @@ export class RunCommandTool implements IMetonaTool {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
stdout: stdout.slice(0, 50_000),
|
stdout: decodeBuffer(stdout).slice(0, 50_000),
|
||||||
stderr: stderr.slice(0, 10_000),
|
stderr: decodeBuffer(stderr).slice(0, 10_000),
|
||||||
command,
|
command,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as { message: string; code?: number; stdout?: string; stderr?: string };
|
const err = error as { message: string; code?: number; stdout?: Buffer; stderr?: Buffer };
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: err.message,
|
error: err.message,
|
||||||
exit_code: err.code,
|
exit_code: err.code,
|
||||||
stdout: (err.stdout ?? '').slice(0, 10_000),
|
stdout: decodeBuffer(err.stdout ?? Buffer.alloc(0)).slice(0, 10_000),
|
||||||
stderr: (err.stderr ?? '').slice(0, 10_000),
|
stderr: decodeBuffer(err.stderr ?? Buffer.alloc(0)).slice(0, 10_000),
|
||||||
command,
|
command,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export function registerAllIPCHandlers(
|
|||||||
sessionRecorder: SessionRecorder,
|
sessionRecorder: SessionRecorder,
|
||||||
memoryManager: MemoryManager,
|
memoryManager: MemoryManager,
|
||||||
mcpManager: MCPManager,
|
mcpManager: MCPManager,
|
||||||
reloadAdapter: () => void,
|
reloadAdapter: () => boolean,
|
||||||
promptInjectionDefender: PromptInjectionDefender,
|
promptInjectionDefender: PromptInjectionDefender,
|
||||||
outputValidator: OutputValidator,
|
outputValidator: OutputValidator,
|
||||||
confirmationHook: ConfirmationHook,
|
confirmationHook: ConfirmationHook,
|
||||||
@@ -56,8 +56,29 @@ export function registerAllIPCHandlers(
|
|||||||
ipcMain.handle('agent:sendMessage', async (_event, userMessage: MetonaMessage, sessionId: string) => {
|
ipcMain.handle('agent:sendMessage', async (_event, userMessage: MetonaMessage, sessionId: string) => {
|
||||||
log.info('[AGENT] sendMessage:', sessionId, userMessage.content.slice(0, 80));
|
log.info('[AGENT] sendMessage:', sessionId, userMessage.content.slice(0, 80));
|
||||||
|
|
||||||
// 发送消息前确保 Adapter 使用最新配置(防止 config:set 中间状态导致 reload 失败)
|
// 发送消息前确保 Adapter 使用最新配置(失败则中止,防止用旧 Provider 的 adapter 发送)
|
||||||
reloadAdapter();
|
if (!reloadAdapter()) {
|
||||||
|
const errorMsg = 'Adapter 加载失败,请检查 LLM 配置(Provider、API Key、Base URL、Model 是否完整)';
|
||||||
|
log.error('[AGENT]', errorMsg);
|
||||||
|
if (!mainWindow.isDestroyed()) {
|
||||||
|
const errorEvent: MetonaStreamEvent = {
|
||||||
|
type: MetonaStreamEventType.ERROR,
|
||||||
|
requestId: '',
|
||||||
|
sessionId,
|
||||||
|
iteration: 0,
|
||||||
|
seq: 0,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
error: { code: MetonaErrorCode.UNKNOWN, message: errorMsg, retryable: false },
|
||||||
|
};
|
||||||
|
mainWindow.webContents.send('agent:streamEvent', errorEvent);
|
||||||
|
mainWindow.webContents.send('agent:streamEvent', {
|
||||||
|
...errorEvent,
|
||||||
|
type: MetonaStreamEventType.DONE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
sessionRecorder.stopRecording({ totalIterations: 0, totalTokens: 0, durationMs: 0, terminationReason: 'error' });
|
||||||
|
return { success: false, error: errorMsg };
|
||||||
|
}
|
||||||
|
|
||||||
// TRACE 层:开始录制
|
// TRACE 层:开始录制
|
||||||
sessionRecorder.startRecording(sessionId);
|
sessionRecorder.startRecording(sessionId);
|
||||||
@@ -88,9 +109,9 @@ export function registerAllIPCHandlers(
|
|||||||
timestamp: m.timestamp,
|
timestamp: m.timestamp,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 从工作空间文件构建 System Prompt
|
// 从工作空间文件构建 System Prompt(注入当前工作空间路径,便于 LLM 在需要绝对路径时使用)
|
||||||
const workspaceFiles = workspaceService.getFiles();
|
const workspaceFiles = workspaceService.getFiles();
|
||||||
const systemPrompt = contextBuilder.buildSystemPrompt(workspaceFiles);
|
const systemPrompt = contextBuilder.buildSystemPrompt(workspaceFiles, workspaceService.getPath());
|
||||||
|
|
||||||
// 检索与用户消息相关的记忆,注入到 System Prompt 动态区
|
// 检索与用户消息相关的记忆,注入到 System Prompt 动态区
|
||||||
try {
|
try {
|
||||||
|
|||||||
+4
-1
@@ -261,7 +261,7 @@ async function initialize(): Promise<void> {
|
|||||||
|
|
||||||
// ===== 热重载 Adapter 回调(设置变更时触发)=====
|
// ===== 热重载 Adapter 回调(设置变更时触发)=====
|
||||||
let lastProvider = configService.get<string>('llm.provider') ?? '';
|
let lastProvider = configService.get<string>('llm.provider') ?? '';
|
||||||
const reloadAdapter = () => {
|
const reloadAdapter = (): boolean => {
|
||||||
try {
|
try {
|
||||||
const newAdapter = createAdapter();
|
const newAdapter = createAdapter();
|
||||||
agentLoop.setAdapter(newAdapter);
|
agentLoop.setAdapter(newAdapter);
|
||||||
@@ -282,6 +282,7 @@ async function initialize(): Promise<void> {
|
|||||||
from: lastProvider,
|
from: lastProvider,
|
||||||
to: provider,
|
to: provider,
|
||||||
reason: 'config_changed',
|
reason: 'config_changed',
|
||||||
|
sessionId: '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
mainWindow.webContents.send('toast:show', {
|
mainWindow.webContents.send('toast:show', {
|
||||||
@@ -290,6 +291,7 @@ async function initialize(): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
lastProvider = provider;
|
lastProvider = provider;
|
||||||
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error(`[CONFIG] Failed to reload adapter: ${(err as Error).message}`);
|
log.error(`[CONFIG] Failed to reload adapter: ${(err as Error).message}`);
|
||||||
// 通知渲染进程 Provider 切换失败(UI 显示错误 Toast)
|
// 通知渲染进程 Provider 切换失败(UI 显示错误 Toast)
|
||||||
@@ -299,6 +301,7 @@ async function initialize(): Promise<void> {
|
|||||||
message: `Provider 切换失败: ${(err as Error).message}`,
|
message: `Provider 切换失败: ${(err as Error).message}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "metona-ai-desktop",
|
"name": "metona-ai-desktop",
|
||||||
"version": "0.2.1",
|
"version": "0.2.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "metona-ai-desktop",
|
"name": "metona-ai-desktop",
|
||||||
"version": "0.2.1",
|
"version": "0.2.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "metona-ai-desktop",
|
"name": "metona-ai-desktop",
|
||||||
"version": "0.2.1",
|
"version": "0.2.2",
|
||||||
"description": "MetonaAI Desktop — 生产级通用 AI Agent 智能体桌面应用",
|
"description": "MetonaAI Desktop — 生产级通用 AI Agent 智能体桌面应用",
|
||||||
"main": "dist-electron/main/main.js",
|
"main": "dist-electron/main/main.js",
|
||||||
"author": "Metona Team",
|
"author": "Metona Team",
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ function CodeBlock({ language, code }: { language: string; code: string }): Reac
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ position: 'relative', my: 1, '&:hover .copy-btn': { opacity: 1 } }}>
|
<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>
|
<span>{language}</span>
|
||||||
<Tooltip title={copied ? '已复制' : '复制'}>
|
<Tooltip title={copied ? '已复制' : '复制'}>
|
||||||
<IconButton className="copy-btn" size="small" onClick={handleCopy} sx={{ opacity: 0, transition: 'opacity 150ms', color: 'text.secondary' }}>
|
<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>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Stack>
|
</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>
|
<code className={language ? `language-${language}` : ''}>{code}</code>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -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 { X, Settings, Bot, Wrench, Server, Palette, FileText, Eye, EyeOff, FolderOpen, Globe } from 'lucide-react';
|
||||||
import { useUIStore, type ThemeMode } from '@renderer/stores/ui-store';
|
import { useUIStore, type ThemeMode } from '@renderer/stores/ui-store';
|
||||||
import { useAgentStore } from '@renderer/stores/agent-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';
|
type SettingsTab = 'workspace' | 'llm' | 'agent' | 'tools' | 'mcp' | 'searxng' | 'appearance' | 'logs';
|
||||||
const TABS: { id: SettingsTab; label: string; icon: typeof Settings }[] = [
|
const TABS: { id: SettingsTab; label: string; icon: typeof Settings }[] = [
|
||||||
@@ -118,11 +119,13 @@ function LLMSettings() {
|
|||||||
}
|
}
|
||||||
}, [provider, numCtx]);
|
}, [provider, numCtx]);
|
||||||
|
|
||||||
// 切换 Provider 时自动填充默认 URL + 清空 Ollama 的 apiKey
|
// 切换 Provider 时自动填充默认 URL + 清空 apiKey(不同 Provider 的 key 不通用)
|
||||||
const handleProviderChange = (newProvider: string) => {
|
const handleProviderChange = (newProvider: string) => {
|
||||||
|
const oldProvider = provider;
|
||||||
setProvider(newProvider);
|
setProvider(newProvider);
|
||||||
// Ollama 不需要 API Key,切换到 Ollama 时清空旧 key
|
// 切换 Provider 时清空 apiKey(不同 Provider 的 key 格式不同,避免用旧 key 调用新 API 导致 401)
|
||||||
if (newProvider === 'ollama' && apiKey) {
|
// Ollama 不需要 apiKey,也一并清空
|
||||||
|
if (oldProvider !== newProvider && apiKey) {
|
||||||
setApiKey('');
|
setApiKey('');
|
||||||
}
|
}
|
||||||
// 自动填充默认 URL(仅在 URL 为空或与旧 provider 默认 URL 匹配时)
|
// 自动填充默认 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="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" />
|
<TextField size="small" label="模型名称" value={model} onChange={(e) => setModel(e.target.value)} placeholder="如 deepseek-v4-pro、qwen3:latest" />
|
||||||
{provider !== 'ollama' && (
|
{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' && (
|
{provider === 'ollama' && (
|
||||||
<TextField
|
<TextField
|
||||||
@@ -292,9 +302,9 @@ function ToolsSettings() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
autoExecToolDetails.map((t) => (
|
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 }}>
|
<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 }} />
|
<Chip label="自动" size="small" color="success" sx={{ height: 16, fontSize: 9 }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
<Button size="small" color="warning" variant="outlined" sx={{ fontSize: 10, minWidth: 70 }} onClick={() => handleSetAutoExec(t.name, false)}>
|
<Button size="small" color="warning" variant="outlined" sx={{ fontSize: 10, minWidth: 70 }} onClick={() => handleSetAutoExec(t.name, false)}>
|
||||||
|
|||||||
@@ -216,17 +216,17 @@ code, pre, .font-mono { font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', mo
|
|||||||
}
|
}
|
||||||
|
|
||||||
.prose-metona pre {
|
.prose-metona pre {
|
||||||
background: #1e1e2e;
|
background: var(--bg-tertiary);
|
||||||
border: 1px solid #2a2d3a;
|
border: 1px solid var(--border-color);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
color: #cdd6f4;
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose-metona pre code {
|
.prose-metona pre code {
|
||||||
color: #cdd6f4;
|
color: var(--text-primary);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-size: 13px;
|
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 li { margin-bottom: 0.25em; }
|
||||||
|
|
||||||
.prose-metona table { width: 100%; border-collapse: collapse; margin-bottom: 1em; font-size: 13px; }
|
.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 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 #2a2d3a; }
|
.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 hr { border: none; height: 1px; background: #2a2d3a; margin: 1.5em 0; }
|
||||||
.prose-metona img { max-width: 100%; border-radius: 8px; }
|
.prose-metona img { max-width: 100%; border-radius: 8px; }
|
||||||
|
|||||||
Reference in New Issue
Block a user