@hirohsu/user-web-feedback 2.8.18 → 2.8.20

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
@@ -78231,11 +78231,11 @@ var init_prompt_aggregator = __esm({
78231
78231
  init_logger();
78232
78232
  COMPONENT_NAME_MAP = {
78233
78233
  "system_prompt": "SystemPrompt",
78234
- "mcp_tools": "MCPToolsPrompt",
78234
+ "mcp_tools": "MCPTools",
78235
78235
  "mcp_tools_detailed": "MCPToolsDetailed",
78236
78236
  "user_context": "UserContext",
78237
78237
  "tool_results": "ToolResults",
78238
- "closing": "ClosingPrompt"
78238
+ "closing": "Closing"
78239
78239
  };
78240
78240
  PromptAggregator = class _PromptAggregator {
78241
78241
  static instance;
@@ -78274,9 +78274,10 @@ var init_prompt_aggregator = __esm({
78274
78274
  const configs = this.getPromptConfigsWithDefaults();
78275
78275
  const isFirstCall = context.isFirstCall !== false;
78276
78276
  const configuredComponents = this.components.map((component) => {
78277
- const configId = Object.entries(COMPONENT_NAME_MAP).find(
78277
+ const configEntry = Object.entries(COMPONENT_NAME_MAP).find(
78278
78278
  ([, name]) => name === component.getName()
78279
- )?.[0];
78279
+ );
78280
+ const configId = configEntry?.[0];
78280
78281
  const config2 = configId ? configs.find((c) => c.id === configId) : null;
78281
78282
  const order = config2 ? isFirstCall ? config2.firstOrder : config2.secondOrder : component.getOrder();
78282
78283
  const enabled = config2 ? config2.enabled : true;
@@ -78376,6 +78377,15 @@ var init_prompt_aggregator = __esm({
78376
78377
  getComponentNames() {
78377
78378
  return this.components.map((c) => c.getName());
78378
78379
  }
78380
+ getPromptConfigsForDebug() {
78381
+ const configs = this.getPromptConfigsWithDefaults();
78382
+ return configs.map((c) => ({
78383
+ id: c.id,
78384
+ enabled: c.enabled,
78385
+ firstOrder: c.firstOrder,
78386
+ secondOrder: c.secondOrder
78387
+ }));
78388
+ }
78379
78389
  getPromptConfigsWithDefaults() {
78380
78390
  try {
78381
78391
  const configs = getPromptConfigs();
@@ -78478,61 +78488,6 @@ function formatToolResults(results) {
78478
78488
  }
78479
78489
  return lines.join("\n");
78480
78490
  }
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
78491
  var init_mcp_tool_parser = __esm({
78537
78492
  "src/utils/mcp-tool-parser.ts"() {
78538
78493
  "use strict";
@@ -78885,131 +78840,91 @@ async function generateCLIReply(request, cliSettings) {
78885
78840
  }
78886
78841
  }
78887
78842
  async function generateAPIReply(request) {
78843
+ const startTime = Date.now();
78888
78844
  try {
78889
78845
  const cacheKey = `${request.aiMessage}:${request.userContext || ""}`;
78890
78846
  if (!request.toolResults) {
78891
78847
  const cached2 = cache.get(cacheKey);
78892
78848
  if (cached2 && Date.now() - cached2.timestamp < CACHE_TTL2) {
78893
78849
  logger.debug("[AI Service] \u4F7F\u7528\u5FEB\u53D6\u56DE\u8986");
78894
- return {
78895
- success: true,
78896
- reply: cached2.reply
78897
- };
78850
+ return { success: true, reply: cached2.reply };
78898
78851
  }
78899
78852
  }
78900
78853
  logger.debug("[AI Service] \u7372\u53D6 AI \u8A2D\u5B9A");
78901
78854
  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
78855
  if (!settings || !settings.apiKey || settings.apiKey === "YOUR_API_KEY_HERE") {
78908
78856
  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
- };
78857
+ return { success: false, error: "\u8ACB\u5148\u5728\u8A2D\u5B9A\u4E2D\u914D\u7F6E AI API Key" };
78913
78858
  }
78914
- let mcpToolsPrompt = "";
78859
+ const aggregator = getPromptAggregator();
78860
+ const cliSettings = getCLISettings();
78861
+ let mcpTools = [];
78915
78862
  if (request.includeMCPTools) {
78916
- logger.debug("[AI Service] \u958B\u59CB\u7372\u53D6 MCP \u5DE5\u5177");
78917
78863
  try {
78918
78864
  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);
78865
+ mcpTools = allTools.map((t) => ({
78866
+ name: t.name,
78867
+ description: t.description,
78868
+ inputSchema: t.inputSchema
78869
+ }));
78870
+ } catch {
78871
+ logger.warn("[AI Service] \u7121\u6CD5\u7372\u53D6 MCP \u5DE5\u5177");
78949
78872
  }
78950
78873
  }
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
78874
+ const context = aggregator.buildContextSync(request, settings, cliSettings, mcpTools);
78875
+ context.mode = "api";
78876
+ const aggregated = aggregator.aggregate(context);
78877
+ const promptSent = aggregated.fullPrompt;
78878
+ logger.debug("[AI Service] PromptAggregator \u69CB\u5EFA\u5B8C\u6210", {
78879
+ componentCount: aggregated.sections.length,
78880
+ sections: aggregated.sections.map((s) => `${s.name}(order:${s.order})`),
78881
+ totalLength: promptSent.length,
78882
+ tokenEstimate: aggregated.metadata.tokenEstimate
78956
78883
  });
78957
- const promptSent = buildPrompt(
78958
- settings.systemPrompt,
78959
- request.aiMessage,
78960
- request.userContext,
78961
- mcpToolsPrompt,
78962
- request.toolResults
78963
- );
78964
- const reply = await generateWithRetry(
78884
+ const reply = await generateWithRetryFromPrompt(
78965
78885
  settings.apiKey,
78966
78886
  settings.model,
78967
- settings.systemPrompt,
78968
- request.aiMessage,
78969
- request.userContext,
78887
+ promptSent,
78970
78888
  settings.temperature,
78971
- settings.maxTokens,
78972
- 0,
78973
- mcpToolsPrompt,
78974
- request.toolResults
78889
+ settings.maxTokens
78975
78890
  );
78976
- logger.debug("[AI Service] \u56DE\u8986\u751F\u6210\u5B8C\u6210", {
78977
- replyLength: reply.length
78978
- });
78891
+ const elapsedMs = Date.now() - startTime;
78979
78892
  if (!request.toolResults) {
78980
- cache.set(cacheKey, {
78981
- reply,
78982
- timestamp: Date.now()
78983
- });
78893
+ cache.set(cacheKey, { reply, timestamp: Date.now() });
78984
78894
  cleanExpiredCache();
78985
78895
  }
78986
- logger.debug("[AI Service] AI \u56DE\u8986\u8ACB\u6C42\u8655\u7406\u5B8C\u6210");
78896
+ const promptConfigs = aggregator.getPromptConfigsForDebug();
78987
78897
  return {
78988
78898
  success: true,
78989
78899
  reply,
78990
78900
  promptSent,
78991
- mode: "api"
78901
+ mode: "api",
78902
+ debug: {
78903
+ sections: aggregated.sections.map((s) => ({ name: s.name, order: s.order, length: s.content.length })),
78904
+ tokenEstimate: aggregated.metadata.tokenEstimate,
78905
+ totalPromptLength: promptSent.length,
78906
+ elapsedMs,
78907
+ model: settings.model,
78908
+ temperature: settings.temperature,
78909
+ maxTokens: settings.maxTokens,
78910
+ mcpToolsCount: mcpTools.length,
78911
+ componentCount: aggregated.sections.length,
78912
+ promptConfigs
78913
+ }
78992
78914
  };
78993
78915
  } catch (error2) {
78916
+ const elapsedMs = Date.now() - startTime;
78994
78917
  logger.error("[AI Service] AI service error:", error2);
78995
78918
  return {
78996
78919
  success: false,
78997
78920
  error: error2 instanceof Error ? error2.message : "\u672A\u77E5\u932F\u8AA4",
78998
- mode: "api"
78921
+ mode: "api",
78922
+ debug: { elapsedMs }
78999
78923
  };
79000
78924
  }
79001
78925
  }
79002
- async function generateWithRetry(apiKey, model, systemPrompt, aiMessage, userContext, temperature, maxTokens, retryCount = 0, mcpToolsPrompt = "", toolResults) {
78926
+ async function generateWithRetryFromPrompt(apiKey, model, prompt, temperature, maxTokens, retryCount = 0) {
79003
78927
  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
78928
  const genAI = new GoogleGenerativeAI(apiKey);
79014
78929
  const generativeModel = genAI.getGenerativeModel({
79015
78930
  model,
@@ -79018,45 +78933,20 @@ async function generateWithRetry(apiKey, model, systemPrompt, aiMessage, userCon
79018
78933
  maxOutputTokens: maxTokens ?? 1e3
79019
78934
  }
79020
78935
  });
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");
78936
+ logger.debug("[AI Service] \u958B\u59CB\u8ABF\u7528 API (PromptAggregator prompt)", { promptLength: prompt.length });
79027
78937
  const result = await generativeModel.generateContent(prompt);
79028
- logger.debug("[AI Service] API \u8ABF\u7528\u5B8C\u6210");
79029
78938
  const response = await result.response;
79030
78939
  const text = response.text();
79031
78940
  if (!text) {
79032
78941
  throw new Error("AI \u56DE\u8986\u70BA\u7A7A");
79033
78942
  }
79034
- logger.debug("[AI Service] \u56DE\u8986\u6587\u5B57\u7372\u53D6\u6210\u529F", {
79035
- textLength: text.length
79036
- });
79037
78943
  return text;
79038
78944
  } 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
78945
  if (error2 instanceof Error) {
79044
78946
  if (error2.message.includes("429") || error2.message.includes("quota")) {
79045
78947
  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
- );
78948
+ await sleep(RETRY_DELAYS[retryCount] || 4e3);
78949
+ return generateWithRetryFromPrompt(apiKey, model, prompt, temperature, maxTokens, retryCount + 1);
79060
78950
  }
79061
78951
  throw new Error("API \u914D\u984D\u5DF2\u7528\u76E1\u6216\u901F\u7387\u9650\u5236\uFF0C\u8ACB\u7A0D\u5F8C\u518D\u8A66");
79062
78952
  }
@@ -79064,51 +78954,13 @@ async function generateWithRetry(apiKey, model, systemPrompt, aiMessage, userCon
79064
78954
  throw new Error("API Key \u7121\u6548\uFF0C\u8ACB\u6AA2\u67E5\u8A2D\u5B9A");
79065
78955
  }
79066
78956
  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
- );
78957
+ await sleep(RETRY_DELAYS[retryCount] || 4e3);
78958
+ return generateWithRetryFromPrompt(apiKey, model, prompt, temperature, maxTokens, retryCount + 1);
79081
78959
  }
79082
78960
  }
79083
78961
  throw error2;
79084
78962
  }
79085
78963
  }
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
78964
  function sleep(ms) {
79113
78965
  return new Promise((resolve2) => setTimeout(resolve2, ms));
79114
78966
  }
@@ -79208,7 +79060,6 @@ var init_ai_service = __esm({
79208
79060
  init_cli_executor();
79209
79061
  init_cli_detector();
79210
79062
  init_prompt_aggregator2();
79211
- init_mcp_tool_parser();
79212
79063
  MAX_RETRIES = 3;
79213
79064
  RETRY_DELAYS = [1e3, 2e3, 4e3];
79214
79065
  cache = /* @__PURE__ */ new Map();
package/dist/index.cjs CHANGED
@@ -75118,11 +75118,11 @@ var init_prompt_aggregator = __esm({
75118
75118
  init_logger();
75119
75119
  COMPONENT_NAME_MAP = {
75120
75120
  "system_prompt": "SystemPrompt",
75121
- "mcp_tools": "MCPToolsPrompt",
75121
+ "mcp_tools": "MCPTools",
75122
75122
  "mcp_tools_detailed": "MCPToolsDetailed",
75123
75123
  "user_context": "UserContext",
75124
75124
  "tool_results": "ToolResults",
75125
- "closing": "ClosingPrompt"
75125
+ "closing": "Closing"
75126
75126
  };
75127
75127
  PromptAggregator = class _PromptAggregator {
75128
75128
  static instance;
@@ -75161,9 +75161,10 @@ var init_prompt_aggregator = __esm({
75161
75161
  const configs = this.getPromptConfigsWithDefaults();
75162
75162
  const isFirstCall = context.isFirstCall !== false;
75163
75163
  const configuredComponents = this.components.map((component) => {
75164
- const configId = Object.entries(COMPONENT_NAME_MAP).find(
75164
+ const configEntry = Object.entries(COMPONENT_NAME_MAP).find(
75165
75165
  ([, name]) => name === component.getName()
75166
- )?.[0];
75166
+ );
75167
+ const configId = configEntry?.[0];
75167
75168
  const config2 = configId ? configs.find((c) => c.id === configId) : null;
75168
75169
  const order = config2 ? isFirstCall ? config2.firstOrder : config2.secondOrder : component.getOrder();
75169
75170
  const enabled = config2 ? config2.enabled : true;
@@ -75263,6 +75264,15 @@ var init_prompt_aggregator = __esm({
75263
75264
  getComponentNames() {
75264
75265
  return this.components.map((c) => c.getName());
75265
75266
  }
75267
+ getPromptConfigsForDebug() {
75268
+ const configs = this.getPromptConfigsWithDefaults();
75269
+ return configs.map((c) => ({
75270
+ id: c.id,
75271
+ enabled: c.enabled,
75272
+ firstOrder: c.firstOrder,
75273
+ secondOrder: c.secondOrder
75274
+ }));
75275
+ }
75266
75276
  getPromptConfigsWithDefaults() {
75267
75277
  try {
75268
75278
  const configs = getPromptConfigs();
@@ -75365,61 +75375,6 @@ function formatToolResults(results) {
75365
75375
  }
75366
75376
  return lines.join("\n");
75367
75377
  }
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
75378
  var init_mcp_tool_parser = __esm({
75424
75379
  "src/utils/mcp-tool-parser.ts"() {
75425
75380
  "use strict";
@@ -75772,131 +75727,91 @@ async function generateCLIReply(request, cliSettings) {
75772
75727
  }
75773
75728
  }
75774
75729
  async function generateAPIReply(request) {
75730
+ const startTime = Date.now();
75775
75731
  try {
75776
75732
  const cacheKey = `${request.aiMessage}:${request.userContext || ""}`;
75777
75733
  if (!request.toolResults) {
75778
75734
  const cached2 = cache.get(cacheKey);
75779
75735
  if (cached2 && Date.now() - cached2.timestamp < CACHE_TTL2) {
75780
75736
  logger.debug("[AI Service] \u4F7F\u7528\u5FEB\u53D6\u56DE\u8986");
75781
- return {
75782
- success: true,
75783
- reply: cached2.reply
75784
- };
75737
+ return { success: true, reply: cached2.reply };
75785
75738
  }
75786
75739
  }
75787
75740
  logger.debug("[AI Service] \u7372\u53D6 AI \u8A2D\u5B9A");
75788
75741
  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
75742
  if (!settings || !settings.apiKey || settings.apiKey === "YOUR_API_KEY_HERE") {
75795
75743
  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
- };
75744
+ return { success: false, error: "\u8ACB\u5148\u5728\u8A2D\u5B9A\u4E2D\u914D\u7F6E AI API Key" };
75800
75745
  }
75801
- let mcpToolsPrompt = "";
75746
+ const aggregator = getPromptAggregator();
75747
+ const cliSettings = getCLISettings();
75748
+ let mcpTools = [];
75802
75749
  if (request.includeMCPTools) {
75803
- logger.debug("[AI Service] \u958B\u59CB\u7372\u53D6 MCP \u5DE5\u5177");
75804
75750
  try {
75805
75751
  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);
75752
+ mcpTools = allTools.map((t) => ({
75753
+ name: t.name,
75754
+ description: t.description,
75755
+ inputSchema: t.inputSchema
75756
+ }));
75757
+ } catch {
75758
+ logger.warn("[AI Service] \u7121\u6CD5\u7372\u53D6 MCP \u5DE5\u5177");
75836
75759
  }
75837
75760
  }
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
75761
+ const context = aggregator.buildContextSync(request, settings, cliSettings, mcpTools);
75762
+ context.mode = "api";
75763
+ const aggregated = aggregator.aggregate(context);
75764
+ const promptSent = aggregated.fullPrompt;
75765
+ logger.debug("[AI Service] PromptAggregator \u69CB\u5EFA\u5B8C\u6210", {
75766
+ componentCount: aggregated.sections.length,
75767
+ sections: aggregated.sections.map((s) => `${s.name}(order:${s.order})`),
75768
+ totalLength: promptSent.length,
75769
+ tokenEstimate: aggregated.metadata.tokenEstimate
75843
75770
  });
75844
- const promptSent = buildPrompt(
75845
- settings.systemPrompt,
75846
- request.aiMessage,
75847
- request.userContext,
75848
- mcpToolsPrompt,
75849
- request.toolResults
75850
- );
75851
- const reply = await generateWithRetry(
75771
+ const reply = await generateWithRetryFromPrompt(
75852
75772
  settings.apiKey,
75853
75773
  settings.model,
75854
- settings.systemPrompt,
75855
- request.aiMessage,
75856
- request.userContext,
75774
+ promptSent,
75857
75775
  settings.temperature,
75858
- settings.maxTokens,
75859
- 0,
75860
- mcpToolsPrompt,
75861
- request.toolResults
75776
+ settings.maxTokens
75862
75777
  );
75863
- logger.debug("[AI Service] \u56DE\u8986\u751F\u6210\u5B8C\u6210", {
75864
- replyLength: reply.length
75865
- });
75778
+ const elapsedMs = Date.now() - startTime;
75866
75779
  if (!request.toolResults) {
75867
- cache.set(cacheKey, {
75868
- reply,
75869
- timestamp: Date.now()
75870
- });
75780
+ cache.set(cacheKey, { reply, timestamp: Date.now() });
75871
75781
  cleanExpiredCache();
75872
75782
  }
75873
- logger.debug("[AI Service] AI \u56DE\u8986\u8ACB\u6C42\u8655\u7406\u5B8C\u6210");
75783
+ const promptConfigs = aggregator.getPromptConfigsForDebug();
75874
75784
  return {
75875
75785
  success: true,
75876
75786
  reply,
75877
75787
  promptSent,
75878
- mode: "api"
75788
+ mode: "api",
75789
+ debug: {
75790
+ sections: aggregated.sections.map((s) => ({ name: s.name, order: s.order, length: s.content.length })),
75791
+ tokenEstimate: aggregated.metadata.tokenEstimate,
75792
+ totalPromptLength: promptSent.length,
75793
+ elapsedMs,
75794
+ model: settings.model,
75795
+ temperature: settings.temperature,
75796
+ maxTokens: settings.maxTokens,
75797
+ mcpToolsCount: mcpTools.length,
75798
+ componentCount: aggregated.sections.length,
75799
+ promptConfigs
75800
+ }
75879
75801
  };
75880
75802
  } catch (error2) {
75803
+ const elapsedMs = Date.now() - startTime;
75881
75804
  logger.error("[AI Service] AI service error:", error2);
75882
75805
  return {
75883
75806
  success: false,
75884
75807
  error: error2 instanceof Error ? error2.message : "\u672A\u77E5\u932F\u8AA4",
75885
- mode: "api"
75808
+ mode: "api",
75809
+ debug: { elapsedMs }
75886
75810
  };
75887
75811
  }
75888
75812
  }
75889
- async function generateWithRetry(apiKey, model, systemPrompt, aiMessage, userContext, temperature, maxTokens, retryCount = 0, mcpToolsPrompt = "", toolResults) {
75813
+ async function generateWithRetryFromPrompt(apiKey, model, prompt, temperature, maxTokens, retryCount = 0) {
75890
75814
  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
75815
  const genAI = new GoogleGenerativeAI(apiKey);
75901
75816
  const generativeModel = genAI.getGenerativeModel({
75902
75817
  model,
@@ -75905,45 +75820,20 @@ async function generateWithRetry(apiKey, model, systemPrompt, aiMessage, userCon
75905
75820
  maxOutputTokens: maxTokens ?? 1e3
75906
75821
  }
75907
75822
  });
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");
75823
+ logger.debug("[AI Service] \u958B\u59CB\u8ABF\u7528 API (PromptAggregator prompt)", { promptLength: prompt.length });
75914
75824
  const result = await generativeModel.generateContent(prompt);
75915
- logger.debug("[AI Service] API \u8ABF\u7528\u5B8C\u6210");
75916
75825
  const response = await result.response;
75917
75826
  const text = response.text();
75918
75827
  if (!text) {
75919
75828
  throw new Error("AI \u56DE\u8986\u70BA\u7A7A");
75920
75829
  }
75921
- logger.debug("[AI Service] \u56DE\u8986\u6587\u5B57\u7372\u53D6\u6210\u529F", {
75922
- textLength: text.length
75923
- });
75924
75830
  return text;
75925
75831
  } 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
75832
  if (error2 instanceof Error) {
75931
75833
  if (error2.message.includes("429") || error2.message.includes("quota")) {
75932
75834
  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
- );
75835
+ await sleep(RETRY_DELAYS[retryCount] || 4e3);
75836
+ return generateWithRetryFromPrompt(apiKey, model, prompt, temperature, maxTokens, retryCount + 1);
75947
75837
  }
75948
75838
  throw new Error("API \u914D\u984D\u5DF2\u7528\u76E1\u6216\u901F\u7387\u9650\u5236\uFF0C\u8ACB\u7A0D\u5F8C\u518D\u8A66");
75949
75839
  }
@@ -75951,51 +75841,13 @@ async function generateWithRetry(apiKey, model, systemPrompt, aiMessage, userCon
75951
75841
  throw new Error("API Key \u7121\u6548\uFF0C\u8ACB\u6AA2\u67E5\u8A2D\u5B9A");
75952
75842
  }
75953
75843
  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
- );
75844
+ await sleep(RETRY_DELAYS[retryCount] || 4e3);
75845
+ return generateWithRetryFromPrompt(apiKey, model, prompt, temperature, maxTokens, retryCount + 1);
75968
75846
  }
75969
75847
  }
75970
75848
  throw error2;
75971
75849
  }
75972
75850
  }
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
75851
  function sleep(ms) {
76000
75852
  return new Promise((resolve) => setTimeout(resolve, ms));
76001
75853
  }
@@ -76095,7 +75947,6 @@ var init_ai_service = __esm({
76095
75947
  init_cli_executor();
76096
75948
  init_cli_detector();
76097
75949
  init_prompt_aggregator2();
76098
- init_mcp_tool_parser();
76099
75950
  MAX_RETRIES = 3;
76100
75951
  RETRY_DELAYS = [1e3, 2e3, 4e3];
76101
75952
  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.20",
4
4
  "description": "基於Node.js的MCP回饋收集器 - 支持AI工作彙報和用戶回饋收集",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {