@hirohsu/user-web-feedback 2.8.18 → 2.8.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -78376,6 +78376,15 @@ var init_prompt_aggregator = __esm({
78376
78376
  getComponentNames() {
78377
78377
  return this.components.map((c) => c.getName());
78378
78378
  }
78379
+ getPromptConfigsForDebug() {
78380
+ const configs = this.getPromptConfigsWithDefaults();
78381
+ return configs.map((c) => ({
78382
+ id: c.id,
78383
+ enabled: c.enabled,
78384
+ firstOrder: c.firstOrder,
78385
+ secondOrder: c.secondOrder
78386
+ }));
78387
+ }
78379
78388
  getPromptConfigsWithDefaults() {
78380
78389
  try {
78381
78390
  const configs = getPromptConfigs();
@@ -78478,61 +78487,6 @@ function formatToolResults(results) {
78478
78487
  }
78479
78488
  return lines.join("\n");
78480
78489
  }
78481
- function buildToolsPrompt(tools, projectName, projectPath) {
78482
- if (tools.length === 0) {
78483
- return "";
78484
- }
78485
- const lines = [];
78486
- if (projectName || projectPath) {
78487
- lines.push("");
78488
- lines.push("## \u5C08\u6848\u80CC\u666F\u8CC7\u8A0A");
78489
- lines.push(`\u7576\u524D\u5C08\u6848: ${projectName || "\u672A\u547D\u540D\u5C08\u6848"}`);
78490
- if (projectPath) {
78491
- lines.push(`\u5C08\u6848\u8DEF\u5F91: ${projectPath}`);
78492
- }
78493
- lines.push("");
78494
- lines.push("**\u91CD\u8981\u6307\u793A**: \u5728\u56DE\u8986\u4E4B\u524D\uFF0C\u4F60\u61C9\u8A72\u5148\u4F7F\u7528 MCP \u5DE5\u5177\u4F86\u67E5\u8A62\u5C08\u6848\u7684\u80CC\u666F\u8CC7\u8A0A\uFF1A");
78495
- lines.push("1. \u5C08\u6848\u7684\u67B6\u69CB\u548C\u7D50\u69CB\uFF08\u5982\u4F7F\u7528 get_symbols_overview, list_dir \u7B49\uFF09");
78496
- lines.push("2. \u5C08\u6848\u7684\u958B\u767C\u8A08\u5283\u548C\u898F\u7BC4\uFF08\u5982\u8B80\u53D6 openspec \u76EE\u9304\u4E2D\u7684\u6587\u4EF6\uFF09");
78497
- lines.push("3. \u7576\u524D\u7684\u4EFB\u52D9\u548C\u9032\u5EA6");
78498
- lines.push("");
78499
- lines.push("**\u8ACB\u52D9\u5FC5\u5148\u8ABF\u7528\u5DE5\u5177\u67E5\u8A62\u5C08\u6848\u8CC7\u8A0A**\uFF0C\u7136\u5F8C\u6839\u64DA\u67E5\u8A62\u7D50\u679C\u63D0\u4F9B\u7CBE\u78BA\u7684\u56DE\u8986\u3002");
78500
- }
78501
- lines.push("");
78502
- lines.push("## MCP \u5DE5\u5177\u4F7F\u7528\u8AAA\u660E");
78503
- lines.push("");
78504
- lines.push("\u7576\u4F60\u9700\u8981\u4F7F\u7528\u5DE5\u5177\u6642\uFF0C\u8ACB\u56DE\u8986\u4E00\u500B JSON \u683C\u5F0F\u7684\u5DE5\u5177\u8ABF\u7528\u8ACB\u6C42\uFF08\u4E0D\u8981\u6709\u5176\u4ED6\u6587\u5B57\uFF09\uFF1A");
78505
- lines.push("");
78506
- lines.push("```json");
78507
- lines.push("{");
78508
- lines.push(' "tool_calls": [');
78509
- lines.push(' { "name": "\u5DE5\u5177\u540D\u7A31", "arguments": { "\u53C3\u6578\u540D": "\u53C3\u6578\u503C" } }');
78510
- lines.push(" ],");
78511
- lines.push(' "message": "\u8AAA\u660E\u4F60\u6B63\u5728\u505A\u4EC0\u9EBC\uFF08\u53EF\u9078\uFF09"');
78512
- lines.push("}");
78513
- lines.push("```");
78514
- lines.push("");
78515
- lines.push("\u5DE5\u5177\u57F7\u884C\u5F8C\uFF0C\u7D50\u679C\u6703\u56DE\u50B3\u7D66\u4F60\u3002\u4F60\u53EF\u4EE5\u7E7C\u7E8C\u8ABF\u7528\u66F4\u591A\u5DE5\u5177\uFF0C\u6216\u6839\u64DA\u7D50\u679C\u63D0\u4F9B\u6700\u7D42\u56DE\u8986\u3002");
78516
- lines.push("\u7576\u4F60\u4E0D\u9700\u8981\u8ABF\u7528\u5DE5\u5177\u6642\uFF0C\u76F4\u63A5\u4EE5\u7D14\u6587\u5B57\u56DE\u8986\u5373\u53EF\u3002");
78517
- lines.push("");
78518
- lines.push("## \u53EF\u7528\u5DE5\u5177\u5217\u8868");
78519
- lines.push("");
78520
- for (const tool of tools) {
78521
- lines.push(`### ${tool.name}`);
78522
- if (tool.description) {
78523
- lines.push(tool.description);
78524
- }
78525
- if (tool.inputSchema) {
78526
- lines.push("");
78527
- lines.push("\u53C3\u6578\u683C\u5F0F:");
78528
- lines.push("```json");
78529
- lines.push(JSON.stringify(tool.inputSchema, null, 2));
78530
- lines.push("```");
78531
- }
78532
- lines.push("");
78533
- }
78534
- return lines.join("\n");
78535
- }
78536
78490
  var init_mcp_tool_parser = __esm({
78537
78491
  "src/utils/mcp-tool-parser.ts"() {
78538
78492
  "use strict";
@@ -78885,131 +78839,91 @@ async function generateCLIReply(request, cliSettings) {
78885
78839
  }
78886
78840
  }
78887
78841
  async function generateAPIReply(request) {
78842
+ const startTime = Date.now();
78888
78843
  try {
78889
78844
  const cacheKey = `${request.aiMessage}:${request.userContext || ""}`;
78890
78845
  if (!request.toolResults) {
78891
78846
  const cached2 = cache.get(cacheKey);
78892
78847
  if (cached2 && Date.now() - cached2.timestamp < CACHE_TTL2) {
78893
78848
  logger.debug("[AI Service] \u4F7F\u7528\u5FEB\u53D6\u56DE\u8986");
78894
- return {
78895
- success: true,
78896
- reply: cached2.reply
78897
- };
78849
+ return { success: true, reply: cached2.reply };
78898
78850
  }
78899
78851
  }
78900
78852
  logger.debug("[AI Service] \u7372\u53D6 AI \u8A2D\u5B9A");
78901
78853
  const settings = getAISettings();
78902
- logger.debug("[AI Service] AI \u8A2D\u5B9A\u7372\u53D6\u5B8C\u6210", {
78903
- hasApiKey: !!settings?.apiKey,
78904
- model: settings?.model,
78905
- hasMcpToolsPrompt: !!settings?.mcpToolsPrompt
78906
- });
78907
78854
  if (!settings || !settings.apiKey || settings.apiKey === "YOUR_API_KEY_HERE") {
78908
78855
  logger.warn("[AI Service] API Key \u672A\u8A2D\u5B9A\u6216\u7121\u6548");
78909
- return {
78910
- success: false,
78911
- error: "\u8ACB\u5148\u5728\u8A2D\u5B9A\u4E2D\u914D\u7F6E AI API Key"
78912
- };
78856
+ return { success: false, error: "\u8ACB\u5148\u5728\u8A2D\u5B9A\u4E2D\u914D\u7F6E AI API Key" };
78913
78857
  }
78914
- let mcpToolsPrompt = "";
78858
+ const aggregator = getPromptAggregator();
78859
+ const cliSettings = getCLISettings();
78860
+ let mcpTools = [];
78915
78861
  if (request.includeMCPTools) {
78916
- logger.debug("[AI Service] \u958B\u59CB\u7372\u53D6 MCP \u5DE5\u5177");
78917
78862
  try {
78918
78863
  const allTools = mcpClientManager.getAllTools();
78919
- logger.debug("[AI Service] MCP \u5DE5\u5177\u7372\u53D6\u5B8C\u6210", {
78920
- toolCount: allTools.length
78921
- });
78922
- if (settings.mcpToolsPrompt) {
78923
- logger.debug("[AI Service] \u4F7F\u7528\u8CC7\u6599\u5EAB\u4E2D\u7684 MCP \u5DE5\u5177\u63D0\u793A\u8A5E");
78924
- mcpToolsPrompt = settings.mcpToolsPrompt.replace(/\{project_name\}/g, request.projectName || "\u672A\u547D\u540D\u5C08\u6848").replace(/\{project_path\}/g, request.projectPath || "");
78925
- if (allTools.length > 0) {
78926
- logger.debug("[AI Service] \u9644\u52A0\u5DE5\u5177\u5217\u8868\u5230\u63D0\u793A\u8A5E");
78927
- mcpToolsPrompt += "\n\n## \u53EF\u7528\u5DE5\u5177\u5217\u8868\n\n";
78928
- for (const tool of allTools) {
78929
- mcpToolsPrompt += `### ${tool.name}
78930
- `;
78931
- if (tool.description) {
78932
- mcpToolsPrompt += `${tool.description}
78933
- `;
78934
- }
78935
- if (tool.inputSchema) {
78936
- mcpToolsPrompt += "\n\u53C3\u6578\u683C\u5F0F:\n```json\n";
78937
- mcpToolsPrompt += JSON.stringify(tool.inputSchema, null, 2);
78938
- mcpToolsPrompt += "\n```\n";
78939
- }
78940
- mcpToolsPrompt += "\n";
78941
- }
78942
- }
78943
- } else {
78944
- logger.debug("[AI Service] \u4F7F\u7528\u9810\u8A2D\u7684 buildToolsPrompt");
78945
- mcpToolsPrompt = buildToolsPrompt(allTools, request.projectName, request.projectPath);
78946
- }
78947
- } catch (error2) {
78948
- logger.warn("[AI Service] Failed to get MCP tools for AI prompt", error2);
78864
+ mcpTools = allTools.map((t) => ({
78865
+ name: t.name,
78866
+ description: t.description,
78867
+ inputSchema: t.inputSchema
78868
+ }));
78869
+ } catch {
78870
+ logger.warn("[AI Service] \u7121\u6CD5\u7372\u53D6 MCP \u5DE5\u5177");
78949
78871
  }
78950
78872
  }
78951
- logger.debug("[AI Service] \u958B\u59CB\u751F\u6210\u56DE\u8986", {
78952
- hasSystemPrompt: !!settings.systemPrompt,
78953
- hasMcpToolsPrompt: !!mcpToolsPrompt,
78954
- temperature: settings.temperature,
78955
- maxTokens: settings.maxTokens
78873
+ const context = aggregator.buildContextSync(request, settings, cliSettings, mcpTools);
78874
+ context.mode = "api";
78875
+ const aggregated = aggregator.aggregate(context);
78876
+ const promptSent = aggregated.fullPrompt;
78877
+ logger.debug("[AI Service] PromptAggregator \u69CB\u5EFA\u5B8C\u6210", {
78878
+ componentCount: aggregated.sections.length,
78879
+ sections: aggregated.sections.map((s) => `${s.name}(order:${s.order})`),
78880
+ totalLength: promptSent.length,
78881
+ tokenEstimate: aggregated.metadata.tokenEstimate
78956
78882
  });
78957
- const promptSent = buildPrompt(
78958
- settings.systemPrompt,
78959
- request.aiMessage,
78960
- request.userContext,
78961
- mcpToolsPrompt,
78962
- request.toolResults
78963
- );
78964
- const reply = await generateWithRetry(
78883
+ const reply = await generateWithRetryFromPrompt(
78965
78884
  settings.apiKey,
78966
78885
  settings.model,
78967
- settings.systemPrompt,
78968
- request.aiMessage,
78969
- request.userContext,
78886
+ promptSent,
78970
78887
  settings.temperature,
78971
- settings.maxTokens,
78972
- 0,
78973
- mcpToolsPrompt,
78974
- request.toolResults
78888
+ settings.maxTokens
78975
78889
  );
78976
- logger.debug("[AI Service] \u56DE\u8986\u751F\u6210\u5B8C\u6210", {
78977
- replyLength: reply.length
78978
- });
78890
+ const elapsedMs = Date.now() - startTime;
78979
78891
  if (!request.toolResults) {
78980
- cache.set(cacheKey, {
78981
- reply,
78982
- timestamp: Date.now()
78983
- });
78892
+ cache.set(cacheKey, { reply, timestamp: Date.now() });
78984
78893
  cleanExpiredCache();
78985
78894
  }
78986
- logger.debug("[AI Service] AI \u56DE\u8986\u8ACB\u6C42\u8655\u7406\u5B8C\u6210");
78895
+ const promptConfigs = aggregator.getPromptConfigsForDebug();
78987
78896
  return {
78988
78897
  success: true,
78989
78898
  reply,
78990
78899
  promptSent,
78991
- mode: "api"
78900
+ mode: "api",
78901
+ debug: {
78902
+ sections: aggregated.sections.map((s) => ({ name: s.name, order: s.order, length: s.content.length })),
78903
+ tokenEstimate: aggregated.metadata.tokenEstimate,
78904
+ totalPromptLength: promptSent.length,
78905
+ elapsedMs,
78906
+ model: settings.model,
78907
+ temperature: settings.temperature,
78908
+ maxTokens: settings.maxTokens,
78909
+ mcpToolsCount: mcpTools.length,
78910
+ componentCount: aggregated.sections.length,
78911
+ promptConfigs
78912
+ }
78992
78913
  };
78993
78914
  } catch (error2) {
78915
+ const elapsedMs = Date.now() - startTime;
78994
78916
  logger.error("[AI Service] AI service error:", error2);
78995
78917
  return {
78996
78918
  success: false,
78997
78919
  error: error2 instanceof Error ? error2.message : "\u672A\u77E5\u932F\u8AA4",
78998
- mode: "api"
78920
+ mode: "api",
78921
+ debug: { elapsedMs }
78999
78922
  };
79000
78923
  }
79001
78924
  }
79002
- async function generateWithRetry(apiKey, model, systemPrompt, aiMessage, userContext, temperature, maxTokens, retryCount = 0, mcpToolsPrompt = "", toolResults) {
78925
+ async function generateWithRetryFromPrompt(apiKey, model, prompt, temperature, maxTokens, retryCount = 0) {
79003
78926
  try {
79004
- logger.debug("[AI Service] generateWithRetry \u958B\u59CB", {
79005
- model,
79006
- retryCount,
79007
- hasSystemPrompt: !!systemPrompt,
79008
- hasMcpToolsPrompt: !!mcpToolsPrompt,
79009
- hasToolResults: !!toolResults,
79010
- temperature,
79011
- maxTokens
79012
- });
79013
78927
  const genAI = new GoogleGenerativeAI(apiKey);
79014
78928
  const generativeModel = genAI.getGenerativeModel({
79015
78929
  model,
@@ -79018,45 +78932,20 @@ async function generateWithRetry(apiKey, model, systemPrompt, aiMessage, userCon
79018
78932
  maxOutputTokens: maxTokens ?? 1e3
79019
78933
  }
79020
78934
  });
79021
- logger.debug("[AI Service] \u69CB\u5EFA\u63D0\u793A\u8A5E");
79022
- const prompt = buildPrompt(systemPrompt, aiMessage, userContext, mcpToolsPrompt, toolResults);
79023
- logger.debug("[AI Service] \u63D0\u793A\u8A5E\u69CB\u5EFA\u5B8C\u6210", {
79024
- promptLength: prompt.length
79025
- });
79026
- logger.debug("[AI Service] \u958B\u59CB\u8ABF\u7528 Google Gemini API");
78935
+ logger.debug("[AI Service] \u958B\u59CB\u8ABF\u7528 API (PromptAggregator prompt)", { promptLength: prompt.length });
79027
78936
  const result = await generativeModel.generateContent(prompt);
79028
- logger.debug("[AI Service] API \u8ABF\u7528\u5B8C\u6210");
79029
78937
  const response = await result.response;
79030
78938
  const text = response.text();
79031
78939
  if (!text) {
79032
78940
  throw new Error("AI \u56DE\u8986\u70BA\u7A7A");
79033
78941
  }
79034
- logger.debug("[AI Service] \u56DE\u8986\u6587\u5B57\u7372\u53D6\u6210\u529F", {
79035
- textLength: text.length
79036
- });
79037
78942
  return text;
79038
78943
  } catch (error2) {
79039
- logger.debug("[AI Service] generateWithRetry \u767C\u751F\u932F\u8AA4", {
79040
- error: error2 instanceof Error ? error2.message : String(error2),
79041
- retryCount
79042
- });
79043
78944
  if (error2 instanceof Error) {
79044
78945
  if (error2.message.includes("429") || error2.message.includes("quota")) {
79045
78946
  if (retryCount < MAX_RETRIES) {
79046
- const delay = RETRY_DELAYS[retryCount] || 4e3;
79047
- await sleep(delay);
79048
- return generateWithRetry(
79049
- apiKey,
79050
- model,
79051
- systemPrompt,
79052
- aiMessage,
79053
- userContext,
79054
- temperature,
79055
- maxTokens,
79056
- retryCount + 1,
79057
- mcpToolsPrompt,
79058
- toolResults
79059
- );
78947
+ await sleep(RETRY_DELAYS[retryCount] || 4e3);
78948
+ return generateWithRetryFromPrompt(apiKey, model, prompt, temperature, maxTokens, retryCount + 1);
79060
78949
  }
79061
78950
  throw new Error("API \u914D\u984D\u5DF2\u7528\u76E1\u6216\u901F\u7387\u9650\u5236\uFF0C\u8ACB\u7A0D\u5F8C\u518D\u8A66");
79062
78951
  }
@@ -79064,51 +78953,13 @@ async function generateWithRetry(apiKey, model, systemPrompt, aiMessage, userCon
79064
78953
  throw new Error("API Key \u7121\u6548\uFF0C\u8ACB\u6AA2\u67E5\u8A2D\u5B9A");
79065
78954
  }
79066
78955
  if (retryCount < MAX_RETRIES) {
79067
- const delay = RETRY_DELAYS[retryCount] || 4e3;
79068
- await sleep(delay);
79069
- return generateWithRetry(
79070
- apiKey,
79071
- model,
79072
- systemPrompt,
79073
- aiMessage,
79074
- userContext,
79075
- temperature,
79076
- maxTokens,
79077
- retryCount + 1,
79078
- mcpToolsPrompt,
79079
- toolResults
79080
- );
78956
+ await sleep(RETRY_DELAYS[retryCount] || 4e3);
78957
+ return generateWithRetryFromPrompt(apiKey, model, prompt, temperature, maxTokens, retryCount + 1);
79081
78958
  }
79082
78959
  }
79083
78960
  throw error2;
79084
78961
  }
79085
78962
  }
79086
- function buildPrompt(systemPrompt, aiMessage, userContext, mcpToolsPrompt = "", toolResults) {
79087
- let prompt = `${systemPrompt}
79088
-
79089
- `;
79090
- if (mcpToolsPrompt) {
79091
- prompt += mcpToolsPrompt + "\n\n";
79092
- }
79093
- prompt += `AI \u5DE5\u4F5C\u532F\u5831\uFF1A
79094
- ${aiMessage}
79095
-
79096
- `;
79097
- if (userContext) {
79098
- prompt += `\u4F7F\u7528\u8005\u4E0A\u4E0B\u6587\uFF1A
79099
- ${userContext}
79100
-
79101
- `;
79102
- }
79103
- if (toolResults) {
79104
- prompt += `\u5148\u524D\u5DE5\u5177\u57F7\u884C\u7D50\u679C\uFF1A
79105
- ${toolResults}
79106
-
79107
- `;
79108
- }
79109
- prompt += "\u8ACB\u751F\u6210\u4E00\u500B\u7C21\u6F54\u3001\u5C08\u696D\u7684\u56DE\u61C9\uFF1A";
79110
- return prompt;
79111
- }
79112
78963
  function sleep(ms) {
79113
78964
  return new Promise((resolve2) => setTimeout(resolve2, ms));
79114
78965
  }
@@ -79208,7 +79059,6 @@ var init_ai_service = __esm({
79208
79059
  init_cli_executor();
79209
79060
  init_cli_detector();
79210
79061
  init_prompt_aggregator2();
79211
- init_mcp_tool_parser();
79212
79062
  MAX_RETRIES = 3;
79213
79063
  RETRY_DELAYS = [1e3, 2e3, 4e3];
79214
79064
  cache = /* @__PURE__ */ new Map();
package/dist/index.cjs CHANGED
@@ -75263,6 +75263,15 @@ var init_prompt_aggregator = __esm({
75263
75263
  getComponentNames() {
75264
75264
  return this.components.map((c) => c.getName());
75265
75265
  }
75266
+ getPromptConfigsForDebug() {
75267
+ const configs = this.getPromptConfigsWithDefaults();
75268
+ return configs.map((c) => ({
75269
+ id: c.id,
75270
+ enabled: c.enabled,
75271
+ firstOrder: c.firstOrder,
75272
+ secondOrder: c.secondOrder
75273
+ }));
75274
+ }
75266
75275
  getPromptConfigsWithDefaults() {
75267
75276
  try {
75268
75277
  const configs = getPromptConfigs();
@@ -75365,61 +75374,6 @@ function formatToolResults(results) {
75365
75374
  }
75366
75375
  return lines.join("\n");
75367
75376
  }
75368
- function buildToolsPrompt(tools, projectName, projectPath) {
75369
- if (tools.length === 0) {
75370
- return "";
75371
- }
75372
- const lines = [];
75373
- if (projectName || projectPath) {
75374
- lines.push("");
75375
- lines.push("## \u5C08\u6848\u80CC\u666F\u8CC7\u8A0A");
75376
- lines.push(`\u7576\u524D\u5C08\u6848: ${projectName || "\u672A\u547D\u540D\u5C08\u6848"}`);
75377
- if (projectPath) {
75378
- lines.push(`\u5C08\u6848\u8DEF\u5F91: ${projectPath}`);
75379
- }
75380
- lines.push("");
75381
- lines.push("**\u91CD\u8981\u6307\u793A**: \u5728\u56DE\u8986\u4E4B\u524D\uFF0C\u4F60\u61C9\u8A72\u5148\u4F7F\u7528 MCP \u5DE5\u5177\u4F86\u67E5\u8A62\u5C08\u6848\u7684\u80CC\u666F\u8CC7\u8A0A\uFF1A");
75382
- lines.push("1. \u5C08\u6848\u7684\u67B6\u69CB\u548C\u7D50\u69CB\uFF08\u5982\u4F7F\u7528 get_symbols_overview, list_dir \u7B49\uFF09");
75383
- lines.push("2. \u5C08\u6848\u7684\u958B\u767C\u8A08\u5283\u548C\u898F\u7BC4\uFF08\u5982\u8B80\u53D6 openspec \u76EE\u9304\u4E2D\u7684\u6587\u4EF6\uFF09");
75384
- lines.push("3. \u7576\u524D\u7684\u4EFB\u52D9\u548C\u9032\u5EA6");
75385
- lines.push("");
75386
- lines.push("**\u8ACB\u52D9\u5FC5\u5148\u8ABF\u7528\u5DE5\u5177\u67E5\u8A62\u5C08\u6848\u8CC7\u8A0A**\uFF0C\u7136\u5F8C\u6839\u64DA\u67E5\u8A62\u7D50\u679C\u63D0\u4F9B\u7CBE\u78BA\u7684\u56DE\u8986\u3002");
75387
- }
75388
- lines.push("");
75389
- lines.push("## MCP \u5DE5\u5177\u4F7F\u7528\u8AAA\u660E");
75390
- lines.push("");
75391
- lines.push("\u7576\u4F60\u9700\u8981\u4F7F\u7528\u5DE5\u5177\u6642\uFF0C\u8ACB\u56DE\u8986\u4E00\u500B JSON \u683C\u5F0F\u7684\u5DE5\u5177\u8ABF\u7528\u8ACB\u6C42\uFF08\u4E0D\u8981\u6709\u5176\u4ED6\u6587\u5B57\uFF09\uFF1A");
75392
- lines.push("");
75393
- lines.push("```json");
75394
- lines.push("{");
75395
- lines.push(' "tool_calls": [');
75396
- lines.push(' { "name": "\u5DE5\u5177\u540D\u7A31", "arguments": { "\u53C3\u6578\u540D": "\u53C3\u6578\u503C" } }');
75397
- lines.push(" ],");
75398
- lines.push(' "message": "\u8AAA\u660E\u4F60\u6B63\u5728\u505A\u4EC0\u9EBC\uFF08\u53EF\u9078\uFF09"');
75399
- lines.push("}");
75400
- lines.push("```");
75401
- lines.push("");
75402
- lines.push("\u5DE5\u5177\u57F7\u884C\u5F8C\uFF0C\u7D50\u679C\u6703\u56DE\u50B3\u7D66\u4F60\u3002\u4F60\u53EF\u4EE5\u7E7C\u7E8C\u8ABF\u7528\u66F4\u591A\u5DE5\u5177\uFF0C\u6216\u6839\u64DA\u7D50\u679C\u63D0\u4F9B\u6700\u7D42\u56DE\u8986\u3002");
75403
- lines.push("\u7576\u4F60\u4E0D\u9700\u8981\u8ABF\u7528\u5DE5\u5177\u6642\uFF0C\u76F4\u63A5\u4EE5\u7D14\u6587\u5B57\u56DE\u8986\u5373\u53EF\u3002");
75404
- lines.push("");
75405
- lines.push("## \u53EF\u7528\u5DE5\u5177\u5217\u8868");
75406
- lines.push("");
75407
- for (const tool of tools) {
75408
- lines.push(`### ${tool.name}`);
75409
- if (tool.description) {
75410
- lines.push(tool.description);
75411
- }
75412
- if (tool.inputSchema) {
75413
- lines.push("");
75414
- lines.push("\u53C3\u6578\u683C\u5F0F:");
75415
- lines.push("```json");
75416
- lines.push(JSON.stringify(tool.inputSchema, null, 2));
75417
- lines.push("```");
75418
- }
75419
- lines.push("");
75420
- }
75421
- return lines.join("\n");
75422
- }
75423
75377
  var init_mcp_tool_parser = __esm({
75424
75378
  "src/utils/mcp-tool-parser.ts"() {
75425
75379
  "use strict";
@@ -75772,131 +75726,91 @@ async function generateCLIReply(request, cliSettings) {
75772
75726
  }
75773
75727
  }
75774
75728
  async function generateAPIReply(request) {
75729
+ const startTime = Date.now();
75775
75730
  try {
75776
75731
  const cacheKey = `${request.aiMessage}:${request.userContext || ""}`;
75777
75732
  if (!request.toolResults) {
75778
75733
  const cached2 = cache.get(cacheKey);
75779
75734
  if (cached2 && Date.now() - cached2.timestamp < CACHE_TTL2) {
75780
75735
  logger.debug("[AI Service] \u4F7F\u7528\u5FEB\u53D6\u56DE\u8986");
75781
- return {
75782
- success: true,
75783
- reply: cached2.reply
75784
- };
75736
+ return { success: true, reply: cached2.reply };
75785
75737
  }
75786
75738
  }
75787
75739
  logger.debug("[AI Service] \u7372\u53D6 AI \u8A2D\u5B9A");
75788
75740
  const settings = getAISettings();
75789
- logger.debug("[AI Service] AI \u8A2D\u5B9A\u7372\u53D6\u5B8C\u6210", {
75790
- hasApiKey: !!settings?.apiKey,
75791
- model: settings?.model,
75792
- hasMcpToolsPrompt: !!settings?.mcpToolsPrompt
75793
- });
75794
75741
  if (!settings || !settings.apiKey || settings.apiKey === "YOUR_API_KEY_HERE") {
75795
75742
  logger.warn("[AI Service] API Key \u672A\u8A2D\u5B9A\u6216\u7121\u6548");
75796
- return {
75797
- success: false,
75798
- error: "\u8ACB\u5148\u5728\u8A2D\u5B9A\u4E2D\u914D\u7F6E AI API Key"
75799
- };
75743
+ return { success: false, error: "\u8ACB\u5148\u5728\u8A2D\u5B9A\u4E2D\u914D\u7F6E AI API Key" };
75800
75744
  }
75801
- let mcpToolsPrompt = "";
75745
+ const aggregator = getPromptAggregator();
75746
+ const cliSettings = getCLISettings();
75747
+ let mcpTools = [];
75802
75748
  if (request.includeMCPTools) {
75803
- logger.debug("[AI Service] \u958B\u59CB\u7372\u53D6 MCP \u5DE5\u5177");
75804
75749
  try {
75805
75750
  const allTools = mcpClientManager.getAllTools();
75806
- logger.debug("[AI Service] MCP \u5DE5\u5177\u7372\u53D6\u5B8C\u6210", {
75807
- toolCount: allTools.length
75808
- });
75809
- if (settings.mcpToolsPrompt) {
75810
- logger.debug("[AI Service] \u4F7F\u7528\u8CC7\u6599\u5EAB\u4E2D\u7684 MCP \u5DE5\u5177\u63D0\u793A\u8A5E");
75811
- mcpToolsPrompt = settings.mcpToolsPrompt.replace(/\{project_name\}/g, request.projectName || "\u672A\u547D\u540D\u5C08\u6848").replace(/\{project_path\}/g, request.projectPath || "");
75812
- if (allTools.length > 0) {
75813
- logger.debug("[AI Service] \u9644\u52A0\u5DE5\u5177\u5217\u8868\u5230\u63D0\u793A\u8A5E");
75814
- mcpToolsPrompt += "\n\n## \u53EF\u7528\u5DE5\u5177\u5217\u8868\n\n";
75815
- for (const tool of allTools) {
75816
- mcpToolsPrompt += `### ${tool.name}
75817
- `;
75818
- if (tool.description) {
75819
- mcpToolsPrompt += `${tool.description}
75820
- `;
75821
- }
75822
- if (tool.inputSchema) {
75823
- mcpToolsPrompt += "\n\u53C3\u6578\u683C\u5F0F:\n```json\n";
75824
- mcpToolsPrompt += JSON.stringify(tool.inputSchema, null, 2);
75825
- mcpToolsPrompt += "\n```\n";
75826
- }
75827
- mcpToolsPrompt += "\n";
75828
- }
75829
- }
75830
- } else {
75831
- logger.debug("[AI Service] \u4F7F\u7528\u9810\u8A2D\u7684 buildToolsPrompt");
75832
- mcpToolsPrompt = buildToolsPrompt(allTools, request.projectName, request.projectPath);
75833
- }
75834
- } catch (error2) {
75835
- logger.warn("[AI Service] Failed to get MCP tools for AI prompt", error2);
75751
+ mcpTools = allTools.map((t) => ({
75752
+ name: t.name,
75753
+ description: t.description,
75754
+ inputSchema: t.inputSchema
75755
+ }));
75756
+ } catch {
75757
+ logger.warn("[AI Service] \u7121\u6CD5\u7372\u53D6 MCP \u5DE5\u5177");
75836
75758
  }
75837
75759
  }
75838
- logger.debug("[AI Service] \u958B\u59CB\u751F\u6210\u56DE\u8986", {
75839
- hasSystemPrompt: !!settings.systemPrompt,
75840
- hasMcpToolsPrompt: !!mcpToolsPrompt,
75841
- temperature: settings.temperature,
75842
- maxTokens: settings.maxTokens
75760
+ const context = aggregator.buildContextSync(request, settings, cliSettings, mcpTools);
75761
+ context.mode = "api";
75762
+ const aggregated = aggregator.aggregate(context);
75763
+ const promptSent = aggregated.fullPrompt;
75764
+ logger.debug("[AI Service] PromptAggregator \u69CB\u5EFA\u5B8C\u6210", {
75765
+ componentCount: aggregated.sections.length,
75766
+ sections: aggregated.sections.map((s) => `${s.name}(order:${s.order})`),
75767
+ totalLength: promptSent.length,
75768
+ tokenEstimate: aggregated.metadata.tokenEstimate
75843
75769
  });
75844
- const promptSent = buildPrompt(
75845
- settings.systemPrompt,
75846
- request.aiMessage,
75847
- request.userContext,
75848
- mcpToolsPrompt,
75849
- request.toolResults
75850
- );
75851
- const reply = await generateWithRetry(
75770
+ const reply = await generateWithRetryFromPrompt(
75852
75771
  settings.apiKey,
75853
75772
  settings.model,
75854
- settings.systemPrompt,
75855
- request.aiMessage,
75856
- request.userContext,
75773
+ promptSent,
75857
75774
  settings.temperature,
75858
- settings.maxTokens,
75859
- 0,
75860
- mcpToolsPrompt,
75861
- request.toolResults
75775
+ settings.maxTokens
75862
75776
  );
75863
- logger.debug("[AI Service] \u56DE\u8986\u751F\u6210\u5B8C\u6210", {
75864
- replyLength: reply.length
75865
- });
75777
+ const elapsedMs = Date.now() - startTime;
75866
75778
  if (!request.toolResults) {
75867
- cache.set(cacheKey, {
75868
- reply,
75869
- timestamp: Date.now()
75870
- });
75779
+ cache.set(cacheKey, { reply, timestamp: Date.now() });
75871
75780
  cleanExpiredCache();
75872
75781
  }
75873
- logger.debug("[AI Service] AI \u56DE\u8986\u8ACB\u6C42\u8655\u7406\u5B8C\u6210");
75782
+ const promptConfigs = aggregator.getPromptConfigsForDebug();
75874
75783
  return {
75875
75784
  success: true,
75876
75785
  reply,
75877
75786
  promptSent,
75878
- mode: "api"
75787
+ mode: "api",
75788
+ debug: {
75789
+ sections: aggregated.sections.map((s) => ({ name: s.name, order: s.order, length: s.content.length })),
75790
+ tokenEstimate: aggregated.metadata.tokenEstimate,
75791
+ totalPromptLength: promptSent.length,
75792
+ elapsedMs,
75793
+ model: settings.model,
75794
+ temperature: settings.temperature,
75795
+ maxTokens: settings.maxTokens,
75796
+ mcpToolsCount: mcpTools.length,
75797
+ componentCount: aggregated.sections.length,
75798
+ promptConfigs
75799
+ }
75879
75800
  };
75880
75801
  } catch (error2) {
75802
+ const elapsedMs = Date.now() - startTime;
75881
75803
  logger.error("[AI Service] AI service error:", error2);
75882
75804
  return {
75883
75805
  success: false,
75884
75806
  error: error2 instanceof Error ? error2.message : "\u672A\u77E5\u932F\u8AA4",
75885
- mode: "api"
75807
+ mode: "api",
75808
+ debug: { elapsedMs }
75886
75809
  };
75887
75810
  }
75888
75811
  }
75889
- async function generateWithRetry(apiKey, model, systemPrompt, aiMessage, userContext, temperature, maxTokens, retryCount = 0, mcpToolsPrompt = "", toolResults) {
75812
+ async function generateWithRetryFromPrompt(apiKey, model, prompt, temperature, maxTokens, retryCount = 0) {
75890
75813
  try {
75891
- logger.debug("[AI Service] generateWithRetry \u958B\u59CB", {
75892
- model,
75893
- retryCount,
75894
- hasSystemPrompt: !!systemPrompt,
75895
- hasMcpToolsPrompt: !!mcpToolsPrompt,
75896
- hasToolResults: !!toolResults,
75897
- temperature,
75898
- maxTokens
75899
- });
75900
75814
  const genAI = new GoogleGenerativeAI(apiKey);
75901
75815
  const generativeModel = genAI.getGenerativeModel({
75902
75816
  model,
@@ -75905,45 +75819,20 @@ async function generateWithRetry(apiKey, model, systemPrompt, aiMessage, userCon
75905
75819
  maxOutputTokens: maxTokens ?? 1e3
75906
75820
  }
75907
75821
  });
75908
- logger.debug("[AI Service] \u69CB\u5EFA\u63D0\u793A\u8A5E");
75909
- const prompt = buildPrompt(systemPrompt, aiMessage, userContext, mcpToolsPrompt, toolResults);
75910
- logger.debug("[AI Service] \u63D0\u793A\u8A5E\u69CB\u5EFA\u5B8C\u6210", {
75911
- promptLength: prompt.length
75912
- });
75913
- logger.debug("[AI Service] \u958B\u59CB\u8ABF\u7528 Google Gemini API");
75822
+ logger.debug("[AI Service] \u958B\u59CB\u8ABF\u7528 API (PromptAggregator prompt)", { promptLength: prompt.length });
75914
75823
  const result = await generativeModel.generateContent(prompt);
75915
- logger.debug("[AI Service] API \u8ABF\u7528\u5B8C\u6210");
75916
75824
  const response = await result.response;
75917
75825
  const text = response.text();
75918
75826
  if (!text) {
75919
75827
  throw new Error("AI \u56DE\u8986\u70BA\u7A7A");
75920
75828
  }
75921
- logger.debug("[AI Service] \u56DE\u8986\u6587\u5B57\u7372\u53D6\u6210\u529F", {
75922
- textLength: text.length
75923
- });
75924
75829
  return text;
75925
75830
  } catch (error2) {
75926
- logger.debug("[AI Service] generateWithRetry \u767C\u751F\u932F\u8AA4", {
75927
- error: error2 instanceof Error ? error2.message : String(error2),
75928
- retryCount
75929
- });
75930
75831
  if (error2 instanceof Error) {
75931
75832
  if (error2.message.includes("429") || error2.message.includes("quota")) {
75932
75833
  if (retryCount < MAX_RETRIES) {
75933
- const delay = RETRY_DELAYS[retryCount] || 4e3;
75934
- await sleep(delay);
75935
- return generateWithRetry(
75936
- apiKey,
75937
- model,
75938
- systemPrompt,
75939
- aiMessage,
75940
- userContext,
75941
- temperature,
75942
- maxTokens,
75943
- retryCount + 1,
75944
- mcpToolsPrompt,
75945
- toolResults
75946
- );
75834
+ await sleep(RETRY_DELAYS[retryCount] || 4e3);
75835
+ return generateWithRetryFromPrompt(apiKey, model, prompt, temperature, maxTokens, retryCount + 1);
75947
75836
  }
75948
75837
  throw new Error("API \u914D\u984D\u5DF2\u7528\u76E1\u6216\u901F\u7387\u9650\u5236\uFF0C\u8ACB\u7A0D\u5F8C\u518D\u8A66");
75949
75838
  }
@@ -75951,51 +75840,13 @@ async function generateWithRetry(apiKey, model, systemPrompt, aiMessage, userCon
75951
75840
  throw new Error("API Key \u7121\u6548\uFF0C\u8ACB\u6AA2\u67E5\u8A2D\u5B9A");
75952
75841
  }
75953
75842
  if (retryCount < MAX_RETRIES) {
75954
- const delay = RETRY_DELAYS[retryCount] || 4e3;
75955
- await sleep(delay);
75956
- return generateWithRetry(
75957
- apiKey,
75958
- model,
75959
- systemPrompt,
75960
- aiMessage,
75961
- userContext,
75962
- temperature,
75963
- maxTokens,
75964
- retryCount + 1,
75965
- mcpToolsPrompt,
75966
- toolResults
75967
- );
75843
+ await sleep(RETRY_DELAYS[retryCount] || 4e3);
75844
+ return generateWithRetryFromPrompt(apiKey, model, prompt, temperature, maxTokens, retryCount + 1);
75968
75845
  }
75969
75846
  }
75970
75847
  throw error2;
75971
75848
  }
75972
75849
  }
75973
- function buildPrompt(systemPrompt, aiMessage, userContext, mcpToolsPrompt = "", toolResults) {
75974
- let prompt = `${systemPrompt}
75975
-
75976
- `;
75977
- if (mcpToolsPrompt) {
75978
- prompt += mcpToolsPrompt + "\n\n";
75979
- }
75980
- prompt += `AI \u5DE5\u4F5C\u532F\u5831\uFF1A
75981
- ${aiMessage}
75982
-
75983
- `;
75984
- if (userContext) {
75985
- prompt += `\u4F7F\u7528\u8005\u4E0A\u4E0B\u6587\uFF1A
75986
- ${userContext}
75987
-
75988
- `;
75989
- }
75990
- if (toolResults) {
75991
- prompt += `\u5148\u524D\u5DE5\u5177\u57F7\u884C\u7D50\u679C\uFF1A
75992
- ${toolResults}
75993
-
75994
- `;
75995
- }
75996
- prompt += "\u8ACB\u751F\u6210\u4E00\u500B\u7C21\u6F54\u3001\u5C08\u696D\u7684\u56DE\u61C9\uFF1A";
75997
- return prompt;
75998
- }
75999
75850
  function sleep(ms) {
76000
75851
  return new Promise((resolve) => setTimeout(resolve, ms));
76001
75852
  }
@@ -76095,7 +75946,6 @@ var init_ai_service = __esm({
76095
75946
  init_cli_executor();
76096
75947
  init_cli_detector();
76097
75948
  init_prompt_aggregator2();
76098
- init_mcp_tool_parser();
76099
75949
  MAX_RETRIES = 3;
76100
75950
  RETRY_DELAYS = [1e3, 2e3, 4e3];
76101
75951
  cache = /* @__PURE__ */ new Map();
@@ -1,26 +1,21 @@
1
1
  /**
2
2
  * conversation-panel.js
3
3
  * 對話面板元件 - 顯示 AI 對話流程
4
- * 支援 6 種對話條目類型: prompt, thinking, tool, result, ai, error
4
+ * 支援 7 種對話條目類型: prompt, thinking, tool, result, ai, error, debug
5
5
  */
6
6
 
7
7
  import { escapeHtml } from './ui-helpers.js';
8
8
 
9
- /**
10
- * 對話條目類型
11
- */
12
9
  export const ConversationEntryType = {
13
10
  PROMPT: 'prompt',
14
11
  THINKING: 'thinking',
15
12
  TOOL: 'tool',
16
13
  RESULT: 'result',
17
14
  AI: 'ai',
18
- ERROR: 'error'
15
+ ERROR: 'error',
16
+ DEBUG: 'debug'
19
17
  };
20
18
 
21
- /**
22
- * 對話條目視覺配置
23
- */
24
19
  const entryConfig = {
25
20
  prompt: {
26
21
  icon: '📤',
@@ -57,12 +52,15 @@ const entryConfig = {
57
52
  title: '錯誤',
58
53
  className: 'entry-error',
59
54
  borderColor: 'var(--accent-red)'
55
+ },
56
+ debug: {
57
+ icon: '🐛',
58
+ title: 'Debug',
59
+ className: 'entry-debug',
60
+ borderColor: 'var(--accent-orange, #f97316)'
60
61
  }
61
62
  };
62
63
 
63
- /**
64
- * 建立對話面板容器
65
- */
66
64
  export function createConversationPanel() {
67
65
  const panel = document.createElement('div');
68
66
  panel.id = 'conversationPanel';
@@ -73,14 +71,18 @@ export function createConversationPanel() {
73
71
  <span class="icon">💬</span>
74
72
  <span id="conversationTitle">AI 對話</span>
75
73
  </div>
76
- <div class="conversation-mode">
77
- <span class="mode-indicator" id="conversationModeIndicator"></span>
78
- <span id="conversationMode">準備中</span>
74
+ <div class="conversation-header-right">
75
+ <div class="conversation-mode">
76
+ <span class="mode-indicator" id="conversationModeIndicator"></span>
77
+ <span id="conversationMode">準備中</span>
78
+ </div>
79
+ <label class="debug-toggle" title="顯示 Debug 資訊">
80
+ <input type="checkbox" id="debugToggle">
81
+ <span>🐛</span>
82
+ </label>
79
83
  </div>
80
84
  </div>
81
- <div class="conversation-body" id="conversationBody">
82
- <!-- 對話條目會動態添加 -->
83
- </div>
85
+ <div class="conversation-body" id="conversationBody"></div>
84
86
  <div class="conversation-footer">
85
87
  <button type="button" id="closeConversation" class="btn btn-secondary">關閉</button>
86
88
  </div>
@@ -88,9 +90,6 @@ export function createConversationPanel() {
88
90
  return panel;
89
91
  }
90
92
 
91
- /**
92
- * 建立對話條目元素
93
- */
94
93
  export function createConversationEntry(type, content, options = {}) {
95
94
  const config = entryConfig[type] || entryConfig.ai;
96
95
  const entry = document.createElement('div');
@@ -98,7 +97,7 @@ export function createConversationEntry(type, content, options = {}) {
98
97
  entry.style.borderLeftColor = config.borderColor;
99
98
 
100
99
  const titleText = options.title || config.title;
101
- const collapsed = options.collapsed ?? (type === 'prompt' || type === 'tool');
100
+ const collapsed = options.collapsed ?? (type === 'prompt' || type === 'tool' || type === 'debug');
102
101
  const timestamp = options.timestamp ? formatTimestamp(options.timestamp) : '';
103
102
 
104
103
  let contentHtml = '';
@@ -126,8 +125,93 @@ export function createConversationEntry(type, content, options = {}) {
126
125
  }
127
126
 
128
127
  /**
129
- * 新增對話條目到面板
128
+ * 建立 Debug 資訊條目(結構化表格)
130
129
  */
130
+ export function createDebugEntry(debugInfo, options = {}) {
131
+ const config = entryConfig.debug;
132
+ const entry = document.createElement('div');
133
+ entry.className = `conversation-entry ${config.className}`;
134
+ entry.style.borderLeftColor = config.borderColor;
135
+
136
+ const titleText = options.title || config.title;
137
+ const collapsed = options.collapsed ?? true;
138
+ const timestamp = options.timestamp ? formatTimestamp(options.timestamp) : '';
139
+
140
+ let bodyHtml = '<div class="debug-info-grid">';
141
+
142
+ if (debugInfo.elapsedMs !== undefined) {
143
+ bodyHtml += debugRow('⏱️ 耗時', `${debugInfo.elapsedMs} ms`);
144
+ }
145
+ if (debugInfo.model) {
146
+ bodyHtml += debugRow('🧠 模型', debugInfo.model);
147
+ }
148
+ if (debugInfo.temperature !== undefined) {
149
+ bodyHtml += debugRow('🌡️ Temperature', debugInfo.temperature);
150
+ }
151
+ if (debugInfo.maxTokens !== undefined) {
152
+ bodyHtml += debugRow('📏 Max Tokens', debugInfo.maxTokens);
153
+ }
154
+ if (debugInfo.tokenEstimate !== undefined) {
155
+ bodyHtml += debugRow('🔢 Token 預估', `~${debugInfo.tokenEstimate}`);
156
+ }
157
+ if (debugInfo.totalPromptLength !== undefined) {
158
+ bodyHtml += debugRow('📐 Prompt 長度', `${debugInfo.totalPromptLength} chars`);
159
+ }
160
+ if (debugInfo.componentCount !== undefined) {
161
+ bodyHtml += debugRow('🧩 組件數量', debugInfo.componentCount);
162
+ }
163
+ if (debugInfo.mcpToolsCount !== undefined) {
164
+ bodyHtml += debugRow('🔧 MCP 工具數', debugInfo.mcpToolsCount);
165
+ }
166
+
167
+ bodyHtml += '</div>';
168
+
169
+ if (debugInfo.sections && debugInfo.sections.length > 0) {
170
+ bodyHtml += '<div class="debug-sections">';
171
+ bodyHtml += '<div class="debug-section-title">📋 提示詞區段順序(實際送出)</div>';
172
+ bodyHtml += '<table class="debug-table"><thead><tr><th>#</th><th>區段名稱</th><th>順序</th><th>長度</th></tr></thead><tbody>';
173
+ debugInfo.sections.forEach((s, i) => {
174
+ bodyHtml += `<tr><td>${i + 1}</td><td>${escapeHtml(s.name)}</td><td>${s.order}</td><td>${s.length} chars</td></tr>`;
175
+ });
176
+ bodyHtml += '</tbody></table></div>';
177
+ }
178
+
179
+ if (debugInfo.promptConfigs && debugInfo.promptConfigs.length > 0) {
180
+ bodyHtml += '<div class="debug-sections">';
181
+ bodyHtml += '<div class="debug-section-title">⚙️ 提示詞配置(設定值)</div>';
182
+ bodyHtml += '<table class="debug-table"><thead><tr><th>ID</th><th>啟用</th><th>第一次</th><th>第二次</th></tr></thead><tbody>';
183
+ debugInfo.promptConfigs.forEach(c => {
184
+ const enabledIcon = c.enabled ? '✅' : '❌';
185
+ bodyHtml += `<tr><td>${escapeHtml(c.id)}</td><td>${enabledIcon}</td><td>${c.firstOrder}</td><td>${c.secondOrder}</td></tr>`;
186
+ });
187
+ bodyHtml += '</tbody></table></div>';
188
+ }
189
+
190
+ if (debugInfo.error) {
191
+ bodyHtml += `<div class="debug-error">❌ 錯誤: ${escapeHtml(debugInfo.error)}</div>`;
192
+ }
193
+
194
+ entry.innerHTML = `
195
+ <details ${collapsed ? '' : 'open'}>
196
+ <summary class="entry-summary">
197
+ <span class="entry-icon">${config.icon}</span>
198
+ <span class="entry-title">${titleText}</span>
199
+ ${timestamp ? `<span class="entry-timestamp">${timestamp}</span>` : ''}
200
+ ${options.badge ? `<span class="entry-badge">${options.badge}</span>` : ''}
201
+ </summary>
202
+ <div class="entry-body debug-body">
203
+ ${bodyHtml}
204
+ </div>
205
+ </details>
206
+ `;
207
+
208
+ return entry;
209
+ }
210
+
211
+ function debugRow(label, value) {
212
+ return `<div class="debug-row"><span class="debug-label">${label}</span><span class="debug-value">${escapeHtml(String(value))}</span></div>`;
213
+ }
214
+
131
215
  export function addConversationEntry(type, content, options = {}) {
132
216
  const body = document.getElementById('conversationBody');
133
217
  if (!body) return null;
@@ -135,13 +219,25 @@ export function addConversationEntry(type, content, options = {}) {
135
219
  const entry = createConversationEntry(type, content, options);
136
220
  body.appendChild(entry);
137
221
  body.scrollTop = body.scrollHeight;
222
+ return entry;
223
+ }
224
+
225
+ export function addDebugEntry(debugInfo, options = {}) {
226
+ const body = document.getElementById('conversationBody');
227
+ if (!body) return null;
228
+
229
+ const entry = createDebugEntry(debugInfo, options);
230
+ body.appendChild(entry);
231
+ body.scrollTop = body.scrollHeight;
232
+
233
+ const debugToggle = document.getElementById('debugToggle');
234
+ if (debugToggle && !debugToggle.checked) {
235
+ entry.style.display = 'none';
236
+ }
138
237
 
139
238
  return entry;
140
239
  }
141
240
 
142
- /**
143
- * 清空對話面板
144
- */
145
241
  export function clearConversationPanel() {
146
242
  const body = document.getElementById('conversationBody');
147
243
  if (body) {
@@ -149,9 +245,6 @@ export function clearConversationPanel() {
149
245
  }
150
246
  }
151
247
 
152
- /**
153
- * 更新對話面板模式顯示
154
- */
155
248
  export function updateConversationMode(mode, cliTool = null) {
156
249
  const modeElement = document.getElementById('conversationMode');
157
250
  const indicator = document.getElementById('conversationModeIndicator');
@@ -176,9 +269,6 @@ export function updateConversationMode(mode, cliTool = null) {
176
269
  }
177
270
  }
178
271
 
179
- /**
180
- * 更新對話面板標題
181
- */
182
272
  export function updateConversationTitle(title) {
183
273
  const titleElement = document.getElementById('conversationTitle');
184
274
  if (titleElement) {
@@ -186,9 +276,6 @@ export function updateConversationTitle(title) {
186
276
  }
187
277
  }
188
278
 
189
- /**
190
- * 顯示對話面板
191
- */
192
279
  export function showConversationPanel() {
193
280
  let panel = document.getElementById('aiConversationPanel');
194
281
  if (!panel) {
@@ -202,14 +289,21 @@ export function showConversationPanel() {
202
289
  if (closeBtn) {
203
290
  closeBtn.onclick = hideConversationPanel;
204
291
  }
292
+
293
+ const debugToggle = panel.querySelector('#debugToggle');
294
+ if (debugToggle) {
295
+ debugToggle.addEventListener('change', (e) => {
296
+ const show = e.target.checked;
297
+ panel.querySelectorAll('.entry-debug').forEach(el => {
298
+ el.style.display = show ? '' : 'none';
299
+ });
300
+ });
301
+ }
205
302
  }
206
303
  panel.style.display = 'flex';
207
304
  clearConversationPanel();
208
305
  }
209
306
 
210
- /**
211
- * 隱藏對話面板
212
- */
213
307
  export function hideConversationPanel() {
214
308
  const panel = document.getElementById('aiConversationPanel');
215
309
  if (panel) {
@@ -217,17 +311,11 @@ export function hideConversationPanel() {
217
311
  }
218
312
  }
219
313
 
220
- /**
221
- * 格式化時間戳記
222
- */
223
314
  function formatTimestamp(timestamp) {
224
315
  const date = new Date(timestamp);
225
316
  return date.toLocaleTimeString('zh-TW', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
226
317
  }
227
318
 
228
- /**
229
- * 新增思考中動畫條目
230
- */
231
319
  export function addThinkingEntry(message = 'AI 思考中...') {
232
320
  return addConversationEntry(ConversationEntryType.THINKING, message, {
233
321
  collapsed: false,
@@ -235,13 +323,10 @@ export function addThinkingEntry(message = 'AI 思考中...') {
235
323
  });
236
324
  }
237
325
 
238
- /**
239
- * 移除思考中條目
240
- */
241
326
  export function removeThinkingEntry() {
242
327
  const body = document.getElementById('conversationBody');
243
328
  if (!body) return;
244
-
329
+
245
330
  const thinkingEntries = body.querySelectorAll('.entry-thinking');
246
331
  thinkingEntries.forEach(entry => entry.remove());
247
332
  }
@@ -38,6 +38,7 @@ import {
38
38
  showConversationPanel,
39
39
  hideConversationPanel,
40
40
  addConversationEntry,
41
+ addDebugEntry,
41
42
  clearConversationPanel,
42
43
  updateConversationMode,
43
44
  updateConversationTitle,
@@ -170,10 +171,18 @@ export async function generateAIReply() {
170
171
  const data = await response.json();
171
172
  removeThinkingEntry();
172
173
 
174
+ if (data.debug) {
175
+ addDebugEntry(data.debug, {
176
+ title: "Debug 資訊",
177
+ collapsed: true,
178
+ timestamp: Date.now(),
179
+ badge: data.debug.elapsedMs ? `${data.debug.elapsedMs}ms` : undefined,
180
+ });
181
+ }
182
+
173
183
  if (data.success) {
174
184
  updateConversationMode(data.mode, data.cliTool);
175
185
 
176
- // 如果有 fallback 原因,顯示通知
177
186
  if (data.fallbackReason) {
178
187
  showToast("warning", "模式切換", data.fallbackReason);
179
188
  }
@@ -199,7 +208,6 @@ export async function generateAIReply() {
199
208
  document.getElementById("feedbackText").value = finalReply;
200
209
  updateCharCount();
201
210
 
202
- // 如果是 fallback,badge 顯示不同的樣式
203
211
  let badge = data.mode === "cli" ? `CLI (${data.cliTool})` : "API";
204
212
  if (data.fallbackReason) {
205
213
  badge = "API (fallback)";
@@ -905,6 +913,15 @@ export async function generateAIReplyWithTools() {
905
913
  const data = await response.json();
906
914
  removeThinkingEntry();
907
915
 
916
+ if (data.debug) {
917
+ addDebugEntry(data.debug, {
918
+ title: `Debug (第 ${round} 輪)`,
919
+ collapsed: true,
920
+ timestamp: Date.now(),
921
+ badge: data.debug.elapsedMs ? `${data.debug.elapsedMs}ms` : undefined,
922
+ });
923
+ }
924
+
908
925
  if (!data.success) {
909
926
  addConversationEntry(ConversationEntryType.ERROR, data.error || "AI 回覆失敗", {
910
927
  title: "錯誤",
@@ -917,12 +934,10 @@ export async function generateAIReplyWithTools() {
917
934
 
918
935
  updateConversationMode(data.mode, data.cliTool);
919
936
 
920
- // 如果有 fallback 原因,顯示通知
921
937
  if (data.fallbackReason) {
922
938
  showToast("warning", "模式切換", data.fallbackReason);
923
939
  }
924
940
 
925
- // 如果是 fallback,badge 顯示不同的樣式
926
941
  let badgeTools1 = data.mode === "cli" ? `CLI (${data.cliTool})` : "API";
927
942
  if (data.fallbackReason) {
928
943
  badgeTools1 = "API (fallback)";
@@ -1092,10 +1107,18 @@ export async function triggerAutoAIReply() {
1092
1107
  const data = await response.json();
1093
1108
  removeThinkingEntry();
1094
1109
 
1110
+ if (data.debug) {
1111
+ addDebugEntry(data.debug, {
1112
+ title: "Debug 資訊 (自動回覆)",
1113
+ collapsed: true,
1114
+ timestamp: Date.now(),
1115
+ badge: data.debug.elapsedMs ? `${data.debug.elapsedMs}ms` : undefined,
1116
+ });
1117
+ }
1118
+
1095
1119
  if (data.success) {
1096
1120
  updateConversationMode(data.mode, data.cliTool);
1097
1121
 
1098
- // 如果有 fallback 原因,顯示通知
1099
1122
  if (data.fallbackReason) {
1100
1123
  showToast("warning", "模式切換", data.fallbackReason);
1101
1124
  }
@@ -1108,7 +1131,6 @@ export async function triggerAutoAIReply() {
1108
1131
  finalReply = "以下為我的回覆:\n" + data.reply;
1109
1132
  }
1110
1133
 
1111
- // 如果是 fallback,badge 顯示不同的樣式
1112
1134
  let badgeAuto1 = data.mode === "cli" ? `CLI (${data.cliTool})` : "API";
1113
1135
  if (data.fallbackReason) {
1114
1136
  badgeAuto1 = "API (fallback)";
@@ -1224,6 +1246,15 @@ export async function triggerAutoAIReply() {
1224
1246
  const data = await response.json();
1225
1247
  removeThinkingEntry();
1226
1248
 
1249
+ if (data.debug) {
1250
+ addDebugEntry(data.debug, {
1251
+ title: `Debug (自動第 ${round} 輪)`,
1252
+ collapsed: true,
1253
+ timestamp: Date.now(),
1254
+ badge: data.debug.elapsedMs ? `${data.debug.elapsedMs}ms` : undefined,
1255
+ });
1256
+ }
1257
+
1227
1258
  if (!data.success) {
1228
1259
  addConversationEntry(ConversationEntryType.ERROR, data.error || "AI 回覆失敗", {
1229
1260
  title: "錯誤",
@@ -1236,12 +1267,10 @@ export async function triggerAutoAIReply() {
1236
1267
 
1237
1268
  updateConversationMode(data.mode, data.cliTool);
1238
1269
 
1239
- // 如果有 fallback 原因,顯示通知
1240
1270
  if (data.fallbackReason) {
1241
1271
  showToast("warning", "模式切換", data.fallbackReason);
1242
1272
  }
1243
1273
 
1244
- // 如果是 fallback,badge 顯示不同的樣式
1245
1274
  let badgeAuto2 = data.mode === "cli" ? `CLI (${data.cliTool})` : "API";
1246
1275
  if (data.fallbackReason) {
1247
1276
  badgeAuto2 = "API (fallback)";
@@ -2075,6 +2075,112 @@ textarea.form-control {
2075
2075
  border-left-color: var(--accent-red);
2076
2076
  }
2077
2077
 
2078
+ .conversation-entry.entry-debug {
2079
+ border-left-color: var(--accent-orange, #f97316);
2080
+ }
2081
+
2082
+ .conversation-header-right {
2083
+ display: flex;
2084
+ align-items: center;
2085
+ gap: var(--spacing-sm);
2086
+ }
2087
+
2088
+ .debug-toggle {
2089
+ display: flex;
2090
+ align-items: center;
2091
+ gap: 4px;
2092
+ cursor: pointer;
2093
+ font-size: 14px;
2094
+ user-select: none;
2095
+ opacity: 0.6;
2096
+ transition: opacity 0.2s;
2097
+ }
2098
+
2099
+ .debug-toggle:hover {
2100
+ opacity: 1;
2101
+ }
2102
+
2103
+ .debug-toggle input[type="checkbox"] {
2104
+ width: 14px;
2105
+ height: 14px;
2106
+ cursor: pointer;
2107
+ }
2108
+
2109
+ .debug-info-grid {
2110
+ display: grid;
2111
+ grid-template-columns: 1fr 1fr;
2112
+ gap: 4px 16px;
2113
+ margin-bottom: 8px;
2114
+ }
2115
+
2116
+ .debug-row {
2117
+ display: flex;
2118
+ justify-content: space-between;
2119
+ padding: 3px 8px;
2120
+ border-radius: 4px;
2121
+ background: var(--bg-primary);
2122
+ font-size: 12px;
2123
+ }
2124
+
2125
+ .debug-label {
2126
+ color: var(--text-secondary);
2127
+ font-weight: 500;
2128
+ }
2129
+
2130
+ .debug-value {
2131
+ color: var(--text-primary);
2132
+ font-family: "Consolas", "Monaco", monospace;
2133
+ }
2134
+
2135
+ .debug-sections {
2136
+ margin-top: 8px;
2137
+ }
2138
+
2139
+ .debug-section-title {
2140
+ font-size: 12px;
2141
+ font-weight: 600;
2142
+ color: var(--text-secondary);
2143
+ margin-bottom: 4px;
2144
+ }
2145
+
2146
+ .debug-table {
2147
+ width: 100%;
2148
+ border-collapse: collapse;
2149
+ font-size: 12px;
2150
+ font-family: "Consolas", "Monaco", monospace;
2151
+ }
2152
+
2153
+ .debug-table th,
2154
+ .debug-table td {
2155
+ padding: 4px 8px;
2156
+ border: 1px solid var(--border-color);
2157
+ text-align: left;
2158
+ }
2159
+
2160
+ .debug-table th {
2161
+ background: var(--bg-hover);
2162
+ color: var(--text-secondary);
2163
+ font-weight: 600;
2164
+ }
2165
+
2166
+ .debug-table td {
2167
+ color: var(--text-primary);
2168
+ }
2169
+
2170
+ .debug-error {
2171
+ margin-top: 8px;
2172
+ padding: 6px 10px;
2173
+ background: rgba(239, 68, 68, 0.1);
2174
+ border: 1px solid rgba(239, 68, 68, 0.3);
2175
+ border-radius: 4px;
2176
+ font-size: 12px;
2177
+ color: var(--accent-red);
2178
+ }
2179
+
2180
+ .debug-body {
2181
+ font-size: 12px;
2182
+ }
2183
+
2078
2184
  .entry-summary {
2079
2185
  padding: var(--spacing-sm) var(--spacing-md);
2080
2186
  cursor: pointer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hirohsu/user-web-feedback",
3
- "version": "2.8.18",
3
+ "version": "2.8.19",
4
4
  "description": "基於Node.js的MCP回饋收集器 - 支持AI工作彙報和用戶回饋收集",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {