diff --git a/src/main/tool-handlers.ts b/src/main/tool-handlers.ts
index 5720906..0b27a34 100644
--- a/src/main/tool-handlers.ts
+++ b/src/main/tool-handlers.ts
@@ -298,12 +298,19 @@ export async function handleRunCommand(params: { command: string; cwd?: string;
return new Promise((resolve) => {
const isWin = process.platform === 'win32';
const shell = isWin ? 'cmd.exe' : 'bash';
- const shellArgs = isWin ? ['/c', params.command] : ['-c', params.command];
+ const actualCmd = isWin ? `chcp 65001 >nul && ${params.command}` : params.command;
+ const shellArgs = isWin ? ['/c', actualCmd] : ['-c', actualCmd];
const startTime = Date.now();
_toolProc = spawn(shell, shellArgs, {
cwd,
- env: { ...process.env, ...(isWin ? {} : { TERM: 'xterm-256color' }) },
+ env: {
+ ...process.env,
+ ...(isWin ? {} : { TERM: 'xterm-256color' }),
+ LANG: 'en_US.UTF-8',
+ LC_ALL: 'en_US.UTF-8',
+ PYTHONIOENCODING: 'utf-8'
+ },
stdio: ['pipe', 'pipe', 'pipe']
});
@@ -312,13 +319,13 @@ export async function handleRunCommand(params: { command: string; cwd?: string;
// 实时推送到工作空间终端
_toolProc.stdout?.on('data', (data: Buffer) => {
- const str = data.toString();
+ const str = data.toString('utf-8');
stdout += str;
mainWindow?.webContents.send('cmd:output', { command: params.command, type: 'stdout', data: str });
});
_toolProc.stderr?.on('data', (data: Buffer) => {
- const str = data.toString();
+ const str = data.toString('utf-8');
stderr += str;
mainWindow?.webContents.send('cmd:output', { command: params.command, type: 'stderr', data: str });
});
diff --git a/src/main/workspace.ts b/src/main/workspace.ts
index c8c11eb..5b432ab 100644
--- a/src/main/workspace.ts
+++ b/src/main/workspace.ts
@@ -88,21 +88,30 @@ export function startProcess(
try {
const isWin = process.platform === 'win32';
const shell = isWin ? 'cmd.exe' : 'bash';
- const shellArgs = isWin ? ['/c', command] : ['-c', command];
+ // Windows cmd 默认代码页为 GBK/CP936,中文输出会乱码
+ // chcp 65001 切换到 UTF-8 代码页,>nul 抑制 chcp 自身的输出
+ const actualCmd = isWin ? `chcp 65001 >nul && ${command}` : command;
+ const shellArgs = isWin ? ['/c', actualCmd] : ['-c', actualCmd];
const proc = spawn(shell, shellArgs, {
cwd: workDir,
- env: { ...process.env, ...(isWin ? {} : { TERM: 'xterm-256color' }) },
+ env: {
+ ...process.env,
+ ...(isWin ? {} : { TERM: 'xterm-256color' }),
+ LANG: 'en_US.UTF-8',
+ LC_ALL: 'en_US.UTF-8',
+ PYTHONIOENCODING: 'utf-8'
+ },
stdio: ['pipe', 'pipe', 'pipe']
});
activeProcesses.set(id, proc);
proc.stdout.on('data', (data: Buffer) => {
- onOutput('stdout', data.toString());
+ onOutput('stdout', data.toString('utf-8'));
});
proc.stderr.on('data', (data: Buffer) => {
- onOutput('stderr', data.toString());
+ onOutput('stderr', data.toString('utf-8'));
});
proc.on('close', (code) => {
@@ -246,17 +255,24 @@ export function execWorkspaceCommand(
try {
const isWin = process.platform === 'win32';
const shell = isWin ? 'cmd.exe' : 'bash';
- const shellArgs = isWin ? ['/c', command] : ['-c', command];
+ const actualCmd = isWin ? `chcp 65001 >nul && ${command}` : command;
+ const shellArgs = isWin ? ['/c', actualCmd] : ['-c', actualCmd];
const proc = spawn(shell, shellArgs, {
cwd: workDir,
- env: { ...process.env, ...(isWin ? {} : { TERM: 'xterm-256color' }) },
+ env: {
+ ...process.env,
+ ...(isWin ? {} : { TERM: 'xterm-256color' }),
+ LANG: 'en_US.UTF-8',
+ LC_ALL: 'en_US.UTF-8',
+ PYTHONIOENCODING: 'utf-8'
+ },
stdio: ['pipe', 'pipe', 'pipe']
});
activeProcesses.set(cmdId, proc);
proc.stdout.on('data', (data: Buffer) => {
- const str = data.toString();
+ const str = data.toString('utf-8');
if (stdoutBuf.length < MAX_OUTPUT) {
stdoutBuf += str;
if (stdoutBuf.length > MAX_OUTPUT) {
@@ -271,7 +287,7 @@ export function execWorkspaceCommand(
});
proc.stderr.on('data', (data: Buffer) => {
- const str = data.toString();
+ const str = data.toString('utf-8');
if (stderrBuf.length < MAX_OUTPUT) {
stderrBuf += str;
if (stderrBuf.length > MAX_OUTPUT) {
diff --git a/src/renderer/index.html b/src/renderer/index.html
index 65185e2..7c320d8 100644
--- a/src/renderer/index.html
+++ b/src/renderer/index.html
@@ -34,7 +34,7 @@
- 🪙 0
+ 0
diff --git a/src/renderer/main.ts b/src/renderer/main.ts
index a3c57cc..795157b 100644
--- a/src/renderer/main.ts
+++ b/src/renderer/main.ts
@@ -212,6 +212,8 @@ async function init(): Promise {
setupDesktopIntegration();
bindGlobalEvents();
+ updateNavHeight();
+ window.addEventListener('resize', updateNavHeight);
await checkConnection();
await loadModels();
@@ -283,10 +285,25 @@ async function init(): Promise {
initSettingsModal();
initHistoryModal();
setupDesktopIntegration();
+ updateNavHeight();
+ window.addEventListener('resize', updateNavHeight);
checkConnection().then(loadModels);
}
}
+/**
+ * 动态计算顶部导航高度(header + model-bar),
+ * 设为 CSS 变量 --nav-height,供 workspace-panel 引用
+ */
+function updateNavHeight(): void {
+ const header = document.querySelector('.app-header');
+ const modelBar = document.querySelector('.model-bar');
+ const headerH = header?.getBoundingClientRect().height ?? 0;
+ const modelBarH = modelBar?.getBoundingClientRect().height ?? 0;
+ const total = Math.ceil(headerH + modelBarH);
+ document.documentElement.style.setProperty('--nav-height', `${total}px`);
+}
+
function bindGlobalEvents(): void {
document.querySelector('#btnNewChat')?.addEventListener('click', startNewSession);
@@ -298,6 +315,18 @@ function bindGlobalEvents(): void {
(document.querySelector('#helpModal') as HTMLElement).style.display = 'none';
(document.querySelector('#kbModal') as HTMLElement).style.display = 'none';
}
+
+ // Ctrl+K — 知识库管理
+ if (e.ctrlKey && e.key === 'k') {
+ e.preventDefault();
+ document.querySelector('#btnKB')?.dispatchEvent(new Event('click'));
+ }
+
+ // Ctrl+M — 记忆面板
+ if (e.ctrlKey && e.key === 'm') {
+ e.preventDefault();
+ document.querySelector('#btnMemory')?.dispatchEvent(new Event('click'));
+ }
});
window.addEventListener('error', (e) => {
diff --git a/src/renderer/styles/style.css b/src/renderer/styles/style.css
index 4a9368c..20f69ef 100644
--- a/src/renderer/styles/style.css
+++ b/src/renderer/styles/style.css
@@ -383,6 +383,13 @@ html, body {
user-select: none;
}
+.token-icon {
+ width: 14px;
+ height: 14px;
+ flex-shrink: 0;
+ opacity: 0.8;
+}
+
/* ═══ 模型选择栏 ═══ */
.model-bar {
display: flex;
@@ -2667,7 +2674,7 @@ html, body {
.workspace-panel {
position: fixed;
- top: 92px; /* header(44px) + border(1px) + model-bar(46px) + border(1px) */
+ top: var(--nav-height, 92px); /* JS 动态计算 header + model-bar 实际高度 */
right: 0;
bottom: 0;
width: 480px;