Merge remote-tracking branch 'origin/feat/plugin-front-end' into feat/back-to-front
This commit is contained in:
@ -9,7 +9,8 @@
|
|||||||
},
|
},
|
||||||
"icon": "media/icon.png",
|
"icon": "media/icon.png",
|
||||||
"categories": [
|
"categories": [
|
||||||
"Other"
|
"Chat",
|
||||||
|
"Programming Languages"
|
||||||
],
|
],
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"IC",
|
"IC",
|
||||||
@ -75,7 +76,7 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"icCoder.backendUrl": {
|
"icCoder.backendUrl": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "http://localhost:2233",
|
"default": "http://192.168.1.108:2233",
|
||||||
"description": "后端服务地址"
|
"description": "后端服务地址"
|
||||||
},
|
},
|
||||||
"icCoder.timeout": {
|
"icCoder.timeout": {
|
||||||
|
|||||||
@ -16,8 +16,8 @@ export interface IccoderConfig {
|
|||||||
|
|
||||||
/** 默认配置 */
|
/** 默认配置 */
|
||||||
const DEFAULT_CONFIG: IccoderConfig = {
|
const DEFAULT_CONFIG: IccoderConfig = {
|
||||||
backendUrl: "http://localhost:8080",
|
backendUrl: "http://192.168.1.108:2233",
|
||||||
timeout: 3600000, // 1小时
|
timeout: 60000,
|
||||||
userId: "default-user",
|
userId: "default-user",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -70,3 +70,98 @@ export const stopIconSvg = `
|
|||||||
<path d="M349.75 349.75m57.15 0l210.2 0q57.15 0 57.15 57.15l0 210.2q0 57.15-57.15 57.15l-210.2 0q-57.15 0-57.15-57.15l0-210.2q0-57.15 57.15-57.15Z" fill="currentColor"></path>
|
<path d="M349.75 349.75m57.15 0l210.2 0q57.15 0 57.15 57.15l0 210.2q0 57.15-57.15 57.15l-210.2 0q-57.15 0-57.15-57.15l0-210.2q0-57.15 57.15-57.15Z" fill="currentColor"></path>
|
||||||
</svg>
|
</svg>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 探索智能体图标 SVG
|
||||||
|
*/
|
||||||
|
export const agentIconSvg = `
|
||||||
|
<svg t="1767101071638" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7779" width="16" height="16" style="display: inline-block; vertical-align: middle;"><path d="M173.474909 410.414545c-20.293818 0-33.838545-13.498182-33.838545-33.792v-135.377454C139.636364 187.066182 187.019636 139.636364 241.198545 139.636364h135.447273c20.270545 0 33.815273 13.591273 33.815273 33.885091s-13.591273 33.838545-33.885091 33.838545h-135.447273c-20.317091 0-33.815273 13.591273-33.815272 33.885091v135.377454c0.046545 20.293818-13.498182 33.792-33.838546 33.792z m203.101091 473.902546h-135.447273C187.042909 884.317091 139.636364 836.933818 139.636364 782.754909V647.447273c0-20.386909 13.544727-33.838545 33.838545-33.838546s33.885091 13.451636 33.885091 33.838546v135.330909c0 20.293818 13.544727 33.931636 33.838545 33.931636h135.447273c20.270545 0 33.815273 13.451636 33.815273 33.745455-0.046545 20.340364-13.591273 33.885091-33.885091 33.885091z m406.178909 0H647.447273c-20.386909 0-33.931636-13.544727-33.931637-33.931636 0-20.293818 13.544727-33.745455 33.931637-33.745455h135.330909c20.386909 0 33.838545-13.637818 33.838545-33.931636V647.447273c0-20.386909 13.544727-33.838545 33.931637-33.838546 20.293818 0 33.838545 13.451636 33.838545 33.838546v135.330909c-0.046545 54.178909-47.522909 101.562182-101.608727 101.562182z m67.723636-473.902546c-20.386909 0-33.931636-13.498182-33.931636-33.792v-135.377454c0-20.340364-13.451636-33.885091-33.838545-33.885091H647.447273c-20.386909 0-33.931636-13.498182-33.931637-33.838545S627.083636 139.636364 647.424 139.636364h135.330909c54.085818 0 101.562182 47.429818 101.562182 101.608727v135.377454c0 20.293818-13.544727 33.792-33.838546 33.792z m0 135.493819H173.474909c-20.293818 0-33.838545-13.591273-33.838545-33.931637s13.544727-33.885091 33.838545-33.885091h677.003636c20.293818 0 33.838545 13.591273 33.838546 33.885091s-13.544727 33.931636-33.838546 33.931637z" fill="#8a8a8a" p-id="7780"></path></svg>`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* planner 图标 SVG
|
||||||
|
*/
|
||||||
|
export const plannerIconSvg = `<svg t="1767143425474" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10954" width="16" height="16"><path d="M860.544 633.856c-82.368 0-152.128 69.632-158.464 152h-354.88c-31.616 0-63.296-31.68-63.296-63.296V437.376c12.608 0 25.344 6.4 44.288 6.4h380.16c12.672 69.696 76.032 126.656 152.128 126.656 88.704 0 158.336-69.696 158.336-158.4s-69.632-158.4-158.336-158.4c-76.096 0-139.456 57.024-152.128 126.656h-361.216c-31.616 0-63.296-31.68-63.296-63.296v-133.12h164.736c31.68 0 63.296-22.848 63.296-54.528a55.04 55.04 0 0 0-56-56h-380.16c-31.68 0-70.72 17.984-70.72 56s31.68 54.528 63.36 54.528h133.056v538.624c0 69.696 57.088 126.656 126.72 126.656h386.56c25.344 57.088 82.368 101.376 145.728 101.376a156.8 156.8 0 0 0 158.336-158.4 156.608 156.608 0 0 0-158.208-158.272z m0-316.8c50.624 0 94.912 44.288 94.912 94.976s-44.288 94.976-94.912 94.976c-50.752 0-95.104-44.288-95.104-94.976s44.352-94.976 95.104-94.976z m0 570.24c-50.752 0-95.104-44.352-95.104-95.04s44.352-95.04 95.104-95.04c50.624 0 94.912 44.352 94.912 95.04s-44.288 95.04-94.912 95.04z" p-id="10955" fill="#8a8a8a"></path></svg>`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存知识库图标 SVG
|
||||||
|
*/
|
||||||
|
export const saveKnowledgeIconSvg = `
|
||||||
|
<span class="tool-save-knowledge-icon">
|
||||||
|
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M827.733333 315.733333l-123.733333-123.733333c-12.8-12.8-34.133333-21.333333-59.733333-21.333334H256c-46.96 0-85.44 40.96-85.44 85.44v512c0 46.96 40.96 85.44 85.44 85.44h512c46.96 0 85.44-40.96 85.44-85.44V375.466667c0-25.6-8.533333-46.933333-21.333333-59.733334z m-140.8 469.333334H337.066667v-85.333334h349.866666v85.333334z m0-170.666667H337.066667v-85.333333h349.866666v85.333333z m0-170.666667H337.066667v-85.333333h349.866666v85.333333z" fill="#8a8a8a"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件读取图标 SVG
|
||||||
|
*/
|
||||||
|
export const fileReadIconSvg = `
|
||||||
|
<span class="tool-file-read-icon">
|
||||||
|
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326z m1.8 562H232V136h302v216c0 23.2 18.8 42 42 42h216v494z" fill="#8a8a8a"/>
|
||||||
|
<path d="M342 472h340c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H342c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8zM342 616h340c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H342c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8zM342 760h340c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H342c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z" fill="#8a8a8a"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件删除图标 SVG
|
||||||
|
*/
|
||||||
|
export const fileDeleteIconSvg = `
|
||||||
|
<span class="tool-file-delete-icon">
|
||||||
|
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72z" fill="#8a8a8a"/>
|
||||||
|
<path d="M832 256H192c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z" fill="#8a8a8a"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仿真图标 SVG
|
||||||
|
*/
|
||||||
|
export const simulationIconSvg = `
|
||||||
|
<span class="tool-simulation-icon">
|
||||||
|
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32z m-40 632H136V232h752v560z" fill="#8a8a8a"/>
|
||||||
|
<path d="M210 304h100c4.4 0 8 3.6 8 8v152c0 4.4-3.6 8-8 8H210c-4.4 0-8-3.6-8-8V312c0-4.4 3.6-8 8-8zM210 544h100c4.4 0 8 3.6 8 8v152c0 4.4-3.6 8-8 8H210c-4.4 0-8-3.6-8-8V552c0-4.4 3.6-8 8-8zM462 304h100c4.4 0 8 3.6 8 8v152c0 4.4-3.6 8-8 8H462c-4.4 0-8-3.6-8-8V312c0-4.4 3.6-8 8-8zM462 544h100c4.4 0 8 3.6 8 8v152c0 4.4-3.6 8-8 8H462c-4.4 0-8-3.6-8-8V552c0-4.4 3.6-8 8-8zM714 304h100c4.4 0 8 3.6 8 8v152c0 4.4-3.6 8-8 8H714c-4.4 0-8-3.6-8-8V312c0-4.4 3.6-8 8-8zM714 544h100c4.4 0 8 3.6 8 8v152c0 4.4-3.6 8-8 8H714c-4.4 0-8-3.6-8-8V552c0-4.4 3.6-8 8-8z" fill="#8a8a8a"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 波形分析图标 SVG
|
||||||
|
*/
|
||||||
|
export const waveformIconSvg = `
|
||||||
|
<span class="tool-waveform-icon">
|
||||||
|
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M952 474H829.8C812.5 327.6 696.4 211.5 550 194.2V72c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v122.2C327.6 211.5 211.5 327.6 194.2 474H72c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h122.2C211.5 696.4 327.6 812.5 474 829.8V952c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V829.8C696.4 812.5 812.5 696.4 829.8 550H952c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8zM512 756c-134.8 0-244-109.2-244-244s109.2-244 244-244 244 109.2 244 244-109.2 244-244 244z" fill="#8a8a8a"/>
|
||||||
|
<path d="M512 392c-32.1 0-62.1 12.4-84.8 35.2-22.7 22.7-35.2 52.7-35.2 84.8s12.5 62.1 35.2 84.8c22.7 22.7 52.7 35.2 84.8 35.2s62.1-12.5 84.8-35.2c22.7-22.7 35.2-52.7 35.2-84.8s-12.5-62.1-35.2-84.8C574.1 404.4 544.1 392 512 392z" fill="#8a8a8a"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 知识库加载图标 SVG
|
||||||
|
*/
|
||||||
|
export const knowledgeLoadIconSvg = `
|
||||||
|
<span class="tool-knowledge-load-icon">
|
||||||
|
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32z m-40 728H184V184h656v656z" fill="#8a8a8a"/>
|
||||||
|
<path d="M492 400h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H492c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8zM492 544h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H492c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8zM492 688h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H492c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8zM340 368a40 40 0 1 0 80 0 40 40 0 1 0-80 0zM340 512a40 40 0 1 0 80 0 40 40 0 1 0-80 0zM340 656a40 40 0 1 0 80 0 40 40 0 1 0-80 0z" fill="#8a8a8a"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态转换图标 SVG
|
||||||
|
*/
|
||||||
|
export const stateTransitionIconSvg = `
|
||||||
|
<span class="tool-state-transition-icon">
|
||||||
|
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64z m0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z" fill="#8a8a8a"/>
|
||||||
|
<path d="M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v275.4c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.8 11.2-1.7l28.6-39c2.6-3.7 1.8-8.7-1.9-11.2z" fill="#8a8a8a"/>
|
||||||
|
<path d="M512 320c-4.4 0-8 3.6-8 8v184c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H560V328c0-4.4-3.6-8-8-8h-40z" fill="#8a8a8a"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
|||||||
@ -26,9 +26,13 @@ export async function showICHelperPanel(
|
|||||||
) {
|
) {
|
||||||
// 检查用户是否已登录
|
// 检查用户是否已登录
|
||||||
try {
|
try {
|
||||||
const session = await vscode.authentication.getSession("iccoder", [], { createIfNone: false });
|
const session = await vscode.authentication.getSession("iccoder", [], {
|
||||||
|
createIfNone: false,
|
||||||
|
});
|
||||||
if (!session) {
|
if (!session) {
|
||||||
vscode.window.showWarningMessage("请先登录后再使用 IC Coder", "立即登录").then((selection) => {
|
vscode.window
|
||||||
|
.showWarningMessage("请先登录后再使用 IC Coder", "立即登录")
|
||||||
|
.then((selection) => {
|
||||||
if (selection === "立即登录") {
|
if (selection === "立即登录") {
|
||||||
vscode.commands.executeCommand("ic-coder.login");
|
vscode.commands.executeCommand("ic-coder.login");
|
||||||
}
|
}
|
||||||
@ -36,7 +40,9 @@ export async function showICHelperPanel(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
vscode.window.showWarningMessage("请先登录后再使用 IC Coder", "立即登录").then((selection) => {
|
vscode.window
|
||||||
|
.showWarningMessage("请先登录后再使用 IC Coder", "立即登录")
|
||||||
|
.then((selection) => {
|
||||||
if (selection === "立即登录") {
|
if (selection === "立即登录") {
|
||||||
vscode.commands.executeCommand("ic-coder.login");
|
vscode.commands.executeCommand("ic-coder.login");
|
||||||
}
|
}
|
||||||
@ -110,7 +116,12 @@ export async function showICHelperPanel(
|
|||||||
// 切换到当前面板的任务上下文
|
// 切换到当前面板的任务上下文
|
||||||
historyManager.switchToPanelTask(panelId);
|
historyManager.switchToPanelTask(panelId);
|
||||||
|
|
||||||
handleUserMessage(panel, message.text, context.extensionPath, message.mode);
|
handleUserMessage(
|
||||||
|
panel,
|
||||||
|
message.text,
|
||||||
|
context.extensionPath,
|
||||||
|
message.mode
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case "readFile":
|
case "readFile":
|
||||||
handleReadFile(panel, message.filePath);
|
handleReadFile(panel, message.filePath);
|
||||||
@ -186,22 +197,54 @@ export async function showICHelperPanel(
|
|||||||
break;
|
break;
|
||||||
// 处理计划操作(只做模式切换,响应已通过 submitAnswer 发送)
|
// 处理计划操作(只做模式切换,响应已通过 submitAnswer 发送)
|
||||||
case "planAction":
|
case "planAction":
|
||||||
if (message.action === 'confirm') {
|
if (message.action === "confirm") {
|
||||||
// 确认执行:切换到 Agent 模式
|
// 确认执行:切换到 Agent 模式
|
||||||
panel.webview.postMessage({
|
panel.webview.postMessage({
|
||||||
command: 'switchMode',
|
command: "switchMode",
|
||||||
mode: 'agent'
|
mode: "agent",
|
||||||
});
|
});
|
||||||
// 获取当前会话的 taskId,用于复用知识图谱数据
|
// 获取当前会话的 taskId,用于复用知识图谱数据
|
||||||
const taskId = getCurrentTaskId();
|
const taskId = getCurrentTaskId();
|
||||||
if (taskId) {
|
if (taskId) {
|
||||||
// 设置待执行的计划,对话结束后自动执行(复用 taskId)
|
// 设置待执行的计划,对话结束后自动执行(复用 taskId)
|
||||||
setPendingPlanExecution(panel, message.planTitle || '计划', context.extensionPath, taskId);
|
setPendingPlanExecution(
|
||||||
|
panel,
|
||||||
|
message.planTitle || "计划",
|
||||||
|
context.extensionPath,
|
||||||
|
taskId
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
console.warn('[ICHelperPanel] 无法获取当前 taskId,知识图谱数据可能丢失');
|
console.warn(
|
||||||
|
"[ICHelperPanel] 无法获取当前 taskId,知识图谱数据可能丢失"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
// 新增:检查工作区状态
|
||||||
|
case "checkWorkspace":
|
||||||
|
const hasWorkspace = !!(
|
||||||
|
vscode.workspace.workspaceFolders &&
|
||||||
|
vscode.workspace.workspaceFolders.length > 0
|
||||||
|
);
|
||||||
|
if (!hasWorkspace) {
|
||||||
|
// 弹窗提示用户需要打开工作区
|
||||||
|
vscode.window
|
||||||
|
.showWarningMessage(
|
||||||
|
"请先打开一个文件夹作为工作区,这样我就能更好地为您服务了 😊",
|
||||||
|
"打开文件夹"
|
||||||
|
)
|
||||||
|
.then((selection) => {
|
||||||
|
if (selection === "打开文件夹") {
|
||||||
|
vscode.commands.executeCommand("vscode.openFolder");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 返回工作区状态给前端
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "workspaceStatus",
|
||||||
|
hasWorkspace: hasWorkspace,
|
||||||
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
|
|||||||
@ -16,7 +16,9 @@ export async function createFile(
|
|||||||
if (workspaceFolders && workspaceFolders.length > 0) {
|
if (workspaceFolders && workspaceFolders.length > 0) {
|
||||||
absolutePath = path.join(workspaceFolders[0].uri.fsPath, filePath);
|
absolutePath = path.join(workspaceFolders[0].uri.fsPath, filePath);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("没有打开的工作区,无法创建相对路径的文件");
|
throw new Error(
|
||||||
|
"请先打开一个文件夹作为工作区,这样我就能为您创建文件了"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +30,7 @@ export async function createFile(
|
|||||||
throw new Error(`文件已存在: ${absolutePath}`);
|
throw new Error(`文件已存在: ${absolutePath}`);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 如果文件不存在,继续创建
|
// 如果文件不存在,继续创建
|
||||||
if (error.code !== 'FileNotFound') {
|
if (error.code !== "FileNotFound") {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,7 +67,9 @@ export async function createOrOverwriteFile(
|
|||||||
if (workspaceFolders && workspaceFolders.length > 0) {
|
if (workspaceFolders && workspaceFolders.length > 0) {
|
||||||
absolutePath = path.join(workspaceFolders[0].uri.fsPath, filePath);
|
absolutePath = path.join(workspaceFolders[0].uri.fsPath, filePath);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("没有打开的工作区,无法创建相对路径的文件");
|
throw new Error(
|
||||||
|
"请先打开一个文件夹作为工作区,这样我就能为您创建文件了"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +103,9 @@ export async function createDirectory(dirPath: string): Promise<void> {
|
|||||||
if (workspaceFolders && workspaceFolders.length > 0) {
|
if (workspaceFolders && workspaceFolders.length > 0) {
|
||||||
absolutePath = path.join(workspaceFolders[0].uri.fsPath, dirPath);
|
absolutePath = path.join(workspaceFolders[0].uri.fsPath, dirPath);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("没有打开的工作区,无法创建相对路径的目录");
|
throw new Error(
|
||||||
|
"请先打开一个文件夹作为工作区,这样我就能为您创建目录了"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +121,7 @@ export async function createDirectory(dirPath: string): Promise<void> {
|
|||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 如果目录不存在,继续创建
|
// 如果目录不存在,继续创建
|
||||||
if (error.code !== 'FileNotFound') {
|
if (error.code !== "FileNotFound") {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,7 +167,9 @@ export async function deleteFile(filePath: string): Promise<void> {
|
|||||||
if (workspaceFolders && workspaceFolders.length > 0) {
|
if (workspaceFolders && workspaceFolders.length > 0) {
|
||||||
absolutePath = path.join(workspaceFolders[0].uri.fsPath, filePath);
|
absolutePath = path.join(workspaceFolders[0].uri.fsPath, filePath);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("没有打开的工作区,无法删除相对路径的文件");
|
throw new Error(
|
||||||
|
"请先打开一个文件夹作为工作区,这样我就能为您删除文件了"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,7 +205,9 @@ export async function updateFile(
|
|||||||
if (workspaceFolders && workspaceFolders.length > 0) {
|
if (workspaceFolders && workspaceFolders.length > 0) {
|
||||||
absolutePath = path.join(workspaceFolders[0].uri.fsPath, filePath);
|
absolutePath = path.join(workspaceFolders[0].uri.fsPath, filePath);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("没有打开的工作区,无法修改相对路径的文件");
|
throw new Error(
|
||||||
|
"请先打开一个文件夹作为工作区,这样我就能为您修改文件了"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,7 +246,9 @@ export async function appendToFile(
|
|||||||
if (workspaceFolders && workspaceFolders.length > 0) {
|
if (workspaceFolders && workspaceFolders.length > 0) {
|
||||||
absolutePath = path.join(workspaceFolders[0].uri.fsPath, filePath);
|
absolutePath = path.join(workspaceFolders[0].uri.fsPath, filePath);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("没有打开的工作区,无法追加相对路径的文件");
|
throw new Error(
|
||||||
|
"请先打开一个文件夹作为工作区,这样我就能为您追加文件内容了"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,7 +286,9 @@ export async function replaceFile(
|
|||||||
if (workspaceFolders && workspaceFolders.length > 0) {
|
if (workspaceFolders && workspaceFolders.length > 0) {
|
||||||
absolutePath = path.join(workspaceFolders[0].uri.fsPath, filePath);
|
absolutePath = path.join(workspaceFolders[0].uri.fsPath, filePath);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("没有打开的工作区,无法修改相对路径的文件");
|
throw new Error(
|
||||||
|
"请先打开一个文件夹作为工作区,这样我就能为您修改文件了"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,14 +305,17 @@ export async function replaceFile(
|
|||||||
|
|
||||||
// 转义特殊字符,将字符串作为字面量处理
|
// 转义特殊字符,将字符串作为字面量处理
|
||||||
const escapeRegExp = (str: string) => {
|
const escapeRegExp = (str: string) => {
|
||||||
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
};
|
};
|
||||||
|
|
||||||
// 替换内容 - 如果是字符串,先转义特殊字符
|
// 替换内容 - 如果是字符串,先转义特殊字符
|
||||||
let newContent: string;
|
let newContent: string;
|
||||||
if (typeof searchValue === 'string') {
|
if (typeof searchValue === "string") {
|
||||||
const escapedSearch = escapeRegExp(searchValue);
|
const escapedSearch = escapeRegExp(searchValue);
|
||||||
newContent = fileContent.replace(new RegExp(escapedSearch, "g"), replaceValue);
|
newContent = fileContent.replace(
|
||||||
|
new RegExp(escapedSearch, "g"),
|
||||||
|
replaceValue
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
newContent = fileContent.replace(searchValue, replaceValue);
|
newContent = fileContent.replace(searchValue, replaceValue);
|
||||||
}
|
}
|
||||||
@ -330,7 +347,9 @@ export async function insertAtLine(
|
|||||||
if (workspaceFolders && workspaceFolders.length > 0) {
|
if (workspaceFolders && workspaceFolders.length > 0) {
|
||||||
absolutePath = path.join(workspaceFolders[0].uri.fsPath, filePath);
|
absolutePath = path.join(workspaceFolders[0].uri.fsPath, filePath);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("没有打开的工作区,无法修改相对路径的文件");
|
throw new Error(
|
||||||
|
"请先打开一个文件夹作为工作区,这样我就能为您修改文件了"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,7 +401,9 @@ export async function renameFile(
|
|||||||
absoluteNewPath = path.join(workspaceRoot, newPath);
|
absoluteNewPath = path.join(workspaceRoot, newPath);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error("没有打开的工作区,无法重命名相对路径的文件");
|
throw new Error(
|
||||||
|
"请先打开一个文件夹作为工作区,这样我就能为您重命名文件了"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldUri = vscode.Uri.file(absoluteOldPath);
|
const oldUri = vscode.Uri.file(absoluteOldPath);
|
||||||
@ -401,10 +422,13 @@ export async function renameFile(
|
|||||||
throw new Error(`目标文件已存在: ${absoluteNewPath}`);
|
throw new Error(`目标文件已存在: ${absoluteNewPath}`);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 如果文件不存在,继续重命名
|
// 如果文件不存在,继续重命名
|
||||||
if (error.code !== 'FileNotFound' && !error.message.includes('目标文件已存在')) {
|
if (
|
||||||
|
error.code !== "FileNotFound" &&
|
||||||
|
!error.message.includes("目标文件已存在")
|
||||||
|
) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
if (error.message.includes('目标文件已存在')) {
|
if (error.message.includes("目标文件已存在")) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,310 +2,176 @@
|
|||||||
* 智能体卡片组件
|
* 智能体卡片组件
|
||||||
*
|
*
|
||||||
* 功能说明:
|
* 功能说明:
|
||||||
* - 显示子智能体的执行过程
|
* - 提供智能体执行状态的可视化展示
|
||||||
* - 支持展开/收起步骤详情
|
* - 显示智能体名称、状态和执行步骤
|
||||||
* - 显示执行状态和统计信息
|
* - 支持实时更新步骤信息
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {
|
import { agentIconSvg } from "../constants/toolIcons";
|
||||||
AgentStartEvent,
|
|
||||||
AgentProgressEvent,
|
|
||||||
AgentCompleteEvent,
|
|
||||||
AgentErrorEvent
|
|
||||||
} from '../types/api';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取智能体卡片样式
|
* 获取智能体卡片的样式
|
||||||
*/
|
*/
|
||||||
export function getAgentCardStyles(): string {
|
export function getAgentCardStyles(): string {
|
||||||
return `
|
return `
|
||||||
|
/* 智能体卡片样式 */
|
||||||
|
.segment-agent {
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
.agent-card {
|
.agent-card {
|
||||||
background: var(--vscode-editor-background);
|
|
||||||
border: 1px solid var(--vscode-input-border);
|
border: 1px solid var(--vscode-input-border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin: 10px 0;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background: var(--vscode-editor-background);
|
||||||
}
|
}
|
||||||
.agent-card-header {
|
.agent-header {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 10px 12px;
|
|
||||||
background: var(--vscode-sideBar-background);
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
.agent-card-header:hover {
|
|
||||||
background: var(--vscode-list-hoverBackground);
|
|
||||||
}
|
|
||||||
.agent-card-title {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
font-weight: 500;
|
padding: 8px 12px;
|
||||||
|
background: var(--vscode-sideBar-background);
|
||||||
|
border-bottom: 1px solid var(--vscode-input-border);
|
||||||
}
|
}
|
||||||
.agent-card-icon {
|
.agent-icon {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
.agent-card-status {
|
.agent-name {
|
||||||
font-size: 12px;
|
font-weight: 500;
|
||||||
padding: 2px 6px;
|
flex: 1;
|
||||||
border-radius: 4px;
|
|
||||||
}
|
}
|
||||||
.agent-card-status.running {
|
.agent-status {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.agent-status.running {
|
||||||
background: var(--vscode-inputValidation-infoBackground);
|
background: var(--vscode-inputValidation-infoBackground);
|
||||||
color: var(--vscode-inputValidation-infoForeground);
|
color: var(--vscode-inputValidation-infoForeground);
|
||||||
}
|
}
|
||||||
.agent-card-status.completed {
|
.agent-status.completed {
|
||||||
background: var(--vscode-testing-iconPassed);
|
background: #28a745;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
.agent-card-status.error {
|
.agent-status.error {
|
||||||
background: var(--vscode-testing-iconFailed);
|
background: #dc3545;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
.agent-card-toggle {
|
.agent-body {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
.agent-steps-container {
|
||||||
|
max-height: 150px;
|
||||||
|
overflow-y: auto;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--vscode-descriptionForeground);
|
|
||||||
}
|
|
||||||
.agent-card-body {
|
|
||||||
padding: 12px;
|
|
||||||
border-top: 1px solid var(--vscode-input-border);
|
|
||||||
}
|
|
||||||
.agent-card-body.collapsed {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.agent-card-instruction {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--vscode-descriptionForeground);
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
border-bottom: 1px dashed var(--vscode-input-border);
|
|
||||||
}
|
|
||||||
.agent-steps {
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
}
|
||||||
.agent-step {
|
.agent-step {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 6px;
|
||||||
padding: 6px 0;
|
padding: 4px 8px;
|
||||||
border-left: 2px solid var(--vscode-input-border);
|
border-radius: 4px;
|
||||||
padding-left: 12px;
|
margin-bottom: 4px;
|
||||||
margin-left: 6px;
|
background: var(--vscode-list-hoverBackground);
|
||||||
}
|
}
|
||||||
.agent-step:last-child {
|
.agent-step:last-child {
|
||||||
border-left-color: transparent;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
.agent-step-icon {
|
.step-icon {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.agent-step-content {
|
.step-name {
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
.agent-step-name {
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
}
|
}
|
||||||
.agent-step-result {
|
.step-result {
|
||||||
font-size: 12px;
|
|
||||||
color: var(--vscode-descriptionForeground);
|
color: var(--vscode-descriptionForeground);
|
||||||
margin-top: 2px;
|
font-size: 11px;
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.agent-card-summary {
|
.agent-step-placeholder {
|
||||||
font-size: 13px;
|
color: var(--vscode-descriptionForeground);
|
||||||
padding: 8px 12px;
|
font-style: italic;
|
||||||
background: var(--vscode-sideBar-background);
|
padding: 8px;
|
||||||
border-top: 1px solid var(--vscode-input-border);
|
text-align: center;
|
||||||
}
|
|
||||||
.agent-card-error {
|
|
||||||
color: var(--vscode-errorForeground);
|
|
||||||
padding: 8px 12px;
|
|
||||||
background: var(--vscode-inputValidation-errorBackground);
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 渲染智能体卡片(启动状态)
|
* 获取智能体卡片的脚本
|
||||||
*/
|
|
||||||
export function renderAgentCardStart(event: AgentStartEvent): string {
|
|
||||||
return `
|
|
||||||
<div class="agent-card" id="agent-${event.agentId}">
|
|
||||||
<div class="agent-card-header" onclick="toggleAgentCard('${event.agentId}')">
|
|
||||||
<div class="agent-card-title">
|
|
||||||
<span class="agent-card-icon">🤖</span>
|
|
||||||
<span>${event.agentName}</span>
|
|
||||||
<span class="agent-card-status running">执行中</span>
|
|
||||||
</div>
|
|
||||||
<span class="agent-card-toggle">▼</span>
|
|
||||||
</div>
|
|
||||||
<div class="agent-card-body" id="agent-body-${event.agentId}">
|
|
||||||
<div class="agent-card-instruction">指令:${escapeHtml(event.instruction)}</div>
|
|
||||||
<div class="agent-steps" id="agent-steps-${event.agentId}">
|
|
||||||
<!-- 步骤将动态添加 -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 渲染步骤项(进行中)
|
|
||||||
*/
|
|
||||||
export function renderAgentStepRunning(event: AgentProgressEvent): string {
|
|
||||||
const inputStr = event.toolInput ? JSON.stringify(event.toolInput) : '';
|
|
||||||
const shortInput = inputStr.length > 50 ? inputStr.substring(0, 50) + '...' : inputStr;
|
|
||||||
|
|
||||||
return `
|
|
||||||
<div class="agent-step" id="agent-step-${event.agentId}-${event.step}">
|
|
||||||
<span class="agent-step-icon">🔄</span>
|
|
||||||
<div class="agent-step-content">
|
|
||||||
<div class="agent-step-name">${event.toolName}</div>
|
|
||||||
<div class="agent-step-result">${escapeHtml(shortInput)}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新步骤项(完成)
|
|
||||||
*/
|
|
||||||
export function getStepCompleteUpdate(event: AgentProgressEvent): { icon: string; result: string } {
|
|
||||||
const result = event.toolResult || '';
|
|
||||||
const shortResult = result.length > 80 ? result.substring(0, 80) + '...' : result;
|
|
||||||
return {
|
|
||||||
icon: event.status === 'completed' ? '✅' : '❌',
|
|
||||||
result: shortResult
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取智能体卡片脚本
|
|
||||||
*/
|
*/
|
||||||
export function getAgentCardScript(): string {
|
export function getAgentCardScript(): string {
|
||||||
return `
|
return `
|
||||||
// 智能体状态存储
|
// 工具名称中文映射
|
||||||
const agentStates = {};
|
function getAgentToolDisplayName(toolName) {
|
||||||
|
const toolNameMap = {
|
||||||
// 切换智能体卡片展开/收起
|
'file_read': '文件读取',
|
||||||
function toggleAgentCard(agentId) {
|
'file_write': '文件写入',
|
||||||
const body = document.getElementById('agent-body-' + agentId);
|
'file_delete': '文件删除',
|
||||||
const header = body?.previousElementSibling;
|
'file_list': '检索文件',
|
||||||
const toggle = header?.querySelector('.agent-card-toggle');
|
'syntax_check': '语法检查',
|
||||||
|
'simulation': '仿真',
|
||||||
if (body && toggle) {
|
'waveform_summary': '波形分析',
|
||||||
body.classList.toggle('collapsed');
|
'knowledge_save': '保存知识库',
|
||||||
toggle.textContent = body.classList.contains('collapsed') ? '▶' : '▼';
|
'knowledge_load': '加载知识库',
|
||||||
}
|
'queryKnowledgeSummary': '查询知识摘要',
|
||||||
}
|
'queryRules': '查询规则',
|
||||||
|
'setModule': '设置模块',
|
||||||
// 处理智能体启动事件
|
'addSignal': '添加信号',
|
||||||
function handleAgentStart(event) {
|
'addSignalExample': '添加信号示例',
|
||||||
agentStates[event.agentId] = {
|
'validateKnowledgeGraph': '验证知识图谱',
|
||||||
status: 'running',
|
'querySignals': '查询信号',
|
||||||
steps: []
|
'addPlan': '添加计划',
|
||||||
|
'addEdge': '添加边',
|
||||||
|
'showPlan': '显示计划',
|
||||||
|
'spawnExplorer': '代码探索'
|
||||||
};
|
};
|
||||||
|
return toolNameMap[toolName] || toolName;
|
||||||
// 在当前消息中添加智能体卡片
|
|
||||||
const currentMessage = document.querySelector('.bot-message:last-child .message-content');
|
|
||||||
if (currentMessage) {
|
|
||||||
currentMessage.insertAdjacentHTML('beforeend', renderAgentCardStart(event));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理智能体进度事件
|
|
||||||
function handleAgentProgress(event) {
|
|
||||||
const stepsContainer = document.getElementById('agent-steps-' + event.agentId);
|
|
||||||
if (!stepsContainer) return;
|
|
||||||
|
|
||||||
if (event.status === 'running') {
|
|
||||||
// 添加新步骤
|
|
||||||
stepsContainer.insertAdjacentHTML('beforeend', renderAgentStepRunning(event));
|
|
||||||
} else if (event.status === 'completed') {
|
|
||||||
// 更新步骤状态
|
|
||||||
const stepEl = document.getElementById('agent-step-' + event.agentId + '-' + event.step);
|
|
||||||
if (stepEl) {
|
|
||||||
const iconEl = stepEl.querySelector('.agent-step-icon');
|
|
||||||
const resultEl = stepEl.querySelector('.agent-step-result');
|
|
||||||
if (iconEl) iconEl.textContent = '✅';
|
|
||||||
if (resultEl) {
|
|
||||||
const result = event.toolResult || '';
|
|
||||||
resultEl.textContent = result.length > 80 ? result.substring(0, 80) + '...' : result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理智能体完成事件
|
|
||||||
function handleAgentComplete(event) {
|
|
||||||
const card = document.getElementById('agent-' + event.agentId);
|
|
||||||
if (!card) return;
|
|
||||||
|
|
||||||
// 更新状态
|
|
||||||
const statusEl = card.querySelector('.agent-card-status');
|
|
||||||
if (statusEl) {
|
|
||||||
statusEl.className = 'agent-card-status completed';
|
|
||||||
statusEl.textContent = '完成';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加摘要
|
|
||||||
const body = card.querySelector('.agent-card-body');
|
|
||||||
if (body) {
|
|
||||||
body.insertAdjacentHTML('beforeend',
|
|
||||||
'<div class="agent-card-summary">' + escapeHtml(event.summary) + '</div>'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自动收起
|
|
||||||
body?.classList.add('collapsed');
|
|
||||||
const toggle = card.querySelector('.agent-card-toggle');
|
|
||||||
if (toggle) toggle.textContent = '▶';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理智能体错误事件
|
|
||||||
function handleAgentError(event) {
|
|
||||||
const card = document.getElementById('agent-' + event.agentId);
|
|
||||||
if (!card) return;
|
|
||||||
|
|
||||||
// 更新状态
|
|
||||||
const statusEl = card.querySelector('.agent-card-status');
|
|
||||||
if (statusEl) {
|
|
||||||
statusEl.className = 'agent-card-status error';
|
|
||||||
statusEl.textContent = '错误';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加错误信息
|
|
||||||
const body = card.querySelector('.agent-card-body');
|
|
||||||
if (body) {
|
|
||||||
body.insertAdjacentHTML('beforeend',
|
|
||||||
'<div class="agent-card-error">错误:' + escapeHtml(event.error) + '</div>'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTML 转义
|
|
||||||
function escapeHtml(text) {
|
|
||||||
if (!text) return '';
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.textContent = text;
|
|
||||||
return div.innerHTML;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTML 转义(服务端使用)
|
* 渲染智能体卡片
|
||||||
|
* @param {Object} segment - 智能体段落数据
|
||||||
|
* @param {HTMLElement} segmentDiv - 段落容器元素
|
||||||
*/
|
*/
|
||||||
function escapeHtml(text: string): string {
|
function renderAgentCard(segment, segmentDiv) {
|
||||||
if (!text) return '';
|
segmentDiv.className += ' segment-agent';
|
||||||
return text
|
|
||||||
.replace(/&/g, '&')
|
const statusText = segment.agentStatus === 'completed' ? '完成'
|
||||||
.replace(/</g, '<')
|
: segment.agentStatus === 'error' ? '错误' : '执行中';
|
||||||
.replace(/>/g, '>')
|
const statusClass = segment.agentStatus || 'running';
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''');
|
const stepsHtml = (segment.agentSteps || []).map(step => {
|
||||||
|
const icon = step.status === 'completed' ? '✅' : step.status === 'error' ? '❌' : '🔄';
|
||||||
|
const displayName = getAgentToolDisplayName(step.toolName);
|
||||||
|
const result = step.toolResult ? \`: \${step.toolResult.substring(0, 50)}\${step.toolResult.length > 50 ? '...' : ''}\` : '';
|
||||||
|
return \`<div class="agent-step"><span class="step-icon">\${icon}</span><span class="step-name">\${displayName}</span><span class="step-result">\${result}</span></div>\`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
segmentDiv.innerHTML = \`
|
||||||
|
<div class="agent-card">
|
||||||
|
<div class="agent-header">
|
||||||
|
<span class="agent-icon">${agentIconSvg}</span>
|
||||||
|
<span class="agent-name">\${segment.agentName || '智能体'}</span>
|
||||||
|
<span class="agent-status \${statusClass}">\${statusText}</span>
|
||||||
|
</div>
|
||||||
|
<div class="agent-body">
|
||||||
|
<div class="agent-steps-container">
|
||||||
|
\${stepsHtml || '<div class="agent-step-placeholder">等待执行...</div>'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
\`;
|
||||||
|
|
||||||
|
// 自动滚动到最新步骤
|
||||||
|
setTimeout(() => {
|
||||||
|
const container = segmentDiv.querySelector('.agent-steps-container');
|
||||||
|
if (container) {
|
||||||
|
container.scrollTop = container.scrollHeight;
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,37 +2,29 @@ import { getWaveformPreviewContent } from "./waveformPreviewContent";
|
|||||||
import {
|
import {
|
||||||
getModelSelectorContent,
|
getModelSelectorContent,
|
||||||
getModelSelectorStyles,
|
getModelSelectorStyles,
|
||||||
getModelSelectorScript
|
getModelSelectorScript,
|
||||||
} from "./modelSelector";
|
} from "./modelSelector";
|
||||||
import {
|
import {
|
||||||
getModeSelectorContent,
|
getModeSelectorContent,
|
||||||
getModeSelectorStyles,
|
getModeSelectorStyles,
|
||||||
getModeSelectorScript
|
getModeSelectorScript,
|
||||||
} from "./agentModeSelector";
|
} from "./agentModeSelector";
|
||||||
import {
|
import {
|
||||||
getContextButtonContent,
|
getContextButtonContent,
|
||||||
getContextButtonStyles,
|
getContextButtonStyles,
|
||||||
getContextButtonScript
|
getContextButtonScript,
|
||||||
} from "./contextButton";
|
} from "./contextButton";
|
||||||
import {
|
import {
|
||||||
getContextCompressContent,
|
getContextCompressContent,
|
||||||
getContextCompressStyles,
|
getContextCompressStyles,
|
||||||
getContextCompressScript
|
getContextCompressScript,
|
||||||
} from "./contextCompress";
|
} from "./contextCompress";
|
||||||
import {
|
|
||||||
getPlanToggleContent,
|
|
||||||
getPlanToggleStyles,
|
|
||||||
getPlanToggleScript
|
|
||||||
} from "./planToggle";
|
|
||||||
import {
|
import {
|
||||||
getOptimizeButtonContent,
|
getOptimizeButtonContent,
|
||||||
getOptimizeButtonStyles,
|
getOptimizeButtonStyles,
|
||||||
getOptimizeButtonScript
|
getOptimizeButtonScript,
|
||||||
} from "./optimizeButton";
|
} from "./optimizeButton";
|
||||||
import {
|
import { sendIconSvg, stopIconSvg } from "../constants/toolIcons";
|
||||||
sendIconSvg,
|
|
||||||
stopIconSvg
|
|
||||||
} from "../constants/toolIcons";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取输入区域的 HTML 内容
|
* 获取输入区域的 HTML 内容
|
||||||
@ -45,7 +37,6 @@ export function getInputAreaContent(): string {
|
|||||||
<!-- 顶部工具栏 -->
|
<!-- 顶部工具栏 -->
|
||||||
<div class="input-top-toolbar">
|
<div class="input-top-toolbar">
|
||||||
${getContextButtonContent()}
|
${getContextButtonContent()}
|
||||||
${getPlanToggleContent()}
|
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
id="messageInput"
|
id="messageInput"
|
||||||
@ -81,7 +72,6 @@ export function getInputAreaStyles(): string {
|
|||||||
${getModelSelectorStyles()}
|
${getModelSelectorStyles()}
|
||||||
${getContextButtonStyles()}
|
${getContextButtonStyles()}
|
||||||
${getContextCompressStyles()}
|
${getContextCompressStyles()}
|
||||||
${getPlanToggleStyles()}
|
|
||||||
${getOptimizeButtonStyles()}
|
${getOptimizeButtonStyles()}
|
||||||
.input-area {
|
.input-area {
|
||||||
border-top: 1px solid var(--vscode-panel-border);
|
border-top: 1px solid var(--vscode-panel-border);
|
||||||
@ -265,12 +255,15 @@ export function getInputAreaScript(): string {
|
|||||||
${getModelSelectorScript()}
|
${getModelSelectorScript()}
|
||||||
${getContextButtonScript()}
|
${getContextButtonScript()}
|
||||||
${getContextCompressScript()}
|
${getContextCompressScript()}
|
||||||
${getPlanToggleScript()}
|
|
||||||
${getOptimizeButtonScript()}
|
${getOptimizeButtonScript()}
|
||||||
|
|
||||||
// 对话状态管理
|
// 对话状态管理
|
||||||
let isConversationActive = false;
|
let isConversationActive = false;
|
||||||
|
|
||||||
|
// 工作区检测状态
|
||||||
|
let hasCheckedWorkspace = false; // 是否已经检测过工作区
|
||||||
|
let hasWorkspace = true; // 工作区状态
|
||||||
|
|
||||||
// 自动调整 textarea 高度
|
// 自动调整 textarea 高度
|
||||||
function autoResizeTextarea() {
|
function autoResizeTextarea() {
|
||||||
if (messageInput) {
|
if (messageInput) {
|
||||||
@ -283,11 +276,16 @@ export function getInputAreaScript(): string {
|
|||||||
if (messageInput) {
|
if (messageInput) {
|
||||||
messageInput.addEventListener('input', autoResizeTextarea);
|
messageInput.addEventListener('input', autoResizeTextarea);
|
||||||
|
|
||||||
|
// 监听点击事件,检测工作区状态
|
||||||
|
messageInput.addEventListener('focus', () => {
|
||||||
|
if (!hasCheckedWorkspace) {
|
||||||
|
hasCheckedWorkspace = true;
|
||||||
|
vscode.postMessage({ command: 'checkWorkspace' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 初始化时调整一次高度
|
// 初始化时调整一次高度
|
||||||
autoResizeTextarea();
|
autoResizeTextarea();
|
||||||
|
|
||||||
// 聚焦到输入框
|
|
||||||
messageInput.focus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换发送按钮状态
|
// 切换发送按钮状态
|
||||||
@ -326,6 +324,14 @@ export function getInputAreaScript(): string {
|
|||||||
const text = messageInput.value.trim();
|
const text = messageInput.value.trim();
|
||||||
if (!text) return;
|
if (!text) return;
|
||||||
|
|
||||||
|
// 检查工作区状态
|
||||||
|
if (!hasWorkspace) {
|
||||||
|
// 如果没有工作区,阻止发送并清空输入框
|
||||||
|
messageInput.value = '';
|
||||||
|
autoResizeTextarea();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const mode = getCurrentMode(); // 从模式选择器组件获取当前模式
|
const mode = getCurrentMode(); // 从模式选择器组件获取当前模式
|
||||||
const model = getCurrentModel(); // 从模型选择器组件获取当前模型
|
const model = getCurrentModel(); // 从模型选择器组件获取当前模型
|
||||||
const planMode = document.getElementById('planToggle')?.checked || false;
|
const planMode = document.getElementById('planToggle')?.checked || false;
|
||||||
|
|||||||
@ -13,13 +13,23 @@
|
|||||||
import {
|
import {
|
||||||
collapseIconSvg,
|
collapseIconSvg,
|
||||||
fileWriteIconSvg,
|
fileWriteIconSvg,
|
||||||
|
fileReadIconSvg,
|
||||||
|
fileDeleteIconSvg,
|
||||||
syntaxCheckIconSvg,
|
syntaxCheckIconSvg,
|
||||||
SearchCode,
|
SearchCode,
|
||||||
|
agentIconSvg,
|
||||||
|
saveKnowledgeIconSvg,
|
||||||
|
simulationIconSvg,
|
||||||
|
waveformIconSvg,
|
||||||
|
knowledgeLoadIconSvg,
|
||||||
|
stateTransitionIconSvg,
|
||||||
} from "../constants/toolIcons";
|
} from "../constants/toolIcons";
|
||||||
import {
|
import {
|
||||||
getWaveformPreviewContent,
|
getWaveformPreviewContent,
|
||||||
getWaveformPreviewScript,
|
getWaveformPreviewScript,
|
||||||
} from "./waveformPreviewContent";
|
} from "./waveformPreviewContent";
|
||||||
|
import { getAgentCardStyles, getAgentCardScript } from "./agentCard";
|
||||||
|
import { getPlanCardStyles, getPlanCardScript } from "./planCard";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取消息区域的 HTML 内容
|
* 获取消息区域的 HTML 内容
|
||||||
@ -416,6 +426,28 @@ export function getMessageAreaStyles(): string {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
.tool-file-read-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.tool-file-read-icon svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.tool-file-delete-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.tool-file-delete-icon svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
.tool-syntax-check-icon {
|
.tool-syntax-check-icon {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
@ -438,6 +470,61 @@ export function getMessageAreaStyles(): string {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
.tool-save-knowledge-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.tool-save-knowledge-icon svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.tool-simulation-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.tool-simulation-icon svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.tool-waveform-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.tool-waveform-icon svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.tool-knowledge-load-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.tool-knowledge-load-icon svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.tool-state-transition-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.tool-state-transition-icon svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
.tool-segment-content {
|
.tool-segment-content {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: max-height 0.3s ease;
|
transition: max-height 0.3s ease;
|
||||||
@ -528,170 +615,9 @@ export function getMessageAreaStyles(): string {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-size: 12px;}
|
font-size: 12px;}
|
||||||
|
|
||||||
/* 智能体卡片样式 */
|
${getAgentCardStyles()}
|
||||||
.segment-agent {
|
|
||||||
margin: 8px 0;
|
|
||||||
}
|
|
||||||
.agent-card {
|
|
||||||
border: 1px solid var(--vscode-input-border);
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
background: var(--vscode-editor-background);
|
|
||||||
}
|
|
||||||
.agent-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 8px 12px;
|
|
||||||
background: var(--vscode-sideBar-background);
|
|
||||||
border-bottom: 1px solid var(--vscode-input-border);
|
|
||||||
}
|
|
||||||
.agent-icon {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
.agent-name {
|
|
||||||
font-weight: 500;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
.agent-status {
|
|
||||||
font-size: 11px;
|
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
.agent-status.running {
|
|
||||||
background: var(--vscode-inputValidation-infoBackground);
|
|
||||||
color: var(--vscode-inputValidation-infoForeground);
|
|
||||||
}
|
|
||||||
.agent-status.completed {
|
|
||||||
background: #28a745;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.agent-status.error {
|
|
||||||
background: #dc3545;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.agent-body {
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
.agent-steps-container {
|
|
||||||
max-height: 150px;
|
|
||||||
overflow-y: auto;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.agent-step {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
background: var(--vscode-list-hoverBackground);
|
|
||||||
}
|
|
||||||
.agent-step:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
.step-icon {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.step-name {
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--vscode-foreground);
|
|
||||||
}
|
|
||||||
.step-result {
|
|
||||||
color: var(--vscode-descriptionForeground);
|
|
||||||
font-size: 11px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.agent-step-placeholder {
|
|
||||||
color: var(--vscode-descriptionForeground);
|
|
||||||
font-style: italic;
|
|
||||||
padding: 8px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 计划卡片样式 */
|
${getPlanCardStyles()}
|
||||||
.segment-plan {
|
|
||||||
margin: 8px 0;
|
|
||||||
}
|
|
||||||
.plan-card {
|
|
||||||
border: 1px solid var(--vscode-input-border);
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
background: var(--vscode-editor-background);
|
|
||||||
}
|
|
||||||
.plan-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 10px 12px;
|
|
||||||
background: var(--vscode-sideBar-background);
|
|
||||||
border-bottom: 1px solid var(--vscode-input-border);
|
|
||||||
}
|
|
||||||
.plan-icon {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
.plan-title {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
.plan-body {
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
.plan-summary {
|
|
||||||
color: var(--vscode-descriptionForeground);
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
.plan-steps {
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
.plan-step {
|
|
||||||
padding: 6px 8px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
background: var(--vscode-list-hoverBackground);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.plan-step:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
.step-num {
|
|
||||||
color: var(--vscode-textLink-foreground);
|
|
||||||
font-weight: 500;
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
.plan-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 12px;
|
|
||||||
border-top: 1px solid var(--vscode-input-border);
|
|
||||||
background: var(--vscode-sideBar-background);
|
|
||||||
}
|
|
||||||
.plan-btn {
|
|
||||||
padding: 6px 16px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.plan-btn-confirm {
|
|
||||||
background: var(--vscode-button-background);
|
|
||||||
color: var(--vscode-button-foreground);
|
|
||||||
}
|
|
||||||
.plan-btn-confirm:hover {
|
|
||||||
background: var(--vscode-button-hoverBackground);
|
|
||||||
}
|
|
||||||
.plan-btn-modify {
|
|
||||||
background: var(--vscode-input-background);
|
|
||||||
color: var(--vscode-foreground);
|
|
||||||
border: 1px solid var(--vscode-input-border);
|
|
||||||
}
|
|
||||||
.plan-btn-cancel {
|
|
||||||
background: transparent;
|
|
||||||
color: var(--vscode-descriptionForeground);
|
|
||||||
}
|
|
||||||
|
|
||||||
${getWaveformPreviewContent()}
|
${getWaveformPreviewContent()}
|
||||||
`;
|
`;
|
||||||
@ -705,18 +631,75 @@ export function getMessageAreaScript(): string {
|
|||||||
// 工具图标定义
|
// 工具图标定义
|
||||||
const collapseIconSvg = \`${collapseIconSvg}\`;
|
const collapseIconSvg = \`${collapseIconSvg}\`;
|
||||||
const fileWriteIconSvg = \`${fileWriteIconSvg}\`;
|
const fileWriteIconSvg = \`${fileWriteIconSvg}\`;
|
||||||
|
const fileReadIconSvg = \`${fileReadIconSvg}\`;
|
||||||
|
const fileDeleteIconSvg = \`${fileDeleteIconSvg}\`;
|
||||||
const syntaxCheckIconSvg = \`${syntaxCheckIconSvg}\`;
|
const syntaxCheckIconSvg = \`${syntaxCheckIconSvg}\`;
|
||||||
const searchCodeIconSvg = \`${SearchCode}\`;
|
const searchCodeIconSvg = \`${SearchCode}\`;
|
||||||
|
const saveKnowledgeIconSvg = \`${saveKnowledgeIconSvg}\`;
|
||||||
|
const simulationIconSvg = \`${simulationIconSvg}\`;
|
||||||
|
const waveformIconSvg = \`${waveformIconSvg}\`;
|
||||||
|
const knowledgeLoadIconSvg = \`${knowledgeLoadIconSvg}\`;
|
||||||
|
const stateTransitionIconSvg = \`${stateTransitionIconSvg}\`;
|
||||||
|
|
||||||
|
${getAgentCardScript()}
|
||||||
|
|
||||||
|
${getPlanCardScript()}
|
||||||
|
|
||||||
|
// 获取工具图标
|
||||||
|
function getToolIcon(toolName) {
|
||||||
|
const iconMap = {
|
||||||
|
'file_read': fileReadIconSvg,
|
||||||
|
'file_write': fileWriteIconSvg,
|
||||||
|
'file_delete': fileDeleteIconSvg,
|
||||||
|
'file_list': searchCodeIconSvg,
|
||||||
|
'syntax_check': syntaxCheckIconSvg,
|
||||||
|
'simulation': simulationIconSvg,
|
||||||
|
'waveform_summary': waveformIconSvg,
|
||||||
|
'knowledge_save': saveKnowledgeIconSvg,
|
||||||
|
'knowledge_load': knowledgeLoadIconSvg,
|
||||||
|
'queryKnowledgeSummary': knowledgeLoadIconSvg,
|
||||||
|
'queryRules': knowledgeLoadIconSvg,
|
||||||
|
'setModule': fileWriteIconSvg,
|
||||||
|
'addSignal': fileWriteIconSvg,
|
||||||
|
'addSignalExample': fileWriteIconSvg,
|
||||||
|
'validateKnowledgeGraph': syntaxCheckIconSvg,
|
||||||
|
'querySignals': searchCodeIconSvg,
|
||||||
|
'addPlan': fileWriteIconSvg,
|
||||||
|
'addEdge': fileWriteIconSvg,
|
||||||
|
'showPlan': searchCodeIconSvg,
|
||||||
|
'addRule': fileWriteIconSvg,
|
||||||
|
'updateNode': fileWriteIconSvg,
|
||||||
|
'addStateTransition': stateTransitionIconSvg
|
||||||
|
};
|
||||||
|
return iconMap[toolName] || '';
|
||||||
|
}
|
||||||
|
|
||||||
// 工具名称映射
|
// 工具名称映射
|
||||||
function getToolDisplayName(toolName) {
|
function getToolDisplayName(toolName) {
|
||||||
const toolNameMap = {
|
const toolNameMap = {
|
||||||
'file_read': '已完成文件读取',
|
'file_read': '已完成文件读取',
|
||||||
'file_write': '已完成文件写入',
|
'file_write': '已完成文件写入',
|
||||||
|
'file_delete': '已完成文件删除',
|
||||||
'file_list': '已检索代码文件',
|
'file_list': '已检索代码文件',
|
||||||
'syntax_check': '已完成语法检查',
|
'syntax_check': '已完成语法检查',
|
||||||
'simulation': '已完成仿真',
|
'simulation': '已完成仿真',
|
||||||
'waveform_summary': '已完成波形分析'
|
'waveform_summary': '已完成波形分析',
|
||||||
|
'knowledge_save': '已保存知识库',
|
||||||
|
'knowledge_load': '已加载知识库',
|
||||||
|
'queryKnowledgeSummary': '已查询知识摘要',
|
||||||
|
'queryRules': '已查询规则',
|
||||||
|
'setModule': '已设置模块',
|
||||||
|
'addSignal': '已添加信号',
|
||||||
|
'addSignalExample': '已添加信号示例',
|
||||||
|
'validateKnowledgeGraph': '已验证知识图谱',
|
||||||
|
'querySignals': '已查询信号',
|
||||||
|
'addPlan': '已添加计划',
|
||||||
|
'addEdge': '已添加边',
|
||||||
|
'showPlan': '已显示计划',
|
||||||
|
'addRule': '已添加规则',
|
||||||
|
'updateNode': '已更新节点',
|
||||||
|
'addStateTransition': '已添加状态转换',
|
||||||
|
'spawnExplorer': '代码探索'
|
||||||
};
|
};
|
||||||
return toolNameMap[toolName] || toolName;
|
return toolNameMap[toolName] || toolName;
|
||||||
}
|
}
|
||||||
@ -949,6 +932,10 @@ export function getMessageAreaScript(): string {
|
|||||||
segmentDiv.className += ' segment-text';
|
segmentDiv.className += ' segment-text';
|
||||||
segmentDiv.innerHTML = formatText(segment.content);
|
segmentDiv.innerHTML = formatText(segment.content);
|
||||||
} else if (segment.type === 'tool') {
|
} else if (segment.type === 'tool') {
|
||||||
|
// 过滤掉不需要显示的工具
|
||||||
|
if (segment.toolName === 'spawnExplorer') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const statusIcon = segment.toolStatus === 'error' ? '❌' : '🔧';
|
const statusIcon = segment.toolStatus === 'error' ? '❌' : '🔧';
|
||||||
const toolResult = segment.toolResult || '';
|
const toolResult = segment.toolResult || '';
|
||||||
|
|
||||||
@ -957,10 +944,7 @@ export function getMessageAreaScript(): string {
|
|||||||
|
|
||||||
segmentDiv.innerHTML = \`
|
segmentDiv.innerHTML = \`
|
||||||
<div class="tool-segment-header\${shouldCollapse ? ' collapsed' : ''}" data-collapsible="\${shouldCollapse}">
|
<div class="tool-segment-header\${shouldCollapse ? ' collapsed' : ''}" data-collapsible="\${shouldCollapse}">
|
||||||
\${shouldCollapse ? collapseIconSvg : ''}
|
\${shouldCollapse ? collapseIconSvg : getToolIcon(segment.toolName)}
|
||||||
\${!shouldCollapse && segment.toolStatus === 'success' && segment.toolName === 'file_write' ? fileWriteIconSvg : ''}
|
|
||||||
\${!shouldCollapse && segment.toolStatus === 'success' && segment.toolName === 'syntax_check' ? syntaxCheckIconSvg : ''}
|
|
||||||
\${!shouldCollapse && segment.toolStatus === 'success' && segment.toolName === 'file_list' ? searchCodeIconSvg : ''}
|
|
||||||
<span class="tool-segment-name">\${getToolDisplayName(segment.toolName) || '工具'}</span>
|
<span class="tool-segment-name">\${getToolDisplayName(segment.toolName) || '工具'}</span>
|
||||||
\${toolResult && !shouldCollapse ? \`<span class="tool-segment-result">\${toolResult}</span>\` : ''}
|
\${toolResult && !shouldCollapse ? \`<span class="tool-segment-result">\${toolResult}</span>\` : ''}
|
||||||
</div>
|
</div>
|
||||||
@ -1083,126 +1067,10 @@ export function getMessageAreaScript(): string {
|
|||||||
}
|
}
|
||||||
} else if (segment.type === 'agent') {
|
} else if (segment.type === 'agent') {
|
||||||
// 智能体卡片渲染
|
// 智能体卡片渲染
|
||||||
segmentDiv.className += ' segment-agent';
|
renderAgentCard(segment, segmentDiv);
|
||||||
const statusText = segment.agentStatus === 'completed' ? '完成'
|
|
||||||
: segment.agentStatus === 'error' ? '错误' : '执行中';
|
|
||||||
const statusClass = segment.agentStatus || 'running';
|
|
||||||
|
|
||||||
const stepsHtml = (segment.agentSteps || []).map(step => {
|
|
||||||
const icon = step.status === 'completed' ? '✅' : step.status === 'error' ? '❌' : '🔄';
|
|
||||||
const result = step.toolResult ? \`: \${step.toolResult.substring(0, 50)}\${step.toolResult.length > 50 ? '...' : ''}\` : '';
|
|
||||||
return \`<div class="agent-step"><span class="step-icon">\${icon}</span><span class="step-name">\${step.toolName}</span><span class="step-result">\${result}</span></div>\`;
|
|
||||||
}).join('');
|
|
||||||
|
|
||||||
segmentDiv.innerHTML = \`
|
|
||||||
<div class="agent-card">
|
|
||||||
<div class="agent-header">
|
|
||||||
<span class="agent-icon">🤖</span>
|
|
||||||
<span class="agent-name">\${segment.agentName || '智能体'}</span>
|
|
||||||
<span class="agent-status \${statusClass}">\${statusText}</span>
|
|
||||||
</div>
|
|
||||||
<div class="agent-body">
|
|
||||||
<div class="agent-steps-container">
|
|
||||||
\${stepsHtml || '<div class="agent-step-placeholder">等待执行...</div>'}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
\`;
|
|
||||||
|
|
||||||
// 自动滚动到最新步骤
|
|
||||||
setTimeout(() => {
|
|
||||||
const container = segmentDiv.querySelector('.agent-steps-container');
|
|
||||||
if (container) {
|
|
||||||
container.scrollTop = container.scrollHeight;
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
} else if (segment.type === 'plan') {
|
} else if (segment.type === 'plan') {
|
||||||
// 计划卡片渲染(类似 askUser)
|
// 计划卡片渲染(使用独立组件)
|
||||||
segmentDiv.className += ' segment-plan';
|
renderPlanCardInSegment(segment, segmentDiv, answeredQuestions);
|
||||||
|
|
||||||
// 检查是否已回答
|
|
||||||
const isAnswered = answeredQuestions.has(segment.askId);
|
|
||||||
const selectedAnswer = answeredQuestions.get(segment.askId);
|
|
||||||
|
|
||||||
if (isAnswered) {
|
|
||||||
segmentDiv.classList.add('answered');
|
|
||||||
}
|
|
||||||
|
|
||||||
const stepsHtml = (segment.planSteps || []).map((step, i) =>
|
|
||||||
\`<div class="plan-step"><span class="step-num">\${i + 1}.</span> \${step}</div>\`
|
|
||||||
).join('');
|
|
||||||
|
|
||||||
// 选项按钮
|
|
||||||
const options = ['确认执行', '修改计划', '取消'];
|
|
||||||
const optionsHtml = options.map(opt => {
|
|
||||||
const isSelected = isAnswered && opt === selectedAnswer;
|
|
||||||
return \`<button class="question-option\${isSelected ? ' selected' : ''}" data-option="\${opt}">\${opt}</button>\`;
|
|
||||||
}).join('');
|
|
||||||
|
|
||||||
segmentDiv.innerHTML = \`
|
|
||||||
<div class="plan-card">
|
|
||||||
<div class="plan-header">
|
|
||||||
<span class="plan-icon">📋</span>
|
|
||||||
<span class="plan-title">\${segment.planTitle || '执行计划'}</span>
|
|
||||||
</div>
|
|
||||||
<div class="plan-body">
|
|
||||||
<div class="plan-summary">\${segment.planSummary || ''}</div>
|
|
||||||
<div class="plan-steps">\${stepsHtml}</div>
|
|
||||||
</div>
|
|
||||||
<div class="plan-actions">
|
|
||||||
<div class="question-options" data-ask-id="\${segment.askId}">\${optionsHtml}</div>
|
|
||||||
<div class="custom-input-container" style="display: \${isAnswered ? 'none' : 'flex'};">
|
|
||||||
<input type="text" class="custom-input" placeholder="输入修改建议..." />
|
|
||||||
<button class="custom-submit">提交</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
\`;
|
|
||||||
|
|
||||||
// 只在未回答时添加事件监听
|
|
||||||
if (!isAnswered) {
|
|
||||||
setTimeout(() => {
|
|
||||||
const optionButtons = segmentDiv.querySelectorAll('.question-option');
|
|
||||||
optionButtons.forEach(btn => {
|
|
||||||
btn.addEventListener('click', function() {
|
|
||||||
const option = this.getAttribute('data-option');
|
|
||||||
// 发送答案到后端
|
|
||||||
handleQuestionAnswerInSegment(segment.askId, option, segmentDiv);
|
|
||||||
// 同时发送 planAction 用于模式切换
|
|
||||||
const actionMap = {
|
|
||||||
'确认执行': 'confirm',
|
|
||||||
'修改计划': 'modify',
|
|
||||||
'取消': 'cancel'
|
|
||||||
};
|
|
||||||
vscode.postMessage({
|
|
||||||
command: 'planAction',
|
|
||||||
action: actionMap[option] || option,
|
|
||||||
planTitle: segment.planTitle
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const submitBtn = segmentDiv.querySelector('.custom-submit');
|
|
||||||
const customInput = segmentDiv.querySelector('.custom-input');
|
|
||||||
if (submitBtn && customInput) {
|
|
||||||
submitBtn.addEventListener('click', function() {
|
|
||||||
const customValue = customInput.value.trim();
|
|
||||||
if (customValue) {
|
|
||||||
handleQuestionAnswerInSegment(segment.askId, customValue, segmentDiv);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
customInput.addEventListener('keypress', function(e) {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
const customValue = customInput.value.trim();
|
|
||||||
if (customValue) {
|
|
||||||
handleQuestionAnswerInSegment(segment.askId, customValue, segmentDiv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
currentSegmentedMessage.appendChild(segmentDiv);
|
currentSegmentedMessage.appendChild(segmentDiv);
|
||||||
@ -1269,6 +1137,10 @@ export function getMessageAreaScript(): string {
|
|||||||
segmentDiv.className += ' segment-text';
|
segmentDiv.className += ' segment-text';
|
||||||
segmentDiv.innerHTML = formatText(segment.content);
|
segmentDiv.innerHTML = formatText(segment.content);
|
||||||
} else if (segment.type === 'tool') {
|
} else if (segment.type === 'tool') {
|
||||||
|
// 过滤掉不需要显示的工具
|
||||||
|
if (segment.toolName === 'spawnExplorer') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const statusIcon = segment.toolStatus === 'error' ? '❌' : '🔧';
|
const statusIcon = segment.toolStatus === 'error' ? '❌' : '🔧';
|
||||||
const toolResult = segment.toolResult || '';
|
const toolResult = segment.toolResult || '';
|
||||||
|
|
||||||
@ -1277,10 +1149,7 @@ export function getMessageAreaScript(): string {
|
|||||||
|
|
||||||
segmentDiv.innerHTML = \`
|
segmentDiv.innerHTML = \`
|
||||||
<div class="tool-segment-header\${shouldCollapse ? ' collapsed' : ''}" data-collapsible="\${shouldCollapse}">
|
<div class="tool-segment-header\${shouldCollapse ? ' collapsed' : ''}" data-collapsible="\${shouldCollapse}">
|
||||||
\${shouldCollapse ? collapseIconSvg : ''}
|
\${shouldCollapse ? collapseIconSvg : getToolIcon(segment.toolName)}
|
||||||
\${!shouldCollapse && segment.toolStatus === 'success' && segment.toolName === 'file_write' ? fileWriteIconSvg : ''}
|
|
||||||
\${!shouldCollapse && segment.toolStatus === 'success' && segment.toolName === 'syntax_check' ? syntaxCheckIconSvg : ''}
|
|
||||||
\${!shouldCollapse && segment.toolStatus === 'success' && segment.toolName === 'file_list' ? searchCodeIconSvg : ''}
|
|
||||||
<span class="tool-segment-name">\${getToolDisplayName(segment.toolName) || '工具'}</span>
|
<span class="tool-segment-name">\${getToolDisplayName(segment.toolName) || '工具'}</span>
|
||||||
\${toolResult && !shouldCollapse ? \`<span class="tool-segment-result">\${toolResult}</span>\` : ''}
|
\${toolResult && !shouldCollapse ? \`<span class="tool-segment-result">\${toolResult}</span>\` : ''}
|
||||||
</div>
|
</div>
|
||||||
@ -1347,72 +1216,10 @@ export function getMessageAreaScript(): string {
|
|||||||
\`;
|
\`;
|
||||||
} else if (segment.type === 'agent') {
|
} else if (segment.type === 'agent') {
|
||||||
// 智能体卡片渲染
|
// 智能体卡片渲染
|
||||||
segmentDiv.className += ' segment-agent';
|
renderAgentCard(segment, segmentDiv);
|
||||||
const statusText = segment.agentStatus === 'completed' ? '完成'
|
|
||||||
: segment.agentStatus === 'error' ? '错误' : '执行中';
|
|
||||||
const statusClass = segment.agentStatus || 'running';
|
|
||||||
|
|
||||||
const stepsHtml = (segment.agentSteps || []).map(step => {
|
|
||||||
const icon = step.status === 'completed' ? '✅' : step.status === 'error' ? '❌' : '🔄';
|
|
||||||
const result = step.toolResult ? \`: \${step.toolResult.substring(0, 50)}\${step.toolResult.length > 50 ? '...' : ''}\` : '';
|
|
||||||
return \`<div class="agent-step"><span class="step-icon">\${icon}</span><span class="step-name">\${step.toolName}</span><span class="step-result">\${result}</span></div>\`;
|
|
||||||
}).join('');
|
|
||||||
|
|
||||||
segmentDiv.innerHTML = \`
|
|
||||||
<div class="agent-card">
|
|
||||||
<div class="agent-header">
|
|
||||||
<span class="agent-icon">🤖</span>
|
|
||||||
<span class="agent-name">\${segment.agentName || '智能体'}</span>
|
|
||||||
<span class="agent-status \${statusClass}">\${statusText}</span>
|
|
||||||
</div>
|
|
||||||
<div class="agent-body">
|
|
||||||
<div class="agent-steps-container">
|
|
||||||
\${stepsHtml || '<div class="agent-step-placeholder">等待执行...</div>'}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
\`;
|
|
||||||
} else if (segment.type === 'plan') {
|
} else if (segment.type === 'plan') {
|
||||||
// 计划卡片渲染
|
// 计划卡片渲染(使用独立组件)
|
||||||
segmentDiv.className += ' segment-plan';
|
renderPlanCardStatic(segment, segmentDiv);
|
||||||
const stepsHtml = (segment.planSteps || []).map((step, i) =>
|
|
||||||
\`<div class="plan-step"><span class="step-num">\${i + 1}.</span> \${step}</div>\`
|
|
||||||
).join('');
|
|
||||||
|
|
||||||
segmentDiv.innerHTML = \`
|
|
||||||
<div class="plan-card">
|
|
||||||
<div class="plan-header">
|
|
||||||
<span class="plan-icon">📋</span>
|
|
||||||
<span class="plan-title">\${segment.planTitle || '执行计划'}</span>
|
|
||||||
</div>
|
|
||||||
<div class="plan-body">
|
|
||||||
<div class="plan-summary">\${segment.planSummary || ''}</div>
|
|
||||||
<div class="plan-steps">\${stepsHtml}</div>
|
|
||||||
</div>
|
|
||||||
<div class="plan-actions">
|
|
||||||
<button class="plan-btn plan-btn-confirm" data-action="confirm">确认执行</button>
|
|
||||||
<button class="plan-btn plan-btn-modify" data-action="modify">修改计划</button>
|
|
||||||
<button class="plan-btn plan-btn-cancel" data-action="cancel">取消</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
\`;
|
|
||||||
|
|
||||||
// 绑定按钮事件
|
|
||||||
setTimeout(() => {
|
|
||||||
const planCard = segmentDiv.querySelector('.plan-card');
|
|
||||||
if (planCard) {
|
|
||||||
planCard.querySelectorAll('.plan-btn').forEach(btn => {
|
|
||||||
btn.addEventListener('click', (e) => {
|
|
||||||
const action = e.currentTarget?.dataset?.action;
|
|
||||||
vscode.postMessage({
|
|
||||||
command: 'planAction',
|
|
||||||
action: action,
|
|
||||||
planTitle: segment.planTitle
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
container.appendChild(segmentDiv);
|
container.appendChild(segmentDiv);
|
||||||
|
|||||||
273
src/views/planCard.ts
Normal file
273
src/views/planCard.ts
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
/**
|
||||||
|
* 计划卡片组件
|
||||||
|
*
|
||||||
|
* 功能说明:
|
||||||
|
* - 显示执行计划的卡片界面
|
||||||
|
* - 包含计划标题、摘要和步骤列表
|
||||||
|
* - 提供确认执行、修改计划、取消等操作按钮
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { plannerIconSvg } from "../constants/toolIcons";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取计划卡片的样式
|
||||||
|
*/
|
||||||
|
export function getPlanCardStyles(): string {
|
||||||
|
return `
|
||||||
|
/* 计划卡片样式 */
|
||||||
|
.segment-plan {
|
||||||
|
margin: 12px 0;
|
||||||
|
}
|
||||||
|
.plan-card {
|
||||||
|
border: 1px solid var(--vscode-input-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--vscode-editor-background);
|
||||||
|
}
|
||||||
|
.plan-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: var(--vscode-sideBar-background);
|
||||||
|
border-bottom: 1px solid var(--vscode-input-border);
|
||||||
|
}
|
||||||
|
.plan-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
.plan-title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.plan-body {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.plan-summary {
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.plan-steps {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.plan-step {
|
||||||
|
padding: 8px 12px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
background: var(--vscode-list-hoverBackground);
|
||||||
|
border-radius: 4px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.plan-step:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.step-num {
|
||||||
|
color: var(--vscode-textLink-foreground);
|
||||||
|
font-weight: 500;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.plan-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-top: 1px solid var(--vscode-input-border);
|
||||||
|
background: var(--vscode-sideBar-background);
|
||||||
|
}
|
||||||
|
.plan-actions .question-options {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.plan-btn {
|
||||||
|
padding: 8px 18px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.plan-btn-confirm {
|
||||||
|
background: var(--vscode-button-background);
|
||||||
|
color: var(--vscode-button-foreground);
|
||||||
|
}
|
||||||
|
.plan-btn-confirm:hover {
|
||||||
|
background: var(--vscode-button-hoverBackground);
|
||||||
|
}
|
||||||
|
.plan-btn-modify {
|
||||||
|
background: var(--vscode-input-background);
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
border: 1px solid var(--vscode-input-border);
|
||||||
|
}
|
||||||
|
.plan-btn-cancel {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
}
|
||||||
|
.plan-actions .custom-input-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.plan-actions .custom-input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: var(--vscode-input-background);
|
||||||
|
color: var(--vscode-input-foreground);
|
||||||
|
border: 1px solid var(--vscode-input-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.plan-actions .custom-submit {
|
||||||
|
padding: 8px 18px;
|
||||||
|
background: var(--vscode-button-background);
|
||||||
|
color: var(--vscode-button-foreground);
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.plan-actions .custom-submit:hover {
|
||||||
|
background: var(--vscode-button-hoverBackground);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取计划卡片的脚本
|
||||||
|
*/
|
||||||
|
export function getPlanCardScript(): string {
|
||||||
|
return `
|
||||||
|
// 渲染计划卡片(在 updateSegmentsRealtime 中使用)
|
||||||
|
function renderPlanCardInSegment(segment, segmentDiv, answeredQuestions) {
|
||||||
|
segmentDiv.className += ' segment-plan';
|
||||||
|
|
||||||
|
// 检查是否已回答
|
||||||
|
const isAnswered = answeredQuestions.has(segment.askId);
|
||||||
|
const selectedAnswer = answeredQuestions.get(segment.askId);
|
||||||
|
|
||||||
|
if (isAnswered) {
|
||||||
|
segmentDiv.classList.add('answered');
|
||||||
|
}
|
||||||
|
|
||||||
|
const stepsHtml = (segment.planSteps || []).map((step, i) =>
|
||||||
|
\`<div class="plan-step"><span class="step-num">\${i + 1}.</span> \${step}</div>\`
|
||||||
|
).join('');
|
||||||
|
|
||||||
|
// 选项按钮
|
||||||
|
const options = ['确认执行', '修改计划', '取消'];
|
||||||
|
const optionsHtml = options.map(opt => {
|
||||||
|
const isSelected = isAnswered && opt === selectedAnswer;
|
||||||
|
return \`<button class="question-option\${isSelected ? ' selected' : ''}" data-option="\${opt}">\${opt}</button>\`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
segmentDiv.innerHTML = \`
|
||||||
|
<div class="plan-card">
|
||||||
|
<div class="plan-header">
|
||||||
|
<span class="plan-icon">${plannerIconSvg}</span>
|
||||||
|
<span class="plan-title">\${segment.planTitle || '执行计划'}</span>
|
||||||
|
</div>
|
||||||
|
<div class="plan-body">
|
||||||
|
<div class="plan-summary">\${segment.planSummary || ''}</div>
|
||||||
|
<div class="plan-steps">\${stepsHtml}</div>
|
||||||
|
</div>
|
||||||
|
<div class="plan-actions">
|
||||||
|
<div class="question-options" data-ask-id="\${segment.askId}">\${optionsHtml}</div>
|
||||||
|
<div class="custom-input-container" style="display: \${isAnswered ? 'none' : 'flex'};">
|
||||||
|
<input type="text" class="custom-input" placeholder="输入修改建议..." />
|
||||||
|
<button class="custom-submit">提交</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
\`;
|
||||||
|
|
||||||
|
// 只在未回答时添加事件监听
|
||||||
|
if (!isAnswered) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const optionButtons = segmentDiv.querySelectorAll('.question-option');
|
||||||
|
optionButtons.forEach(btn => {
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
const option = this.getAttribute('data-option');
|
||||||
|
// 发送答案到后端
|
||||||
|
handleQuestionAnswerInSegment(segment.askId, option, segmentDiv);
|
||||||
|
// 同时发送 planAction 用于模式切换
|
||||||
|
const actionMap = {
|
||||||
|
'确认执行': 'confirm',
|
||||||
|
'修改计划': 'modify',
|
||||||
|
'取消': 'cancel'
|
||||||
|
};
|
||||||
|
vscode.postMessage({
|
||||||
|
command: 'planAction',
|
||||||
|
action: actionMap[option] || option,
|
||||||
|
planTitle: segment.planTitle
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const submitBtn = segmentDiv.querySelector('.custom-submit');
|
||||||
|
const customInput = segmentDiv.querySelector('.custom-input');
|
||||||
|
if (submitBtn && customInput) {
|
||||||
|
submitBtn.addEventListener('click', function() {
|
||||||
|
const customValue = customInput.value.trim();
|
||||||
|
if (customValue) {
|
||||||
|
handleQuestionAnswerInSegment(segment.askId, customValue, segmentDiv);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
customInput.addEventListener('keypress', function(e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
const customValue = customInput.value.trim();
|
||||||
|
if (customValue) {
|
||||||
|
handleQuestionAnswerInSegment(segment.askId, customValue, segmentDiv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染计划卡片(在 renderSegments 中使用)
|
||||||
|
function renderPlanCardStatic(segment, segmentDiv) {
|
||||||
|
segmentDiv.className += ' segment-plan';
|
||||||
|
const stepsHtml = (segment.planSteps || []).map((step, i) =>
|
||||||
|
\`<div class="plan-step"><span class="step-num">\${i + 1}.</span> \${step}</div>\`
|
||||||
|
).join('');
|
||||||
|
|
||||||
|
segmentDiv.innerHTML = \`
|
||||||
|
<div class="plan-card">
|
||||||
|
<div class="plan-header">
|
||||||
|
<span class="plan-icon">📋</span>
|
||||||
|
<span class="plan-title">\${segment.planTitle || '执行计划'}</span>
|
||||||
|
</div>
|
||||||
|
<div class="plan-body">
|
||||||
|
<div class="plan-summary">\${segment.planSummary || ''}</div>
|
||||||
|
<div class="plan-steps">\${stepsHtml}</div>
|
||||||
|
</div>
|
||||||
|
<div class="plan-actions">
|
||||||
|
<button class="plan-btn plan-btn-confirm" data-action="confirm">确认执行</button>
|
||||||
|
<button class="plan-btn plan-btn-modify" data-action="modify">修改计划</button>
|
||||||
|
<button class="plan-btn plan-btn-cancel" data-action="cancel">取消</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
\`;
|
||||||
|
|
||||||
|
// 绑定按钮事件
|
||||||
|
setTimeout(() => {
|
||||||
|
const planCard = segmentDiv.querySelector('.plan-card');
|
||||||
|
if (planCard) {
|
||||||
|
planCard.querySelectorAll('.plan-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', (e) => {
|
||||||
|
const action = e.currentTarget?.dataset?.action;
|
||||||
|
vscode.postMessage({
|
||||||
|
command: 'planAction',
|
||||||
|
action: action,
|
||||||
|
planTitle: segment.planTitle
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
@ -263,61 +263,63 @@ export function getWaveformPreviewScript(): string {
|
|||||||
const timeRange = maxTime - minTime || 1;
|
const timeRange = maxTime - minTime || 1;
|
||||||
|
|
||||||
// 绘制波形
|
// 绘制波形
|
||||||
|
if (signal.width === 1) {
|
||||||
|
// 单比特信号 - 绘制数字波形
|
||||||
let pathData = '';
|
let pathData = '';
|
||||||
let lastX = leftMargin;
|
const yHigh = y;
|
||||||
let lastValue = signal.values[0].value;
|
const yLow = y + signalHeight;
|
||||||
|
|
||||||
signal.values.forEach((point, i) => {
|
signal.values.forEach((point, i) => {
|
||||||
const x = leftMargin + ((point.time - minTime) / timeRange) * waveformWidth;
|
const x = leftMargin + ((point.time - minTime) / timeRange) * waveformWidth;
|
||||||
const value = point.value;
|
const currentY = (point.value === '1') ? yHigh : yLow;
|
||||||
|
|
||||||
if (signal.width === 1) {
|
|
||||||
// 单比特信号 - 绘制数字波形
|
|
||||||
const yHigh = y;
|
|
||||||
const yLow = y + signalHeight;
|
|
||||||
const currentY = (value === '1') ? yHigh : yLow;
|
|
||||||
|
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
pathData = \`M \${x} \${currentY}\`;
|
pathData = \`M \${x} \${currentY}\`;
|
||||||
} else {
|
} else {
|
||||||
// 绘制垂直跳变
|
const prevValue = signal.values[i - 1].value;
|
||||||
const prevY = (lastValue === '1') ? yHigh : yLow;
|
const prevY = (prevValue === '1') ? yHigh : yLow;
|
||||||
if (prevY !== currentY) {
|
if (prevY !== currentY) {
|
||||||
pathData += \` L \${x} \${prevY} L \${x} \${currentY}\`;
|
pathData += \` L \${x} \${prevY} L \${x} \${currentY}\`;
|
||||||
} else {
|
} else {
|
||||||
pathData += \` L \${x} \${currentY}\`;
|
pathData += \` L \${x} \${currentY}\`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lastValue = value;
|
|
||||||
lastX = x;
|
|
||||||
} else {
|
|
||||||
// 多比特信号 - 绘制总线波形(梯形)
|
|
||||||
const yTop = y + 5;
|
|
||||||
const yBottom = y + signalHeight - 5;
|
|
||||||
const transitionWidth = 5;
|
|
||||||
|
|
||||||
if (i === 0) {
|
|
||||||
pathData = \`M \${x} \${yTop + (yBottom - yTop) / 2}\`;
|
|
||||||
} else {
|
|
||||||
// 绘制梯形过渡
|
|
||||||
pathData += \` L \${x - transitionWidth} \${yTop} L \${x} \${yTop + (yBottom - yTop) / 2}\`;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastX = x;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 延伸到右边界
|
// 延伸到右边界
|
||||||
if (signal.width === 1) {
|
const lastValue = signal.values[signal.values.length - 1].value;
|
||||||
const lastY = (lastValue === '1') ? y : (y + signalHeight);
|
const lastY = (lastValue === '1') ? yHigh : yLow;
|
||||||
pathData += \` L \${leftMargin + waveformWidth} \${lastY}\`;
|
pathData += \` L \${leftMargin + waveformWidth} \${lastY}\`;
|
||||||
} else {
|
|
||||||
const yMid = y + signalHeight / 2;
|
|
||||||
pathData += \` L \${leftMargin + waveformWidth} \${yMid}\`;
|
|
||||||
}
|
|
||||||
|
|
||||||
svgContent += \`<path d="\${pathData}" stroke="\${color}" stroke-width="1.5" fill="none"/>\`;
|
svgContent += \`<path d="\${pathData}" stroke="\${color}" stroke-width="1.5" fill="none"/>\`;
|
||||||
|
} else {
|
||||||
|
// 多比特信号 - 绘制总线波形(上下双线)
|
||||||
|
const yTop = y + 5;
|
||||||
|
const yBottom = y + signalHeight - 5;
|
||||||
|
const transitionWidth = 4;
|
||||||
|
|
||||||
|
let topPath = \`M \${leftMargin} \${yTop}\`;
|
||||||
|
let bottomPath = \`M \${leftMargin} \${yBottom}\`;
|
||||||
|
|
||||||
|
signal.values.forEach((point, i) => {
|
||||||
|
const x = leftMargin + ((point.time - minTime) / timeRange) * waveformWidth;
|
||||||
|
|
||||||
|
// 上线和下线都延伸到变化点
|
||||||
|
topPath += \` L \${x} \${yTop}\`;
|
||||||
|
bottomPath += \` L \${x} \${yBottom}\`;
|
||||||
|
|
||||||
|
// 绘制梯形过渡
|
||||||
|
topPath += \` L \${x + transitionWidth} \${yBottom} L \${x + transitionWidth} \${yTop}\`;
|
||||||
|
bottomPath += \` L \${x + transitionWidth} \${yTop} L \${x + transitionWidth} \${yBottom}\`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 延伸到右边界
|
||||||
|
topPath += \` L \${leftMargin + waveformWidth} \${yTop}\`;
|
||||||
|
bottomPath += \` L \${leftMargin + waveformWidth} \${yBottom}\`;
|
||||||
|
|
||||||
|
svgContent += \`<path d="\${topPath}" stroke="\${color}" stroke-width="1.5" fill="none"/>\`;
|
||||||
|
svgContent += \`<path d="\${bottomPath}" stroke="\${color}" stroke-width="1.5" fill="none"/>\`;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 绘制时间轴
|
// 绘制时间轴
|
||||||
|
|||||||
@ -499,8 +499,6 @@ export function getWebviewContent(iconUri?: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
messageInput.focus();
|
|
||||||
|
|
||||||
// 监听来自插件的消息
|
// 监听来自插件的消息
|
||||||
window.addEventListener('message', event => {
|
window.addEventListener('message', event => {
|
||||||
const message = event.data;
|
const message = event.data;
|
||||||
@ -572,6 +570,14 @@ export function getWebviewContent(iconUri?: string): string {
|
|||||||
hideLoadingIndicator();
|
hideLoadingIndicator();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'workspaceStatus':
|
||||||
|
// 更新工作区状态
|
||||||
|
if (typeof hasWorkspace !== 'undefined') {
|
||||||
|
hasWorkspace = message.hasWorkspace;
|
||||||
|
console.log('[WebView] 工作区状态:', hasWorkspace);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case 'vcdInfo':
|
case 'vcdInfo':
|
||||||
// 渲染迷你波形预览信息
|
// 渲染迷你波形预览信息
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user