@hirohsu/user-web-feedback 2.8.1 → 2.8.8

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/index.cjs CHANGED
@@ -20660,6 +20660,7 @@ __export(database_exports, {
20660
20660
  getMCPServerById: () => getMCPServerById,
20661
20661
  getPinnedPrompts: () => getPinnedPrompts,
20662
20662
  getPromptById: () => getPromptById,
20663
+ getPromptConfigs: () => getPromptConfigs,
20663
20664
  getRecentMCPServerErrors: () => getRecentMCPServerErrors,
20664
20665
  getSelfProbeSettings: () => getSelfProbeSettings,
20665
20666
  getToolEnableConfigs: () => getToolEnableConfigs,
@@ -20677,6 +20678,7 @@ __export(database_exports, {
20677
20678
  queryLogs: () => queryLogs,
20678
20679
  queryMCPServerLogs: () => queryMCPServerLogs,
20679
20680
  reorderPrompts: () => reorderPrompts,
20681
+ resetPromptConfigs: () => resetPromptConfigs,
20680
20682
  saveSelfProbeSettings: () => saveSelfProbeSettings,
20681
20683
  setToolEnabled: () => setToolEnabled,
20682
20684
  toggleMCPServerEnabled: () => toggleMCPServerEnabled,
@@ -20687,6 +20689,7 @@ __export(database_exports, {
20687
20689
  updateCLITerminalActivity: () => updateCLITerminalActivity,
20688
20690
  updateMCPServer: () => updateMCPServer,
20689
20691
  updatePrompt: () => updatePrompt,
20692
+ updatePromptConfigs: () => updatePromptConfigs,
20690
20693
  updateUserPreferences: () => updateUserPreferences
20691
20694
  });
20692
20695
  function hashPrompt(prompt) {
@@ -20984,6 +20987,21 @@ function createTables() {
20984
20987
  updated_at TEXT DEFAULT CURRENT_TIMESTAMP
20985
20988
  )
20986
20989
  `);
20990
+ db.exec(`
20991
+ CREATE TABLE IF NOT EXISTS prompt_configs (
20992
+ id TEXT PRIMARY KEY,
20993
+ name TEXT NOT NULL,
20994
+ display_name TEXT NOT NULL,
20995
+ content TEXT,
20996
+ first_order INTEGER DEFAULT 0,
20997
+ second_order INTEGER DEFAULT 0,
20998
+ enabled INTEGER DEFAULT 1,
20999
+ editable INTEGER DEFAULT 1,
21000
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
21001
+ updated_at TEXT DEFAULT CURRENT_TIMESTAMP
21002
+ )
21003
+ `);
21004
+ initDefaultPromptConfigs();
20987
21005
  }
20988
21006
  function initDefaultSettings() {
20989
21007
  if (!db) throw new Error("Database not initialized");
@@ -22378,7 +22396,104 @@ function saveSelfProbeSettings(settings) {
22378
22396
  }
22379
22397
  return getSelfProbeSettings();
22380
22398
  }
22381
- var import_better_sqlite3, import_path2, import_fs2, import_crypto2, DB_DIR, DB_PATH, db, SYSTEM_PROMPT_VERSIONS, CURRENT_PROMPT_VERSION;
22399
+ function initDefaultPromptConfigs() {
22400
+ const db2 = tryGetDb();
22401
+ if (!db2) return;
22402
+ const now = (/* @__PURE__ */ new Date()).toISOString();
22403
+ const existingIds = db2.prepare("SELECT id FROM prompt_configs").all().map((row) => row.id);
22404
+ const stmt = db2.prepare(`
22405
+ INSERT INTO prompt_configs (id, name, display_name, content, first_order, second_order, enabled, editable, created_at, updated_at)
22406
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
22407
+ `);
22408
+ for (const config2 of DEFAULT_PROMPT_CONFIGS) {
22409
+ if (!existingIds.includes(config2.id)) {
22410
+ stmt.run(
22411
+ config2.id,
22412
+ config2.name,
22413
+ config2.displayName,
22414
+ config2.content,
22415
+ config2.firstOrder,
22416
+ config2.secondOrder,
22417
+ config2.enabled ? 1 : 0,
22418
+ config2.editable ? 1 : 0,
22419
+ now,
22420
+ now
22421
+ );
22422
+ logger.info(`Added missing prompt config: ${config2.id}`);
22423
+ }
22424
+ }
22425
+ }
22426
+ function getPromptConfigs() {
22427
+ const db2 = tryGetDb();
22428
+ if (!db2) return [];
22429
+ const rows = db2.prepare(`
22430
+ SELECT id, name, display_name as displayName, content,
22431
+ first_order as firstOrder, second_order as secondOrder,
22432
+ enabled, editable, created_at as createdAt, updated_at as updatedAt
22433
+ FROM prompt_configs
22434
+ ORDER BY first_order ASC
22435
+ `).all();
22436
+ const aiSettings = getAISettings();
22437
+ return rows.map((row) => {
22438
+ let content = row.content;
22439
+ if (row.id === "system_prompt" && !content && aiSettings.systemPrompt) {
22440
+ content = aiSettings.systemPrompt;
22441
+ } else if (row.id === "mcp_tools" && !content && aiSettings.mcpToolsPrompt) {
22442
+ content = aiSettings.mcpToolsPrompt;
22443
+ }
22444
+ return {
22445
+ ...row,
22446
+ content,
22447
+ enabled: row.enabled === 1,
22448
+ editable: row.editable === 1
22449
+ };
22450
+ });
22451
+ }
22452
+ function updatePromptConfigs(request) {
22453
+ const db2 = tryGetDb();
22454
+ if (!db2) return false;
22455
+ const now = (/* @__PURE__ */ new Date()).toISOString();
22456
+ for (const prompt of request.prompts) {
22457
+ const updates = [];
22458
+ const values = [];
22459
+ if (prompt.firstOrder !== void 0) {
22460
+ updates.push("first_order = ?");
22461
+ values.push(prompt.firstOrder);
22462
+ }
22463
+ if (prompt.secondOrder !== void 0) {
22464
+ updates.push("second_order = ?");
22465
+ values.push(prompt.secondOrder);
22466
+ }
22467
+ if (prompt.enabled !== void 0) {
22468
+ updates.push("enabled = ?");
22469
+ values.push(prompt.enabled ? 1 : 0);
22470
+ }
22471
+ if (prompt.content !== void 0) {
22472
+ updates.push("content = ?");
22473
+ values.push(prompt.content);
22474
+ if (prompt.id === "system_prompt") {
22475
+ db2.prepare("UPDATE ai_settings SET system_prompt = ?, updated_at = ? WHERE id = 1").run(prompt.content || "", now);
22476
+ } else if (prompt.id === "mcp_tools") {
22477
+ db2.prepare("UPDATE ai_settings SET mcp_tools_prompt = ?, updated_at = ? WHERE id = 1").run(prompt.content || "", now);
22478
+ }
22479
+ }
22480
+ if (updates.length > 0) {
22481
+ updates.push("updated_at = ?");
22482
+ values.push(now);
22483
+ values.push(prompt.id);
22484
+ db2.prepare(`UPDATE prompt_configs SET ${updates.join(", ")} WHERE id = ?`).run(...values);
22485
+ }
22486
+ }
22487
+ return true;
22488
+ }
22489
+ function resetPromptConfigs() {
22490
+ const db2 = tryGetDb();
22491
+ if (!db2) return [];
22492
+ db2.prepare("DELETE FROM prompt_configs").run();
22493
+ initDefaultPromptConfigs();
22494
+ return getPromptConfigs();
22495
+ }
22496
+ var import_better_sqlite3, import_path2, import_fs2, import_crypto2, DB_DIR, DB_PATH, db, SYSTEM_PROMPT_VERSIONS, CURRENT_PROMPT_VERSION, DEFAULT_PROMPT_CONFIGS;
22382
22497
  var init_database = __esm({
22383
22498
  "src/utils/database.ts"() {
22384
22499
  "use strict";
@@ -22425,6 +22540,14 @@ var init_database = __esm({
22425
22540
  \u4FDD\u6301\u56DE\u61C9\u7C21\u77ED\uFF082-3\u53E5\u8A71\uFF09\uFF0C\u9664\u975E\u9700\u8981\u66F4\u8A73\u7D30\u7684\u8AAA\u660E\u3002`
22426
22541
  };
22427
22542
  CURRENT_PROMPT_VERSION = "v2";
22543
+ DEFAULT_PROMPT_CONFIGS = [
22544
+ { id: "system_prompt", name: "System Prompt", displayName: "\u7CFB\u7D71\u63D0\u793A\u8A5E", content: null, firstOrder: 10, secondOrder: 10, enabled: true, editable: true },
22545
+ { id: "mcp_tools", name: "MCP Tools", displayName: "MCP \u5DE5\u5177\u8AAA\u660E", content: null, firstOrder: 20, secondOrder: 0, enabled: true, editable: true },
22546
+ { id: "mcp_tools_detailed", name: "MCP Tools Detailed", displayName: "MCP \u5DE5\u5177\u8A73\u7D30\u5217\u8868", content: null, firstOrder: 25, secondOrder: 15, enabled: true, editable: false },
22547
+ { id: "user_context", name: "User Context", displayName: "\u7528\u6236\u4E0A\u4E0B\u6587", content: null, firstOrder: 30, secondOrder: 20, enabled: true, editable: false },
22548
+ { id: "tool_results", name: "Tool Results", displayName: "\u5DE5\u5177\u57F7\u884C\u7D50\u679C", content: null, firstOrder: 0, secondOrder: 30, enabled: true, editable: false },
22549
+ { id: "closing", name: "Closing", displayName: "\u7D50\u5C3E\u63D0\u793A", content: null, firstOrder: 100, secondOrder: 100, enabled: true, editable: true }
22550
+ ];
22428
22551
  }
22429
22552
  });
22430
22553
 
@@ -22518,6 +22641,9 @@ var init_logger = __esm({
22518
22641
  this.flushInterval = setInterval(() => {
22519
22642
  this.flushToDatabase();
22520
22643
  }, this.FLUSH_INTERVAL_MS);
22644
+ if (this.flushInterval.unref) {
22645
+ this.flushInterval.unref();
22646
+ }
22521
22647
  process.on("beforeExit", () => {
22522
22648
  this.flushToDatabase();
22523
22649
  });
@@ -74680,6 +74806,107 @@ ${mcpPrompt}`;
74680
74806
  }
74681
74807
  });
74682
74808
 
74809
+ // src/utils/prompt-aggregator/components/mcp-tools-detailed.ts
74810
+ var MCPToolsDetailedComponent;
74811
+ var init_mcp_tools_detailed = __esm({
74812
+ "src/utils/prompt-aggregator/components/mcp-tools-detailed.ts"() {
74813
+ "use strict";
74814
+ init_cjs_shims();
74815
+ init_base_component();
74816
+ init_mcp_client_manager();
74817
+ MCPToolsDetailedComponent = class extends BasePromptComponent {
74818
+ constructor() {
74819
+ super("MCPToolsDetailed", 25);
74820
+ }
74821
+ build(context) {
74822
+ const { request } = context;
74823
+ if (!request.includeMCPTools) {
74824
+ return null;
74825
+ }
74826
+ try {
74827
+ const allTools = mcpClientManager.getAllTools();
74828
+ if (!allTools || allTools.length === 0) {
74829
+ return null;
74830
+ }
74831
+ let result = "## MCP \u5DE5\u5177\u8A73\u7D30\u4F7F\u7528\u8AAA\u660E\n\n";
74832
+ result += "\u4EE5\u4E0B\u662F\u6240\u6709\u53EF\u7528\u7684 MCP \u5DE5\u5177\u53CA\u5176\u8A73\u7D30\u4F7F\u7528\u65B9\u5F0F\uFF1A\n\n";
74833
+ for (const tool of allTools) {
74834
+ result += `### ${tool.name}
74835
+
74836
+ `;
74837
+ if (tool.description) {
74838
+ result += `**\u8AAA\u660E**: ${tool.description}
74839
+
74840
+ `;
74841
+ }
74842
+ if (tool.inputSchema) {
74843
+ result += "**\u53C3\u6578\u683C\u5F0F**:\n```json\n";
74844
+ result += JSON.stringify(tool.inputSchema, null, 2);
74845
+ result += "\n```\n\n";
74846
+ const schema = tool.inputSchema;
74847
+ if (schema.properties && typeof schema.properties === "object") {
74848
+ result += "**\u53C3\u6578\u8AAA\u660E**:\n";
74849
+ const props = schema.properties;
74850
+ const required2 = schema.required || [];
74851
+ for (const [propName, propDef] of Object.entries(props)) {
74852
+ const isRequired = required2.includes(propName);
74853
+ result += `- \`${propName}\``;
74854
+ if (propDef.type) result += ` (${propDef.type})`;
74855
+ if (isRequired) result += " **\u5FC5\u586B**";
74856
+ if (propDef.description) result += `: ${propDef.description}`;
74857
+ result += "\n";
74858
+ }
74859
+ result += "\n";
74860
+ }
74861
+ }
74862
+ result += "**\u8ABF\u7528\u7BC4\u4F8B**:\n```json\n";
74863
+ result += JSON.stringify({
74864
+ tool_calls: [{
74865
+ name: tool.name,
74866
+ arguments: this.generateExampleArgs(tool.inputSchema)
74867
+ }]
74868
+ }, null, 2);
74869
+ result += "\n```\n\n---\n\n";
74870
+ }
74871
+ return result;
74872
+ } catch {
74873
+ return null;
74874
+ }
74875
+ }
74876
+ generateExampleArgs(schema) {
74877
+ if (!schema || !schema.properties) {
74878
+ return {};
74879
+ }
74880
+ const result = {};
74881
+ const props = schema.properties;
74882
+ for (const [propName, propDef] of Object.entries(props)) {
74883
+ switch (propDef.type) {
74884
+ case "string":
74885
+ result[propName] = "<\u5B57\u4E32\u503C>";
74886
+ break;
74887
+ case "number":
74888
+ case "integer":
74889
+ result[propName] = 0;
74890
+ break;
74891
+ case "boolean":
74892
+ result[propName] = true;
74893
+ break;
74894
+ case "array":
74895
+ result[propName] = [];
74896
+ break;
74897
+ case "object":
74898
+ result[propName] = {};
74899
+ break;
74900
+ default:
74901
+ result[propName] = "<\u503C>";
74902
+ }
74903
+ }
74904
+ return result;
74905
+ }
74906
+ };
74907
+ }
74908
+ });
74909
+
74683
74910
  // src/utils/prompt-aggregator/components/user-context.ts
74684
74911
  var UserContextComponent;
74685
74912
  var init_user_context = __esm({
@@ -74792,6 +75019,7 @@ var init_components = __esm({
74792
75019
  init_system_prompt();
74793
75020
  init_pinned_prompts();
74794
75021
  init_mcp_tools();
75022
+ init_mcp_tools_detailed();
74795
75023
  init_user_context();
74796
75024
  init_tool_results();
74797
75025
  init_ai_message();
@@ -74803,7 +75031,7 @@ var init_components = __esm({
74803
75031
  function getPromptAggregator() {
74804
75032
  return PromptAggregator.getInstance();
74805
75033
  }
74806
- var PromptAggregator;
75034
+ var COMPONENT_NAME_MAP, PromptAggregator;
74807
75035
  var init_prompt_aggregator = __esm({
74808
75036
  "src/utils/prompt-aggregator/prompt-aggregator.ts"() {
74809
75037
  "use strict";
@@ -74812,6 +75040,14 @@ var init_prompt_aggregator = __esm({
74812
75040
  init_database();
74813
75041
  init_mcp_client_manager();
74814
75042
  init_logger();
75043
+ COMPONENT_NAME_MAP = {
75044
+ "system_prompt": "SystemPrompt",
75045
+ "mcp_tools": "MCPToolsPrompt",
75046
+ "mcp_tools_detailed": "MCPToolsDetailed",
75047
+ "user_context": "UserContext",
75048
+ "tool_results": "ToolResults",
75049
+ "closing": "ClosingPrompt"
75050
+ };
74815
75051
  PromptAggregator = class _PromptAggregator {
74816
75052
  static instance;
74817
75053
  components = [];
@@ -74830,6 +75066,7 @@ var init_prompt_aggregator = __esm({
74830
75066
  this.register(new SystemPromptComponent());
74831
75067
  this.register(new PinnedPromptsComponent());
74832
75068
  this.register(new MCPToolsPromptComponent());
75069
+ this.register(new MCPToolsDetailedComponent());
74833
75070
  this.register(new UserContextComponent());
74834
75071
  this.register(new ToolResultsComponent());
74835
75072
  this.register(new AIMessageComponent());
@@ -74845,14 +75082,21 @@ var init_prompt_aggregator = __esm({
74845
75082
  aggregate(context) {
74846
75083
  const sections = [];
74847
75084
  const promptParts = [];
74848
- for (const component of this.components) {
75085
+ const configs = this.getPromptConfigsWithDefaults();
75086
+ const isFirstCall = context.isFirstCall !== false;
75087
+ const configuredComponents = this.components.map((component) => {
75088
+ const configId = Object.entries(COMPONENT_NAME_MAP).find(
75089
+ ([, name]) => name === component.getName()
75090
+ )?.[0];
75091
+ const config2 = configId ? configs.find((c) => c.id === configId) : null;
75092
+ const order = config2 ? isFirstCall ? config2.firstOrder : config2.secondOrder : component.getOrder();
75093
+ const enabled = config2 ? config2.enabled : true;
75094
+ return { component, order, enabled };
75095
+ }).filter((item) => item.enabled && item.order > 0).sort((a, b) => a.order - b.order);
75096
+ for (const { component, order } of configuredComponents) {
74849
75097
  const content = component.build(context);
74850
75098
  if (content) {
74851
- sections.push({
74852
- name: component.getName(),
74853
- content,
74854
- order: component.getOrder()
74855
- });
75099
+ sections.push({ name: component.getName(), content, order });
74856
75100
  promptParts.push(content);
74857
75101
  }
74858
75102
  }
@@ -74943,6 +75187,21 @@ var init_prompt_aggregator = __esm({
74943
75187
  getComponentNames() {
74944
75188
  return this.components.map((c) => c.getName());
74945
75189
  }
75190
+ getPromptConfigsWithDefaults() {
75191
+ try {
75192
+ const configs = getPromptConfigs();
75193
+ if (configs.length > 0) return configs;
75194
+ } catch {
75195
+ logger.warn("[PromptAggregator] \u7121\u6CD5\u5F9E\u8CC7\u6599\u5EAB\u7372\u53D6\u63D0\u793A\u8A5E\u914D\u7F6E\uFF0C\u4F7F\u7528\u9810\u8A2D\u503C");
75196
+ }
75197
+ return [
75198
+ { id: "system_prompt", name: "System Prompt", displayName: "\u7CFB\u7D71\u63D0\u793A\u8A5E", content: null, firstOrder: 10, secondOrder: 10, enabled: true, editable: false, createdAt: "", updatedAt: "" },
75199
+ { id: "mcp_tools", name: "MCP Tools", displayName: "MCP \u5DE5\u5177\u8AAA\u660E", content: null, firstOrder: 20, secondOrder: 0, enabled: true, editable: true, createdAt: "", updatedAt: "" },
75200
+ { id: "user_context", name: "User Context", displayName: "\u7528\u6236\u4E0A\u4E0B\u6587", content: null, firstOrder: 30, secondOrder: 20, enabled: true, editable: true, createdAt: "", updatedAt: "" },
75201
+ { id: "tool_results", name: "Tool Results", displayName: "\u5DE5\u5177\u57F7\u884C\u7D50\u679C", content: null, firstOrder: 0, secondOrder: 30, enabled: true, editable: false, createdAt: "", updatedAt: "" },
75202
+ { id: "closing", name: "Closing", displayName: "\u7D50\u5C3E\u63D0\u793A", content: null, firstOrder: 40, secondOrder: 40, enabled: true, editable: true, createdAt: "", updatedAt: "" }
75203
+ ];
75204
+ }
74946
75205
  };
74947
75206
  }
74948
75207
  });
@@ -75675,8 +75934,25 @@ function cleanExpiredCache() {
75675
75934
  function clearAICache() {
75676
75935
  cache.clear();
75677
75936
  }
75678
- async function validateAPIKey(apiKey, model) {
75937
+ async function validateAPIKey(apiKey, model, apiUrl, openaiCompatible) {
75679
75938
  try {
75939
+ const provider = getProviderFromUrl(apiUrl);
75940
+ if (openaiCompatible || provider === "openai" || provider === "nvidia" || provider === "zai") {
75941
+ const OpenAI = (await import("openai")).default;
75942
+ const client = new OpenAI({
75943
+ apiKey,
75944
+ baseURL: apiUrl || "https://api.openai.com/v1"
75945
+ });
75946
+ const response2 = await client.chat.completions.create({
75947
+ model,
75948
+ messages: [{ role: "user", content: "Hello" }],
75949
+ max_tokens: 10
75950
+ });
75951
+ if (response2.choices && response2.choices.length > 0) {
75952
+ return { valid: true };
75953
+ }
75954
+ return { valid: false, error: "\u7121\u6CD5\u751F\u6210\u56DE\u61C9" };
75955
+ }
75680
75956
  const genAI = new GoogleGenerativeAI(apiKey);
75681
75957
  const generativeModel = genAI.getGenerativeModel({ model });
75682
75958
  const result = await generativeModel.generateContent("Hello");
@@ -75693,6 +75969,16 @@ async function validateAPIKey(apiKey, model) {
75693
75969
  };
75694
75970
  }
75695
75971
  }
75972
+ function getProviderFromUrl(apiUrl) {
75973
+ if (!apiUrl) return "google";
75974
+ const normalizedUrl = apiUrl.toLowerCase();
75975
+ if (normalizedUrl.includes("api.openai.com")) return "openai";
75976
+ if (normalizedUrl.includes("api.anthropic.com")) return "anthropic";
75977
+ if (normalizedUrl.includes("generativelanguage.googleapis.com")) return "google";
75978
+ if (normalizedUrl.includes("nvidia.com")) return "nvidia";
75979
+ if (normalizedUrl.includes("bigmodel.cn") || normalizedUrl.includes("z.ai")) return "zai";
75980
+ return "openai";
75981
+ }
75696
75982
  function estimateTokenCount(text) {
75697
75983
  const englishChars = (text.match(/[a-zA-Z0-9]/g) || []).length;
75698
75984
  const chineseChars = (text.match(/[\u4e00-\u9fa5]/g) || []).length;
@@ -90395,7 +90681,7 @@ var WebServer = class {
90395
90681
  this.app.post("/api/ai-settings/validate", async (req, res) => {
90396
90682
  const startTime = Date.now();
90397
90683
  try {
90398
- let { apiKey } = req.body;
90684
+ let { apiKey, apiUrl, openaiCompatible } = req.body;
90399
90685
  const { model } = req.body;
90400
90686
  if (!model) {
90401
90687
  const errorMsg = "\u6A21\u578B\u70BA\u5FC5\u586B\u6B04\u4F4D";
@@ -90415,6 +90701,7 @@ var WebServer = class {
90415
90701
  return;
90416
90702
  }
90417
90703
  let usingDatabaseKey = false;
90704
+ let usingDatabaseUrl = false;
90418
90705
  if (!apiKey) {
90419
90706
  const settings = getAISettings();
90420
90707
  if (!settings || !settings.apiKey || settings.apiKey === "YOUR_API_KEY_HERE") {
@@ -90437,13 +90724,18 @@ var WebServer = class {
90437
90724
  }
90438
90725
  apiKey = settings.apiKey;
90439
90726
  usingDatabaseKey = true;
90727
+ if (!apiUrl) {
90728
+ apiUrl = settings.apiUrl;
90729
+ usingDatabaseUrl = true;
90730
+ }
90440
90731
  logger.info("\u4F7F\u7528\u8CC7\u6599\u5EAB\u4E2D\u89E3\u5BC6\u7684 API Key \u9032\u884C\u9A57\u8B49");
90441
90732
  logger.debug(`\u89E3\u5BC6\u5F8C\u7684 API Key \u9577\u5EA6: ${apiKey.length}, \u524D\u7DB4: ${apiKey.substring(0, 3)}...`);
90442
90733
  } else {
90443
90734
  logger.info("\u4F7F\u7528\u65B0\u8F38\u5165\u7684 API Key \u9032\u884C\u9A57\u8B49");
90444
90735
  logger.debug(`\u65B0\u8F38\u5165\u7684 API Key \u9577\u5EA6: ${apiKey.length}, \u524D\u7DB4: ${apiKey.substring(0, 3)}...`);
90445
90736
  }
90446
- const result = await validateAPIKey(apiKey, model);
90737
+ logger.info(`\u9A57\u8B49\u4F7F\u7528 API URL: ${apiUrl} (\u8CC7\u6599\u5EAB: ${usingDatabaseUrl}), OpenAI \u76F8\u5BB9: ${openaiCompatible}`);
90738
+ const result = await validateAPIKey(apiKey, model, apiUrl, openaiCompatible);
90447
90739
  if (result.valid) {
90448
90740
  logger.info(`API Key \u9A57\u8B49\u6210\u529F (${usingDatabaseKey ? "\u8CC7\u6599\u5EAB" : "\u65B0\u8F38\u5165"})`);
90449
90741
  logAPIRequest({
@@ -90606,6 +90898,68 @@ var WebServer = class {
90606
90898
  });
90607
90899
  }
90608
90900
  });
90901
+ this.app.get("/api/settings/prompts", (req, res) => {
90902
+ try {
90903
+ const prompts = getPromptConfigs();
90904
+ res.json({ success: true, prompts });
90905
+ } catch (error2) {
90906
+ logger.error("\u7372\u53D6\u63D0\u793A\u8A5E\u914D\u7F6E\u5931\u6557:", error2);
90907
+ res.status(500).json({
90908
+ success: false,
90909
+ error: error2 instanceof Error ? error2.message : "\u7372\u53D6\u63D0\u793A\u8A5E\u914D\u7F6E\u5931\u6557"
90910
+ });
90911
+ }
90912
+ });
90913
+ this.app.put("/api/settings/prompts", (req, res) => {
90914
+ try {
90915
+ const { prompts } = req.body;
90916
+ if (!prompts || !Array.isArray(prompts)) {
90917
+ res.status(400).json({ success: false, error: "prompts \u5FC5\u9808\u662F\u9663\u5217" });
90918
+ return;
90919
+ }
90920
+ for (const prompt of prompts) {
90921
+ if (!prompt.id) {
90922
+ res.status(400).json({ success: false, error: "\u6BCF\u500B\u914D\u7F6E\u5FC5\u9808\u5305\u542B id" });
90923
+ return;
90924
+ }
90925
+ if (prompt.firstOrder !== void 0 && (prompt.firstOrder < 0 || prompt.firstOrder > 1e3)) {
90926
+ res.status(400).json({ success: false, error: "firstOrder \u5FC5\u9808\u5728 0-1000 \u4E4B\u9593" });
90927
+ return;
90928
+ }
90929
+ if (prompt.secondOrder !== void 0 && (prompt.secondOrder < 0 || prompt.secondOrder > 1e3)) {
90930
+ res.status(400).json({ success: false, error: "secondOrder \u5FC5\u9808\u5728 0-1000 \u4E4B\u9593" });
90931
+ return;
90932
+ }
90933
+ }
90934
+ const success = updatePromptConfigs({ prompts });
90935
+ if (success) {
90936
+ const updatedPrompts = getPromptConfigs();
90937
+ logger.info(`\u63D0\u793A\u8A5E\u914D\u7F6E\u5DF2\u66F4\u65B0: ${prompts.length} \u9805`);
90938
+ res.json({ success: true, prompts: updatedPrompts });
90939
+ } else {
90940
+ res.status(500).json({ success: false, error: "\u66F4\u65B0\u5931\u6557" });
90941
+ }
90942
+ } catch (error2) {
90943
+ logger.error("\u66F4\u65B0\u63D0\u793A\u8A5E\u914D\u7F6E\u5931\u6557:", error2);
90944
+ res.status(500).json({
90945
+ success: false,
90946
+ error: error2 instanceof Error ? error2.message : "\u66F4\u65B0\u63D0\u793A\u8A5E\u914D\u7F6E\u5931\u6557"
90947
+ });
90948
+ }
90949
+ });
90950
+ this.app.post("/api/settings/prompts/reset", (req, res) => {
90951
+ try {
90952
+ const prompts = resetPromptConfigs();
90953
+ logger.info("\u63D0\u793A\u8A5E\u914D\u7F6E\u5DF2\u91CD\u7F6E\u70BA\u9810\u8A2D\u503C");
90954
+ res.json({ success: true, prompts });
90955
+ } catch (error2) {
90956
+ logger.error("\u91CD\u7F6E\u63D0\u793A\u8A5E\u914D\u7F6E\u5931\u6557:", error2);
90957
+ res.status(500).json({
90958
+ success: false,
90959
+ error: error2 instanceof Error ? error2.message : "\u91CD\u7F6E\u63D0\u793A\u8A5E\u914D\u7F6E\u5931\u6557"
90960
+ });
90961
+ }
90962
+ });
90609
90963
  this.app.get("/api/logs", (req, res) => {
90610
90964
  try {
90611
90965
  const options = {};
@@ -92269,9 +92623,35 @@ var MCPServer = class {
92269
92623
  this.mcpServer.registerTool(
92270
92624
  "collect_feedback",
92271
92625
  {
92272
- description: "Collect feedback from users about AI work summary. This tool opens a web interface for users to provide feedback on the AI's work.",
92626
+ description: `Collect feedback from users about AI work. This tool opens a web interface for users to provide feedback.
92627
+
92628
+ CRITICAL: The 'work_summary' field is the PRIMARY and ONLY content displayed to users in the feedback UI. You MUST include ALL relevant information in this field as a comprehensive Markdown-formatted report. The UI renders Markdown, so use headings, tables, code blocks, and lists for better readability.`,
92273
92629
  inputSchema: {
92274
- work_summary: external_exports.string().describe("AI\u5DE5\u4F5C\u532F\u5831\u5167\u5BB9\uFF0C\u63CF\u8FF0AI\u5B8C\u6210\u7684\u5DE5\u4F5C\u548C\u7D50\u679C"),
92630
+ work_summary: external_exports.string().describe(`\u3010CRITICAL - THIS IS THE ONLY CONTENT SHOWN TO USERS\u3011
92631
+
92632
+ Include a COMPLETE Markdown report with ALL of the following sections:
92633
+
92634
+ ## Required Sections:
92635
+ 1. **\u{1F4CB} Task Summary** - Brief description of what was requested and accomplished
92636
+ 2. **\u{1F4C1} Implementation Details** - Files created/modified with:
92637
+ - Full file paths
92638
+ - Key code snippets in fenced code blocks
92639
+ - Explanation of changes
92640
+ 3. **\u2705 Status Table** - Markdown table showing completion status:
92641
+ | Item | Status | Notes |
92642
+ |------|--------|-------|
92643
+ | Feature A | \u2705 Done | ... |
92644
+ 4. **\u{1F9EA} Test Results** - Build/test command outputs and outcomes
92645
+ 5. **\u27A1\uFE0F Next Steps** - Actionable options in A/B/C format for user decision:
92646
+ - Option A: [action] - [description]
92647
+ - Option B: [action] - [description]
92648
+ 6. **\u{1F3D7}\uFE0F Architecture** (if applicable) - ASCII diagrams or Mermaid code blocks
92649
+
92650
+ ## Format Requirements:
92651
+ - Use Markdown: ## headings, \`code\`, **bold**, tables
92652
+ - Minimum 500 characters for non-trivial tasks
92653
+ - Be specific with file paths and code examples
92654
+ - Include ALL information user needs to make decisions`),
92275
92655
  project_name: external_exports.string().optional().describe("\u5C08\u6848\u540D\u7A31\uFF08\u7528\u65BC Dashboard \u5206\u7D44\u986F\u793A\uFF09"),
92276
92656
  project_path: external_exports.string().optional().describe("\u5C08\u6848\u8DEF\u5F91\uFF08\u7528\u65BC\u552F\u4E00\u8B58\u5225\u5C08\u6848\uFF09")
92277
92657
  }
@@ -53,9 +53,7 @@
53
53
 
54
54
  .logs-table-container {
55
55
  background: var(--bg-primary);
56
- border: 1px solid var(--border-color);
57
56
  border-radius: var(--radius-md);
58
- overflow: hidden;
59
57
  }
60
58
 
61
59
  .logs-table {
@@ -178,15 +176,17 @@
178
176
  display: flex;
179
177
  flex-direction: column;
180
178
  min-height: 0;
181
- overflow: hidden;
179
+ overflow: visible;
182
180
  }
183
181
 
184
182
  .logs-table-wrapper {
185
183
  flex: 1;
186
184
  overflow-y: auto;
185
+ overflow-x: hidden;
187
186
  border: 1px solid var(--border-color);
188
187
  border-radius: 8px;
189
188
  margin-bottom: 16px;
189
+ max-height: calc(100vh - 350px);
190
190
  }
191
191
 
192
192
  .logs-table-wrapper::-webkit-scrollbar {
@@ -187,6 +187,31 @@
187
187
  <option value="openai">OpenAI</option>
188
188
  <option value="anthropic">Anthropic (Claude)</option>
189
189
  <option value="google">Google (Gemini)</option>
190
+ <option value="nvidia">NVIDIA</option>
191
+ <option value="zai">Z.AI (Zhipu AI)</option>
192
+ </select>
193
+ </div>
194
+
195
+ <div class="form-group">
196
+ <label class="form-label" for="apiUrl">API Endpoint</label>
197
+ <input type="url" id="apiUrl" class="form-input" placeholder="API 端點 URL">
198
+ <p class="form-help">可自定義 API 端點,留空使用預設值</p>
199
+ </div>
200
+
201
+ <div class="form-group">
202
+ <div class="checkbox-group">
203
+ <input type="checkbox" id="openaiCompatible" />
204
+ <label for="openaiCompatible">OpenAI 相容模式</label>
205
+ </div>
206
+ <p class="form-help">啟用後使用 OpenAI API 格式(適用於自建或第三方相容服務)</p>
207
+ </div>
208
+
209
+ <!-- Z.AI 專用設定 -->
210
+ <div id="zaiExtSettings" class="form-group" style="display: none;">
211
+ <label class="form-label" for="zaiRegion">地區</label>
212
+ <select id="zaiRegion" class="form-select">
213
+ <option value="international">國際版 (api.z.ai)</option>
214
+ <option value="china">中國版 (bigmodel.cn)</option>
190
215
  </select>
191
216
  </div>
192
217
 
@@ -204,17 +229,6 @@
204
229
  <input type="text" id="aiModel" class="form-input" placeholder="例如: gpt-4-turbo, claude-3-opus">
205
230
  </div>
206
231
 
207
- <div class="form-group">
208
- <label class="form-label" for="systemPrompt">系統提示詞</label>
209
- <textarea id="systemPrompt" class="form-textarea" placeholder="自定義系統提示詞..."></textarea>
210
- <p class="form-help">留空將使用預設提示詞</p>
211
- </div>
212
-
213
- <div class="form-group">
214
- <label class="form-label" for="mcpToolsPrompt">MCP 工具提示詞</label>
215
- <textarea id="mcpToolsPrompt" class="form-textarea" placeholder="設定 MCP 工具的使用說明..."></textarea>
216
- <p class="form-help">定義 AI 如何使用 MCP 工具。{project_name} 和 {project_path} 會被替換為實際值</p>
217
- </div>
218
232
 
219
233
  <div class="form-row" style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
220
234
  <div class="form-group">
@@ -378,6 +392,27 @@
378
392
  <button id="saveSelfProbeBtn" class="btn btn-primary">儲存設定</button>
379
393
  </div>
380
394
  </section>
395
+
396
+ <!-- AI 提示詞設定 -->
397
+ <section class="settings-section">
398
+ <h2 class="section-title">
399
+ <span class="icon">📝</span>
400
+ AI 提示詞設定
401
+ </h2>
402
+ <p class="form-help" style="margin-bottom: 16px;">
403
+ 自定義 AI 回覆時使用的提示詞順序。第一次順序用於首次呼叫,第二次順序用於後續呼叫。順序為 0 表示不使用該組件。
404
+ </p>
405
+
406
+ <div id="promptConfigList" style="display: flex; flex-direction: column; gap: 16px;">
407
+ <div style="text-align: center; padding: 20px; color: var(--text-muted);">正在載入...</div>
408
+ </div>
409
+
410
+ <div class="form-actions" style="margin-top: 16px;">
411
+ <button id="resetPromptsBtn" class="btn btn-secondary">恢復預設</button>
412
+ <button id="savePromptsBtn" class="btn btn-primary">儲存提示詞設定</button>
413
+ </div>
414
+ </section>
415
+
381
416
  </div>
382
417
 
383
418
  <!-- Toast Container -->