4 Commits

Author SHA1 Message Date
f933d84cd1 feat: 新增会话压缩命令和上下文显示功能
- ICHelperPanel: 新增 compressConversation 命令处理,支持手动触发会话压缩
- ICHelperPanel: 在加载历史会话时设置 lastTaskId,确保压缩操作可用
- webviewContent: 新增 contextUsage 消息处理,更新上下文使用量显示
- userInteraction: 将用户回答超时时间从 5 分钟延长至 2 小时
2025-12-31 18:50:27 +08:00
b794d1ceb0 feat: 实现上下文使用量监控和会话压缩功能
- sseHandler: 新增 onContextUsage 回调处理上下文使用量事件
- dialogService: 集成上下文使用量回调,追踪 AI 消息用于后端重启恢复
- apiClient: 新增 compactDialog API 支持手动压缩对话历史
- messageHandler: 新增 lastTaskId 管理机制,支持会话恢复后的压缩操作,转发上下文使用量到 WebView
2025-12-31 18:50:20 +08:00
259310a29d feat: 新增上下文使用量事件类型定义
- 新增 context_usage 事件类型
- 新增 ContextUsageEvent 接口,包含当前 token 数、最大 token 数和使用百分比
- 用于实时监控对话上下文的使用情况
2025-12-31 18:50:11 +08:00
715eac5949 feat: 新增多环境配置支持
- 新增 dev/test/prod 三种环境配置
- 支持通过 CURRENT_ENV 常量快速切换环境
- 重构配置获取逻辑,使用环境映射表
- 新增 getCurrentEnv() 方法获取当前环境
2025-12-31 18:50:05 +08:00
9 changed files with 154 additions and 13 deletions

View File

@ -1,9 +1,15 @@
/** /**
* 配置管理 * 配置管理
* 后端地址已预配置,用户无需手动设置 * 支持 dev本地开发和 test测试服务器两种环境
*/ */
import * as vscode from "vscode"; import * as vscode from "vscode";
/** 环境类型 */
type Environment = "dev" | "test" | "prod";
/** 当前环境 - 修改这里切换环境 */
const CURRENT_ENV: Environment = "dev";
/** 配置项接口 */ /** 配置项接口 */
export interface IccoderConfig { export interface IccoderConfig {
/** 后端服务地址 */ /** 后端服务地址 */
@ -14,19 +20,40 @@ export interface IccoderConfig {
userId: string; userId: string;
} }
/** 默认配置(预配置,不暴露给用户) */ /** 环境配置 */
const DEFAULT_CONFIG: IccoderConfig = { const ENV_CONFIG: Record<Environment, IccoderConfig> = {
/** 本地开发环境 */
dev: {
backendUrl: "http://localhost:2233",
timeout: 60000,
userId: "default-user",
},
/** 测试服务器环境 */
test: {
backendUrl: "http://192.168.1.108:2233", backendUrl: "http://192.168.1.108:2233",
timeout: 60000, timeout: 60000,
userId: "default-user", userId: "default-user",
},
/** 生产环境 */
prod: {
backendUrl: "https://api.iccoder.com", // TODO: 替换为实际生产地址
timeout: 60000,
userId: "default-user",
},
}; };
/**
* 获取当前环境
*/
export function getCurrentEnv(): Environment {
return CURRENT_ENV;
}
/** /**
* 获取配置项 * 获取配置项
* 直接返回预配置的值,用户无需手动配置
*/ */
export function getConfig(): IccoderConfig { export function getConfig(): IccoderConfig {
return { ...DEFAULT_CONFIG }; return { ...ENV_CONFIG[CURRENT_ENV] };
} }
/** /**
@ -34,7 +61,6 @@ export function getConfig(): IccoderConfig {
*/ */
export function getApiUrl(path: string): string { export function getApiUrl(path: string): string {
const { backendUrl } = getConfig(); const { backendUrl } = getConfig();
// 确保 URL 格式正确
const baseUrl = backendUrl.endsWith("/") const baseUrl = backendUrl.endsWith("/")
? backendUrl.slice(0, -1) ? backendUrl.slice(0, -1)
: backendUrl; : backendUrl;

View File

@ -12,7 +12,9 @@ import {
handlePlanAction, handlePlanAction,
setPendingPlanExecution, setPendingPlanExecution,
getCurrentTaskId, getCurrentTaskId,
setLastTaskId,
} from "../utils/messageHandler"; } from "../utils/messageHandler";
import { compactDialog } from "../services/apiClient";
import { VCDViewerPanel } from "./VCDViewerPanel"; import { VCDViewerPanel } from "./VCDViewerPanel";
import { ChatHistoryManager } from "../utils/chatHistoryManager"; import { ChatHistoryManager } from "../utils/chatHistoryManager";
import { MessageType } from "../types/chatHistory"; import { MessageType } from "../types/chatHistory";
@ -195,6 +197,39 @@ export async function showICHelperPanel(
case "abortDialog": case "abortDialog":
void abortCurrentDialog(); void abortCurrentDialog();
break; break;
// 新增:压缩会话
case "compressConversation":
{
const taskId = getCurrentTaskId();
if (taskId) {
compactDialog(taskId)
.then((result) => {
if (result.success) {
panel.webview.postMessage({
command: "receiveMessage",
text: "✅ 会话压缩完成",
});
} else {
panel.webview.postMessage({
command: "receiveMessage",
text: `❌ 压缩失败: ${result.error || "未知错误"}`,
});
}
})
.catch((err) => {
panel.webview.postMessage({
command: "receiveMessage",
text: `❌ 压缩失败: ${err.message || "网络错误"}`,
});
});
} else {
panel.webview.postMessage({
command: "receiveMessage",
text: "❌ 没有活跃的会话",
});
}
}
break;
// 处理计划操作(只做模式切换,响应已通过 submitAnswer 发送) // 处理计划操作(只做模式切换,响应已通过 submitAnswer 发送)
case "planAction": case "planAction":
if (message.action === "confirm") { if (message.action === "confirm") {
@ -528,6 +563,9 @@ async function selectConversation(
return; return;
} }
// 设置 lastTaskId用于压缩等操作
setLastTaskId(taskId);
// 更新面板的任务映射,确保后续对话保存到正确的任务中 // 更新面板的任务映射,确保后续对话保存到正确的任务中
const panelId = (panel as any).__uniqueId; const panelId = (panel as any).__uniqueId;
historyManager.setPanelTask(panelId, taskId, workspacePath); historyManager.setPanelTask(panelId, taskId, workspacePath);

View File

@ -155,6 +155,26 @@ export async function stopDialog(taskId: string): Promise<StopDialogResponse> {
}); });
} }
/** 压缩对话响应 */
export interface CompactDialogResponse {
success: boolean;
taskId: string;
message?: string;
error?: string;
}
/**
* 手动压缩对话历史
* POST /api/dialog/compact
*/
export async function compactDialog(taskId: string): Promise<CompactDialogResponse> {
console.log(`[API] 压缩对话: taskId=${taskId}`);
return request<CompactDialogResponse>('/api/dialog/compact', {
method: 'POST',
body: { taskId }
});
}
/** /**
* 创建成功的工具结果 * 创建成功的工具结果
*/ */

View File

@ -73,6 +73,8 @@ export interface DialogCallbacks {
onError?: (message: string) => void; onError?: (message: string) => void;
/** 通知消息 */ /** 通知消息 */
onNotification?: (message: string) => void; onNotification?: (message: string) => void;
/** 上下文使用量更新 */
onContextUsage?: (data: { currentTokens: number; maxTokens: number; percentage: number }) => void;
} }
/** /**
@ -553,6 +555,12 @@ export class DialogSession {
onComplete: (data) => { onComplete: (data) => {
this.isActive = false; this.isActive = false;
this.finalizeTextSegment(); this.finalizeTextSegment();
// 追踪 AI 消息(用于后端重启后恢复)
if (this.accumulatedText) {
historyManager.trackAiMessage(this.accumulatedText);
}
// 发送所有段落 // 发送所有段落
callbacks.onComplete?.(this.segments); callbacks.onComplete?.(this.segments);
}, },
@ -639,6 +647,11 @@ export class DialogSession {
await historyManager.saveCompactedData(data.compactedData); await historyManager.saveCompactedData(data.compactedData);
}, },
onContextUsage: (data) => {
console.log('[DialogSession] onContextUsage:', data.currentTokens, '/', data.maxTokens);
callbacks.onContextUsage?.(data);
},
onOpen: () => { onOpen: () => {
console.log('[DialogSession] SSE 连接已建立'); console.log('[DialogSession] SSE 连接已建立');
}, },

View File

@ -27,7 +27,8 @@ import type {
AgentStartEvent, AgentStartEvent,
AgentProgressEvent, AgentProgressEvent,
AgentCompleteEvent, AgentCompleteEvent,
AgentErrorEvent AgentErrorEvent,
ContextUsageEvent
} from '../types/api'; } from '../types/api';
import type { MemoryCompactedEvent } from '../types/memory'; import type { MemoryCompactedEvent } from '../types/memory';
@ -71,6 +72,8 @@ export interface SSECallbacks {
onAgentError?: (data: AgentErrorEvent) => void; onAgentError?: (data: AgentErrorEvent) => void;
/** 记忆压缩完成 */ /** 记忆压缩完成 */
onMemoryCompacted?: (data: MemoryCompactedEvent) => void; onMemoryCompacted?: (data: MemoryCompactedEvent) => void;
/** 上下文使用量更新 */
onContextUsage?: (data: ContextUsageEvent) => void;
/** 连接打开 */ /** 连接打开 */
onOpen?: () => void; onOpen?: () => void;
/** 连接关闭 */ /** 连接关闭 */
@ -325,6 +328,9 @@ function dispatchEvent(
case 'memory_compacted': case 'memory_compacted':
callbacks.onMemoryCompacted?.(data as MemoryCompactedEvent); callbacks.onMemoryCompacted?.(data as MemoryCompactedEvent);
break; break;
case 'context_usage':
callbacks.onContextUsage?.(data as ContextUsageEvent);
break;
default: default:
console.log(`[SSE] 未知事件类型: ${eventType}`, data); console.log(`[SSE] 未知事件类型: ${eventType}`, data);
} }

View File

@ -67,13 +67,13 @@ export class UserInteractionManager {
reject reject
}); });
// 设置超时(5分钟 // 设置超时(2小时
setTimeout(() => { setTimeout(() => {
if (this.pendingQuestions.has(askId)) { if (this.pendingQuestions.has(askId)) {
this.pendingQuestions.delete(askId); this.pendingQuestions.delete(askId);
reject(new Error('用户回答超时')); reject(new Error('用户回答超时'));
} }
}, 300000); }, 7200000);
}); });
} }

View File

@ -54,6 +54,7 @@ export type SSEEventType =
| 'agent_complete' // 子智能体完成 | 'agent_complete' // 子智能体完成
| 'agent_error' // 子智能体错误 | 'agent_error' // 子智能体错误
| 'memory_compacted' // 记忆压缩完成 | 'memory_compacted' // 记忆压缩完成
| 'context_usage' // 上下文使用量
| 'complete' // 对话完成 | 'complete' // 对话完成
| 'error' // 错误 | 'error' // 错误
| 'warning' // 警告 | 'warning' // 警告
@ -181,6 +182,13 @@ export interface AgentErrorEvent {
timestamp: number; timestamp: number;
} }
/** context_usage 事件数据 */
export interface ContextUsageEvent {
currentTokens: number;
maxTokens: number;
percentage: number;
}
// ============== 工具调用协议 (MCP 格式) ============== // ============== 工具调用协议 (MCP 格式) ==============
/** /**

View File

@ -27,6 +27,9 @@ let useBackendService = true;
/** 当前对话会话 */ /** 当前对话会话 */
let currentSession: DialogSession | null = null; let currentSession: DialogSession | null = null;
/** 最后一个活跃的 taskId用于压缩等操作 */
let lastTaskId: string | null = null;
/** 待执行的计划Plan 模式确认后自动执行) */ /** 待执行的计划Plan 模式确认后自动执行) */
let pendingPlanExecution: { let pendingPlanExecution: {
panel: vscode.WebviewPanel; panel: vscode.WebviewPanel;
@ -128,6 +131,8 @@ async function handleUserMessageWithBackend(
// 创建或复用会话 // 创建或复用会话
if (!currentSession || !currentSession.active) { if (!currentSession || !currentSession.active) {
currentSession = dialogManager.createSession(extensionPath, reuseTaskId); currentSession = dialogManager.createSession(extensionPath, reuseTaskId);
// 保存 taskId 用于后续操作(如压缩)
lastTaskId = currentSession.getTaskId();
if (reuseTaskId) { if (reuseTaskId) {
console.log('[MessageHandler] 复用 taskId 创建会话:', reuseTaskId); console.log('[MessageHandler] 复用 taskId 创建会话:', reuseTaskId);
} }
@ -253,6 +258,16 @@ async function handleUserMessageWithBackend(
onNotification: (message) => { onNotification: (message) => {
vscode.window.showInformationMessage(message); vscode.window.showInformationMessage(message);
}, },
onContextUsage: (data) => {
// 发送上下文使用量到 WebView
panel.webview.postMessage({
command: 'contextUsage',
currentTokens: data.currentTokens,
maxTokens: data.maxTokens,
percentage: data.percentage
});
},
}, mode); }, mode);
}); });
} }
@ -310,7 +325,15 @@ export async function abortCurrentDialog(): Promise<void> {
* 获取当前会话的 taskId * 获取当前会话的 taskId
*/ */
export function getCurrentTaskId(): string | null { export function getCurrentTaskId(): string | null {
return currentSession?.getTaskId() || null; return currentSession?.getTaskId() || lastTaskId;
}
/**
* 设置最后的 taskId加载历史会话时调用
*/
export function setLastTaskId(taskId: string): void {
lastTaskId = taskId;
console.log('[MessageHandler] 设置 lastTaskId:', taskId);
} }
/** /**

View File

@ -571,6 +571,13 @@ export function getWebviewContent(iconUri?: string): string {
currentSegmentedMessage = null; currentSegmentedMessage = null;
break; break;
case 'contextUsage':
// 更新上下文使用量显示
if (typeof updateContextDisplay === 'function') {
updateContextDisplay(message.currentTokens, message.maxTokens);
}
break;
case 'hideLoading': case 'hideLoading':
// 隐藏加载指示器 // 隐藏加载指示器
hideLoadingIndicator(); hideLoadingIndicator();