kiku-agent 1.3.1 → 1.3.3

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.
Files changed (232) hide show
  1. package/README.md +69 -12
  2. package/package.json +5 -12
  3. package/server/agent/agentCore.js +319 -46
  4. package/server/agent/appPaths.js +31 -0
  5. package/server/agent/audioPlayer.js +339 -12
  6. package/server/agent/autoContext.js +193 -0
  7. package/server/agent/catalogEntityResolver.js +170 -28
  8. package/server/agent/clarifyingQuestions.js +137 -35
  9. package/server/agent/cli.js +177 -48
  10. package/server/agent/cloudAdapters.js +116 -0
  11. package/server/agent/commandRegistry.js +409 -80
  12. package/server/agent/commands.js +1056 -125
  13. package/server/agent/config.js +1088 -690
  14. package/server/agent/contextCompactor.js +213 -21
  15. package/server/agent/cpuMetrics.js +199 -0
  16. package/server/agent/engineService.js +591 -0
  17. package/server/agent/eval/AGENTS.md +228 -0
  18. package/server/agent/eval/analyze.py +684 -0
  19. package/server/agent/eval/gates.json +15 -0
  20. package/server/agent/eval/modules/analysis/compare.js +198 -0
  21. package/server/agent/eval/modules/analysis/gates.js +72 -0
  22. package/server/agent/eval/modules/analysis/judge.js +70 -0
  23. package/server/agent/eval/modules/core/cli.js +453 -0
  24. package/server/agent/eval/modules/core/config.js +60 -0
  25. package/server/agent/eval/modules/core/store.js +236 -0
  26. package/server/agent/eval/modules/evaluation/cases.js +689 -0
  27. package/server/agent/eval/modules/evaluation/runner.js +288 -0
  28. package/server/agent/eval/modules/evaluation/scoring.js +363 -0
  29. package/server/agent/eval/modules/evaluation/worker.js +122 -0
  30. package/server/agent/eval/modules/reporting/benchmark.js +265 -0
  31. package/server/agent/eval/modules/reporting/benchmarkPng.js +487 -0
  32. package/server/agent/eval/modules/reporting/dashboard.js +1406 -0
  33. package/server/agent/eval/modules/server/evalApi.js +1099 -0
  34. package/server/agent/eval/modules/ui/evalUi.js +4799 -0
  35. package/server/agent/eval/modules/utils/smokes.js +119 -0
  36. package/server/agent/eventBus.js +1 -0
  37. package/server/agent/ink/main.jsx +196 -191
  38. package/server/agent/jobManager.js +11 -1
  39. package/server/agent/lanDiscovery.js +721 -23
  40. package/server/agent/llmClient.js +317 -71
  41. package/server/agent/lmStudioWakeup.js +517 -65
  42. package/server/agent/locationService.js +797 -0
  43. package/server/agent/mcpQuizData.js +509 -0
  44. package/server/agent/memory/contextAllocator.js +19 -7
  45. package/server/agent/memory.js +3 -1
  46. package/server/agent/modelCompatibility.js +352 -0
  47. package/server/agent/modelDefaults.js +1 -0
  48. package/server/agent/modelManager.js +46 -2
  49. package/server/agent/newsWatch.js +9 -3
  50. package/server/agent/processManager.js +229 -0
  51. package/server/agent/prompts.js +174 -8
  52. package/server/agent/routingInterceptor.js +9 -1
  53. package/server/agent/sanitizer.js +41 -6
  54. package/server/agent/searchQueryPlanner.js +202 -0
  55. package/server/agent/session.js +59 -4
  56. package/server/agent/tests/agentBubbleSystem.test.js +65 -0
  57. package/server/agent/tests/agentFollowUps.test.js +253 -0
  58. package/server/agent/tests/agentMusicControl.test.js +245 -0
  59. package/server/agent/tests/apiRouter.test.js +108 -0
  60. package/server/agent/tests/architectureFixes.test.js +81 -0
  61. package/server/agent/tests/architectureRefactoring.test.js +194 -0
  62. package/server/agent/tests/autoContext.test.js +176 -0
  63. package/server/agent/tests/autoResearch.test.js +64 -0
  64. package/server/agent/tests/bilibiliEmbed.test.js +296 -0
  65. package/server/agent/tests/catalogSync.test.js +130 -0
  66. package/server/agent/tests/chatHardening.test.js +121 -0
  67. package/server/agent/tests/chineseWebSearchEscalation.test.js +62 -0
  68. package/server/agent/tests/citations.test.js +180 -0
  69. package/server/agent/tests/clarificationAnalytics.test.js +219 -0
  70. package/server/agent/tests/clarifyingQuestions.test.js +627 -0
  71. package/server/agent/tests/cliAutomation.test.js +158 -0
  72. package/server/agent/tests/cloudProviders.test.js +322 -0
  73. package/server/agent/tests/commandInput.test.js +24 -0
  74. package/server/agent/tests/commands.test.js +506 -0
  75. package/server/agent/tests/contextIntelligence.test.js +209 -0
  76. package/server/agent/tests/conversationSafety.test.js +53 -0
  77. package/server/agent/tests/cpuMetrics.test.js +101 -0
  78. package/server/agent/tests/decisionAndSearchEnhancements.test.js +167 -0
  79. package/server/agent/tests/dramaAnswerQuality.test.js +73 -0
  80. package/server/agent/tests/dynamicCatalogIngestion.test.js +285 -0
  81. package/server/agent/tests/dynamicRoutingAndSearch.test.js +92 -0
  82. package/server/agent/tests/edgeCasesScenarios.test.js +131 -0
  83. package/server/agent/tests/engineService.test.js +436 -0
  84. package/server/agent/tests/engineUnload.test.js +94 -0
  85. package/server/agent/tests/entertainmentMetrics.test.js +126 -0
  86. package/server/agent/tests/envBootstrap.test.js +87 -0
  87. package/server/agent/tests/envCommand.test.js +284 -0
  88. package/server/agent/tests/errorHandling.test.js +80 -0
  89. package/server/agent/tests/errors.test.js +13 -0
  90. package/server/agent/tests/evalAnalyze.test.js +54 -0
  91. package/server/agent/tests/evalCommand.test.js +54 -0
  92. package/server/agent/tests/evalCorpus.test.js +126 -0
  93. package/server/agent/tests/evalHarness.test.js +62 -0
  94. package/server/agent/tests/evalJudgeContext.test.js +38 -0
  95. package/server/agent/tests/evalMetrics.test.js +320 -0
  96. package/server/agent/tests/evalStore.test.js +190 -0
  97. package/server/agent/tests/evalStudio.test.js +179 -0
  98. package/server/agent/tests/feedbackLearning.test.js +186 -0
  99. package/server/agent/tests/fetchApi.test.js +99 -0
  100. package/server/agent/tests/firstRun.test.js +44 -0
  101. package/server/agent/tests/gemma4eOptimization.test.js +95 -0
  102. package/server/agent/tests/hardwareTiers.test.js +206 -0
  103. package/server/agent/tests/hybridWebSearch.test.js +194 -0
  104. package/server/agent/tests/judgeService.test.js +251 -0
  105. package/server/agent/tests/lanDiscovery.test.js +324 -0
  106. package/server/agent/tests/lanInterfaces.test.js +510 -0
  107. package/server/agent/tests/lmStudioWakeup.test.js +278 -0
  108. package/server/agent/tests/locationTool.test.js +278 -0
  109. package/server/agent/tests/mcpClient.test.js +201 -0
  110. package/server/agent/tests/memoryRoute.test.js +173 -0
  111. package/server/agent/tests/memoryUiNormalization.test.js +75 -0
  112. package/server/agent/tests/modelCompatibility.test.js +148 -0
  113. package/server/agent/tests/modelManager.test.js +141 -0
  114. package/server/agent/tests/multilingualScenarios.test.js +69 -0
  115. package/server/agent/tests/musicControlUiBridge.test.js +113 -0
  116. package/server/agent/tests/newCommands.test.js +347 -0
  117. package/server/agent/tests/newCommandsTui.test.js +141 -0
  118. package/server/agent/tests/newTools.test.js +113 -0
  119. package/server/agent/tests/newsIntelligence.test.js +178 -0
  120. package/server/agent/tests/nextGenArchitecture.test.js +101 -0
  121. package/server/agent/tests/offlineInput.test.js +217 -0
  122. package/server/agent/tests/openDomainPrompt.test.js +197 -0
  123. package/server/agent/tests/openDomainSearch.test.js +121 -0
  124. package/server/agent/tests/packagingGuard.test.js +57 -0
  125. package/server/agent/tests/presentation.test.js +61 -0
  126. package/server/agent/tests/processManager.test.js +144 -0
  127. package/server/agent/tests/providerPersistence.test.js +44 -0
  128. package/server/agent/tests/queryBenchmarks.test.js +83 -0
  129. package/server/agent/tests/queryDetails.test.js +126 -0
  130. package/server/agent/tests/quickActionsAndModals.test.js +180 -0
  131. package/server/agent/tests/realWorldValidation.test.js +391 -0
  132. package/server/agent/tests/resilienceAndConcurrency.test.js +117 -0
  133. package/server/agent/tests/runtimeWatchdog.test.js +32 -0
  134. package/server/agent/tests/sanitizer.test.js +37 -0
  135. package/server/agent/tests/scenariosData.js +378 -0
  136. package/server/agent/tests/schemaRouting.test.js +54 -0
  137. package/server/agent/tests/searchQueryPlanner.test.js +108 -0
  138. package/server/agent/tests/securityAuditRemediation.test.js +221 -0
  139. package/server/agent/tests/securityAuditRemediations2.test.js +154 -0
  140. package/server/agent/tests/securityHardening.test.js +146 -0
  141. package/server/agent/tests/securitySanitizer.test.js +38 -0
  142. package/server/agent/tests/sessionAndCopy.test.js +267 -0
  143. package/server/agent/tests/sidebarControls.test.js +135 -0
  144. package/server/agent/tests/statefulMemory.test.js +277 -0
  145. package/server/agent/tests/subagent.test.js +216 -0
  146. package/server/agent/tests/subagentProgress.test.js +109 -0
  147. package/server/agent/tests/subagentTextFallback.test.js +115 -0
  148. package/server/agent/tests/suggestions.test.js +199 -0
  149. package/server/agent/tests/suggestionsTui.test.js +251 -0
  150. package/server/agent/tests/telemetryAndProfiles.test.js +179 -0
  151. package/server/agent/tests/terminalImage.test.js +80 -0
  152. package/server/agent/tests/terminalMarkdown.test.js +166 -0
  153. package/server/agent/tests/terminalText.test.js +133 -0
  154. package/server/agent/tests/textProtocolLoop.test.js +197 -0
  155. package/server/agent/tests/textToolParsing.test.js +187 -0
  156. package/server/agent/tests/theme.test.js +26 -0
  157. package/server/agent/tests/toolRegistryParity.test.js +53 -0
  158. package/server/agent/tests/toolRoutingRegressions.test.js +160 -0
  159. package/server/agent/tests/tools.test.js +259 -0
  160. package/server/agent/tests/unifiedEndpoints.test.js +159 -0
  161. package/server/agent/tests/v110Features.test.js +262 -0
  162. package/server/agent/tests/versionBump.test.js +69 -0
  163. package/server/agent/tests/vramEstimator.test.js +595 -0
  164. package/server/agent/tests/webChatDocs.test.js +199 -0
  165. package/server/agent/tests/webChatInferenceControl.test.js +298 -0
  166. package/server/agent/tests/webChatInspection.test.js +551 -0
  167. package/server/agent/tests/webChatMcp.test.js +94 -0
  168. package/server/agent/tests/webChatMemory.test.js +112 -0
  169. package/server/agent/tests/webChatSecurity.test.js +169 -0
  170. package/server/agent/tests/webExtract.test.js +284 -0
  171. package/server/agent/tests/webSources.test.js +281 -0
  172. package/server/agent/textToolProtocol.js +173 -46
  173. package/server/agent/toolRouter.js +318 -44
  174. package/server/agent/tools.js +877 -96
  175. package/server/agent/ui.js +39 -34
  176. package/server/agent/vramService.js +35 -54
  177. package/server/agent/webSearch.js +441 -162
  178. package/server/agent/websearch/orchestrator.js +15 -5
  179. package/server/agent/websearch/resilience/cache.js +9 -2
  180. package/server/agent/websearch/security/ssrf.js +41 -10
  181. package/server/agent/youtubeAuth.js +5 -20
  182. package/server/apiRouter.js +5558 -4422
  183. package/server/eval/judgeRubrics.js +110 -0
  184. package/server/eval/judgeService.js +290 -0
  185. package/server/eval/runBenchmark.js +182 -0
  186. package/server/mcp/data/discography.js +2 -2
  187. package/server/mcp/examples/antigravity.example.json +17 -0
  188. package/server/mcp/examples/librechat.example.yaml +16 -0
  189. package/server/mcp/handler.js +8 -4
  190. package/server/mcp/security.js +220 -21
  191. package/server/mcp/server.js +2 -1
  192. package/server/mcp/tests/connection.test.js +162 -0
  193. package/server/mcp/tests/mcpKeyAuth.test.js +156 -0
  194. package/server/mcp/tools.js +7 -1
  195. package/server/supabase_migration_prefix.sql +102 -0
  196. package/server/supabase_schema.sql +63 -0
  197. package/server/web-chat/core/connection.js +179 -218
  198. package/server/web-chat/core/conversationContext.js +9 -2
  199. package/server/web-chat/core/directCatalogEngine.js +52 -28
  200. package/server/web-chat/core/dramaKnowledge.js +33 -22
  201. package/server/web-chat/core/followUpEngine.js +735 -81
  202. package/server/web-chat/core/futureEventGuard.js +5 -1
  203. package/server/web-chat/core/musicControlAction.js +70 -0
  204. package/server/web-chat/core/security.js +32 -21
  205. package/server/web-chat/core/toolInspector.js +6 -6
  206. package/server/web-chat/core/urlGuard.js +45 -0
  207. package/server/web-chat/router.js +204 -157
  208. package/server/web-chat/routes/chatRoute.js +885 -488
  209. package/server/web-chat/routes/engineRoute.js +270 -0
  210. package/server/web-chat/routes/exportRoute.js +1 -1
  211. package/server/web-chat/routes/lanDiscoveryRoute.js +17 -2
  212. package/server/web-chat/routes/lmStudioLoadRoute.js +172 -0
  213. package/server/web-chat/routes/mcpRoute.js +386 -0
  214. package/server/web-chat/routes/memoryRoute.js +253 -0
  215. package/server/web-chat/routes/providerRoute.js +49 -16
  216. package/server/web-chat/routes/staticRoute.js +306 -17
  217. package/server/web-chat/routes/statusRoute.js +194 -73
  218. package/server/web-chat/routes/suggestRoute.js +11 -3
  219. package/server/web-chat/routes/verifyRoute.js +26 -4
  220. package/server/web-chat/server.js +7 -6
  221. package/server/web-chat/ui/docsBuilder.js +3558 -0
  222. package/server/web-chat/ui/htmlBuilder.js +12 -0
  223. package/server/web-chat/ui/scripts/client.js +3401 -1302
  224. package/server/web-chat/ui/scripts/webchatI18n.js +155 -153
  225. package/server/web-chat/ui/settingsBuilder.js +7870 -0
  226. package/server/web-chat/ui/styles/lan-card.css +165 -0
  227. package/server/web-chat/ui/styles/main.css +4289 -447
  228. package/server/web-chat/ui/sw.js +64 -0
  229. package/server/web-chat/ui/templates/body.html +407 -281
  230. package/src/data/awardsData.js +146 -146
  231. package/src/data/musicVideoData.js +6 -4
  232. package/src/lib/vram/estimator.js +195 -19
@@ -2,7 +2,8 @@ import { executeTool } from '../../agent/tools.js';
2
2
  import { makeInspectableToolPayload } from './toolInspector.js';
3
3
  import { generateDynamicFollowUps } from './followUpEngine.js';
4
4
  import { buildDramaSpecialistAnswer } from './dramaKnowledge.js';
5
- import { resolveQueryEntities, detectPromotionIntent } from '../../agent/catalogEntityResolver.js';
5
+ import { resolveQueryEntities } from '../../agent/catalogEntityResolver.js';
6
+ import { evaluateMusicControlIntent } from '../../agent/audioPlayer.js';
6
7
 
7
8
  export async function executeDirectCatalogKnowledge(query, _persona = 'default', options = {}) {
8
9
  const toolsUsed = [];
@@ -30,6 +31,35 @@ export async function executeDirectCatalogKnowledge(query, _persona = 'default',
30
31
  }
31
32
  };
32
33
 
34
+ const musicIntent = evaluateMusicControlIntent(query);
35
+ if (musicIntent && musicIntent.score >= 3) {
36
+ let args = {};
37
+ if (musicIntent.action === 'set_volume') {
38
+ args = { volume: musicIntent.volume !== undefined ? musicIntent.volume : 80 };
39
+ } else if (musicIntent.action === 'seek') {
40
+ args = { seconds: musicIntent.seconds || 0 };
41
+ } else if (musicIntent.action === 'set_repeat') {
42
+ args = { mode: 'all' };
43
+ } else if (musicIntent.action === 'set_shuffle') {
44
+ args = { enabled: true };
45
+ } else if (musicIntent.action === 'play') {
46
+ const cleanSongQuery = query.replace(/(?:เล่นเพลง|เปิดเพลง|ฟังเพลง|play\s*song|放歌|听歌)/gi, '').trim();
47
+ args = cleanSongQuery ? { query: cleanSongQuery } : {};
48
+ }
49
+ const toolRes = await addToolExec(musicIntent.tool, args);
50
+ const msg = toolRes?.message || 'ดำเนินการควบคุมเครื่องเล่นเพลงเรียบร้อยแล้ว';
51
+ return {
52
+ answer: `> [OK] **Music Player Control**\n> ${msg}`,
53
+ thinking: 'ตรวจพบคำสั่งควบคุมเพลง -> ดำเนินการผ่าน Music Tool ทันที',
54
+ tools: toolsUsed,
55
+ suggestions: [
56
+ { label: '❚❚ พักการเล่นเพลง', action: 'send_query', query: 'พักเพลง' },
57
+ { label: '▶ เล่นเพลงต่อ', action: 'send_query', query: 'เล่นต่อ' },
58
+ { label: '■ ปิดเพลง', action: 'send_query', query: 'ปิดเพลง' }
59
+ ]
60
+ };
61
+ }
62
+
33
63
  const specialistAnswer = await buildDramaSpecialistAnswer(query, addToolExec);
34
64
  if (specialistAnswer) answerParts.push(specialistAnswer);
35
65
 
@@ -38,7 +68,7 @@ export async function executeDirectCatalogKnowledge(query, _persona = 'default',
38
68
  if (!specialistAnswer && (q.includes('ประวัติ') || q.includes('who is') || q.includes('profile') || q.includes('slogan') || q.includes('ฉายา'))) {
39
69
  const profile = await addToolExec('get_profile_and_fandom', {});
40
70
  if (profile) {
41
- answerParts.push(`### 🌸 ประวัติและข้อมูลส่วนตัว: จวีจิ้งอี (Ju Jingyi / 鞠婧祎)\n\n` +
71
+ answerParts.push(`### [Profile] ประวัติและข้อมูลส่วนตัว: จวีจิ้งอี (Ju Jingyi / 鞠婧祎)\n\n` +
42
72
  `- **ชื่อภาษาจีน:** ${profile.profile?.nameCn || '鞠婧祎'} (${profile.profile?.pinyin || 'Jū Jìngyī'})\n` +
43
73
  `- **ชื่อสากล/ฉายา:** Kiku / เสี่ยวจวี (小鞠) / สาวงาม 4,000 ปี\n` +
44
74
  `- **วันเกิด:** ${profile.profile?.birthDate || '18 มิถุนายน 1994'}\n` +
@@ -49,7 +79,6 @@ export async function executeDirectCatalogKnowledge(query, _persona = 'default',
49
79
  }
50
80
  }
51
81
 
52
-
53
82
  if (!specialistAnswer && (entityRes.hasBrand || q.includes('แบรนด์') || q.includes('พรีเซนเตอร์') || q.includes('ambassador') || q.includes('โฆษณา') || q.includes('โปรโมท') || q.includes('โปรโมต') || entityRes.isPromotion)) {
54
83
  const brandQuery = entityRes.primaryBrand ? (entityRes.primaryBrand.nameEn || entityRes.primaryBrand.id) : undefined;
55
84
  const brandsRes = await addToolExec('get_brand_endorsements', brandQuery ? { query: brandQuery } : {});
@@ -57,51 +86,50 @@ export async function executeDirectCatalogKnowledge(query, _persona = 'default',
57
86
  if (brands.length > 0) {
58
87
  if (entityRes.primaryBrand) {
59
88
  const b = brands[0];
60
- let md = `### 👑 ข้อมูลแบรนด์: ${b.brand} (${b.year || 'ปัจจุบัน'})\n\n` +
89
+ let md = `### [Brand] ข้อมูลแบรนด์: ${b.brand} (${b.year || 'ปัจจุบัน'})\n\n` +
61
90
  `- **ตำแหน่ง:** ${b.title || 'Brand Ambassador'}\n` +
62
91
  `- **หมวดหมู่:** ${b.category || 'Luxury'}\n` +
63
92
  `- **ไฮไลต์สำคัญ:** ${b.highlight || 'พรีเซนเตอร์ระดับโลกของจวีจิ้งอี'}\n`;
64
- if (b.bilibiliSearchUrl) md += `- 🔗 [ค้นหาคลิปโปรโมทบน Bilibili](${b.bilibiliSearchUrl})\n`;
65
- if (b.youtubeCommercialSearch) md += `- 🔗 [ค้นหาโฆษณาบน YouTube](${b.youtubeCommercialSearch})\n`;
93
+ if (b.bilibiliSearchUrl) md += `- [Link] [ค้นหาคลิปโปรโมทบน Bilibili](${b.bilibiliSearchUrl})\n`;
94
+ if (b.youtubeCommercialSearch) md += `- [Link] [ค้นหาโฆษณาบน YouTube](${b.youtubeCommercialSearch})\n`;
66
95
  answerParts.push(md);
67
96
  } else {
68
- let md = `### 👑 บทบาท Brand Ambassador และพรีเซนเตอร์ระดับโลก (${brands.length} แบรนด์)\n\n`;
97
+ let md = `### [Brand] บทบาท Brand Ambassador และพรีเซนเตอร์ระดับโลก (${brands.length} แบรนด์)\n\n`;
69
98
  brands.slice(0, 15).forEach(b => {
70
99
  md += `- **${b.brand}** — ตำแหน่ง: **${b.title}** (${b.category || 'Luxury'}) [ปี ${b.year || 'ปัจจุบัน'}]\n`;
71
- if (b.bilibiliSearchUrl) md += ` - 🔗 [คลิปบน Bilibili](${b.bilibiliSearchUrl})\n`;
100
+ if (b.bilibiliSearchUrl) md += ` - [Link] [คลิปบน Bilibili](${b.bilibiliSearchUrl})\n`;
72
101
  });
73
102
  answerParts.push(md);
74
103
  }
75
104
  }
76
105
  }
77
106
 
78
-
79
107
  if (!specialistAnswer && (entityRes.hasWork || q.includes('ซีรีส์') || q.includes('ละคร') || q.includes('หนัง') || q.includes('ผลงาน') || q.includes('บทบาท'))) {
80
108
  const workQuery = entityRes.primaryWork ? (entityRes.primaryWork.nameCn || entityRes.primaryWork.nameEn) : q.replace(/(ซีรีส์|ละคร|หนัง|ผลงาน|เรื่อง|เด่น|ของ|จวีจิ้งอี)/g, '').trim();
81
109
  const worksRes = await addToolExec('search_works', { query: workQuery });
82
110
  if (worksRes?.status === 'rumored_project_info' && worksRes?.rumoredProject) {
83
111
  const r = worksRes.rumoredProject;
84
- answerParts.push(`### 🎬 ข้อมูลซีรีส์ 《${r.titleCn}》 (${r.titleEn})\n\n` +
112
+ answerParts.push(`### [Series] ข้อมูลซีรีส์ 《${r.titleCn}》 (${r.titleEn})\n\n` +
85
113
  `**《${r.titleCn}》** เป็นโปรเจกต์ซีรีส์ที่มีกระแสข่าวลือแคสติ้ง (${r.status}):\n\n` +
86
- `#### 📖 รายละเอียดโปรเจกต์:\n` +
114
+ `#### [Details] รายละเอียดโปรเจกต์:\n` +
87
115
  `- **ผู้ผลิต:** ${r.producer || 'Youku x Stellar Media'}\n` +
88
116
  `- **ต้นฉบับ:** ${r.originalNovel || 'นวนิยายจีน'}\n` +
89
117
  `- **เนื้อหา:** ${r.details || ''}\n\n` +
90
- `#### 🔥 เหตุผลที่เป็นกระแส:\n` +
118
+ `#### [Highlight] เหตุผลที่เป็นกระแส:\n` +
91
119
  `${r.buzzReason || ''}\n\n` +
92
- `> ⚠️ **ข้อควรระวัง (Disambiguation):** ${r.disambiguation || ''}`);
120
+ `> [Notice] **ข้อควรระวัง (Disambiguation):** ${r.disambiguation || ''}`);
93
121
  } else {
94
122
  const works = worksRes?.works || [];
95
123
  if (works.length > 0) {
96
- let md = `### 🎬 ผลงานซีรีส์และภาพยนตร์ที่โดดเด่น (${works.length} เรื่อง)\n\n`;
124
+ let md = `### [Works] ผลงานซีรีส์และภาพยนตร์ที่โดดเด่น (${works.length} เรื่อง)\n\n`;
97
125
  works.slice(0, 15).forEach(w => {
98
- md += `#### 🌟 **${w.titleTh || w.titleCn}** (${w.titleCn || ''})\n` +
126
+ md += `#### [Featured] **${w.titleTh || w.titleCn}** (${w.titleCn || ''})\n` +
99
127
  `- **บทบาทที่ได้รับ:** ${w.role || w.character || 'นักแสดงนำ'}\n` +
100
128
  `- **ปีที่ฉาย:** ${w.year || 'N/A'} • **ประเภท:** ${w.category || 'ซีรีส์โบราณ/โรแมนติก'}\n` +
101
129
  `- **เรื่องย่อ:** ${w.synopsis || w.description || 'ผลงานซีรีส์ชื่อดังของจวีจิ้งอี'}\n` +
102
130
  `- **ช่องทางรับชม:** ${w.streamingPlatform || w.platform || 'ไม่ระบุในแคตตาล็อก'}\n`;
103
131
  if (w.youtubeSearchUrl) {
104
- md += `- 🔗 [ดูตัวอย่างบน YouTube](${w.youtubeSearchUrl})\n\n`;
132
+ md += `- [Link] [ดูตัวอย่างบน YouTube](${w.youtubeSearchUrl})\n\n`;
105
133
  } else {
106
134
  md += `\n`;
107
135
  }
@@ -111,27 +139,25 @@ export async function executeDirectCatalogKnowledge(query, _persona = 'default',
111
139
  }
112
140
  }
113
141
 
114
-
115
142
  if (!specialistAnswer && (entityRes.hasSong || q.includes('เพลง') || q.includes('อัลบั้ม') || q.includes('ost') || q.includes('ร้อง'))) {
116
143
  const songQuery = entityRes.primarySong ? (entityRes.primarySong.nameCn || entityRes.primarySong.nameEn) : q.replace(/(เพลง|อัลบั้ม|ost|ของ|จวีจิ้งอี)/g, '').trim();
117
144
  const discoRes = await addToolExec('search_discography', { query: songQuery });
118
145
  const songs = discoRes?.songs || [];
119
146
  if (songs.length > 0) {
120
- let md = `### 🎵 ผลงานเพลงและเพลงประกอบซีรีส์ (Discography - ${songs.length} เพลง)\n\n`;
147
+ let md = `### [Discography] ผลงานเพลงและเพลงประกอบซีรีส์ (${songs.length} เพลง)\n\n`;
121
148
  songs.slice(0, 20).forEach(s => {
122
149
  md += `- **${s.titleTh || s.titleCn}** (${s.titleCn || ''}) — *ปี ${s.year || ''}* [ประเภท: ${s.work || 'Single'}]\n`;
123
- if (s.youtubeUrl) md += ` - 🔗 [ฟังบน YouTube](${s.youtubeUrl})\n`;
150
+ if (s.youtubeUrl) md += ` - [Link] [ฟังบน YouTube](${s.youtubeUrl})\n`;
124
151
  });
125
152
  answerParts.push(md);
126
153
  }
127
154
  }
128
155
 
129
-
130
156
  if (!specialistAnswer && (entityRes.hasAward || q.includes('รางวัล') || q.includes('award') || q.includes('เกียรติยศ'))) {
131
157
  const awardsRes = await addToolExec('get_awards', {});
132
158
  const awards = awardsRes?.awards || [];
133
159
  if (awards.length > 0) {
134
- let md = `### 🏆 รางวัลและความสำเร็จสำคัญ (${awards.length} รางวัล)\n\n`;
160
+ let md = `### [Awards] รางวัลและความสำเร็จสำคัญ (${awards.length} รางวัล)\n\n`;
135
161
  awards.slice(0, 20).forEach(a => {
136
162
  md += `- **${a.year || ''}**: ${a.award || a.title} (${a.ceremony || 'งานประกาศรางวัล'}) — *${a.category || ''}*\n`;
137
163
  });
@@ -139,12 +165,11 @@ export async function executeDirectCatalogKnowledge(query, _persona = 'default',
139
165
  }
140
166
  }
141
167
 
142
-
143
168
  if (!specialistAnswer && (entityRes.hasTimeline || q.includes('ไทม์ไลน์') || q.includes('timeline') || q.includes('snh48') || q.includes('เส้นทาง') || q.includes('อดีต'))) {
144
169
  const timelineRes = await addToolExec('get_career_timeline', {});
145
170
  const events = timelineRes?.timeline || [];
146
171
  if (events.length > 0) {
147
- let md = `### 🌸 ไทม์ไลน์เส้นทางอาชีพสำคัญ (Career Timeline - ${events.length} เหตุการณ์)\n\n`;
172
+ let md = `### [Timeline] ไทม์ไลน์เส้นทางอาชีพสำคัญ (${events.length} เหตุการณ์)\n\n`;
148
173
  events.slice(0, 20).forEach(e => {
149
174
  md += `- **ปี ${e.year}**: ${e.title} — ${e.description || ''}\n`;
150
175
  });
@@ -152,12 +177,11 @@ export async function executeDirectCatalogKnowledge(query, _persona = 'default',
152
177
  }
153
178
  }
154
179
 
155
-
156
180
  if (!specialistAnswer && (q.includes('บทบาท') || q.includes('ตัวละคร') || q.includes('character') || q.includes('role'))) {
157
181
  const rolesRes = await addToolExec('get_character_roles', {});
158
182
  const roles = rolesRes?.roles || [];
159
183
  if (roles.length > 0) {
160
- let md = `### 🎭 โครงสร้างบทบาทและตัวละครเด่น (${roles.length} บทบาท)\n\n`;
184
+ let md = `### [Roles] โครงสร้างบทบาทและตัวละครเด่น (${roles.length} บทบาท)\n\n`;
161
185
  roles.slice(0, 15).forEach(r => {
162
186
  md += `- **${r.nameTh || r.nameCn}** (${r.nameCn || ''}) จากเรื่อง *${r.drama || ''}* — บุคลิก: ${r.personality || r.archetype || 'ตัวเอก'}\n`;
163
187
  });
@@ -177,9 +201,9 @@ export async function executeDirectCatalogKnowledge(query, _persona = 'default',
177
201
  const worksRes = await addToolExec('search_works', { featuredOnly: true });
178
202
  const brandRes = await addToolExec('get_brand_endorsements', { isGlobal: true });
179
203
  const topBrand = brandRes?.brands?.[0]?.brand || 'Global Brand Ambassador';
180
- let md = `### 🌸 ข้อมูลสรุป จวีจิ้งอี (Ju Jingyi / 鞠婧祎)\n\n`;
204
+ let md = `### [Profile] ข้อมูลสรุป จวีจิ้งอี (Ju Jingyi / 鞠婧祎)\n\n`;
181
205
  md += `จวีจิ้งอี เป็นศิลปิน นักร้อง และนักแสดงหญิงชั้นนำของประเทศจีน เจ้าของฉายา "สาวงาม 4,000 ปี" และตัวแทนภาพลักษณ์แบรนด์ระดับสากล (${topBrand})\n\n`;
182
- md += `#### 🎬 ผลงานซีรีส์เด่น:\n`;
206
+ md += `#### [Works] ผลงานซีรีส์เด่น:\n`;
183
207
  (worksRes?.works || []).slice(0, 4).forEach(w => {
184
208
  md += `- **${w.titleTh || w.titleCn}** รับบทเป็น *${w.role || w.character}* (ปี ${w.year})\n`;
185
209
  });
@@ -187,7 +211,7 @@ export async function executeDirectCatalogKnowledge(query, _persona = 'default',
187
211
  }
188
212
  }
189
213
 
190
- const headerNotice = `> 💡 **โหมดฐานข้อมูลอัจฉริยะ (Direct Catalog Knowledge Mode)**\n` +
214
+ const headerNotice = `> [Direct Catalog] **โหมดฐานข้อมูลอัจฉริยะ (Direct Catalog Knowledge Mode)**\n` +
191
215
  `> *ระบบสืบค้นข้อมูลจาก Local Database ให้โดยตรงทันทีอย่างแม่นยำ (เปิด LM Studio เพื่อเปิดใช้งาน Generative AI)*\n\n`;
192
216
 
193
217
  const finalMarkdown = headerNotice + answerParts.join('\n\n');
@@ -12,42 +12,53 @@ export function detectDramaKnowledgeIntent(query) {
12
12
  return null;
13
13
  }
14
14
 
15
- export function assessDramaAnswerQuality(query, answer) {
15
+ export function evaluateDramaCoverageScore(query, answer) {
16
16
  const intent = detectDramaKnowledgeIntent(query);
17
- if (!intent) return { applicable: false, valid: true, issues: [] };
17
+ if (!intent) return { applicable: false, coverageScore: 1.0, qualityScore: 1.0, valid: true, issues: [] };
18
18
 
19
19
  const text = String(answer || '');
20
20
  const issues = [];
21
+ let factHits = 0;
22
+ let expectedFacts = 3;
23
+
21
24
  const unsupported = /(?:ระดับแนวหน้า|ไม่มีใครเทียบได้|ก้าวข้ามจากไอดอล(?:ได้)?อย่างสมบูรณ์|ได้รับรางวัลมากมาย)/i;
22
25
  if (unsupported.test(text)) issues.push('unsupported-career-claim');
23
26
  if (/★\s*\d+(?:\.\d+)?\s*\/\s*10/.test(text)) issues.push('unattributed-rating');
24
27
 
25
28
  if (intent === 'white-snake-story') {
26
- if (!/(?:ไป๋ซู่เจิน|bai\s+suzhen).{0,100}(?:งูขาว|white[- ]snake spirit)/is.test(text)) {
27
- issues.push('missing-bai-suzhen-identity');
28
- }
29
- if (!/(?:สวี่เซียน|xu\s+xian).{0,80}(?:มนุษย์ชาย|human man|human scholar|mortal)/is.test(text)) {
30
- issues.push('missing-xu-xian-identity');
31
- }
32
- if (/(?:ตัวละครเอก|นางเอก|protagonist).{0,80}(?:มนุษย์สาว|human woman).{0,200}(?:พญางูขาว|white snake)/is.test(text)) {
33
- issues.push('reversed-species-or-gender');
34
- }
29
+ expectedFacts = 3;
30
+ const mentionsBai = /(?:ไป๋ซู่เจิน|bai\s+suzhen).{0,100}(?:งูขาว|white[- ]snake spirit)/is.test(text);
31
+ const mentionsXu = /(?:สวี่เซียน|xu\s+xian).{0,80}(?:มนุษย์ชาย|human man|human scholar|mortal)/is.test(text);
32
+ const reversed = /(?:ตัวละครเอก|นางเอก|protagonist).{0,80}(?:มนุษย์สาว|human woman).{0,200}(?:พญางูขาว|white snake)/is.test(text);
33
+
34
+ if (mentionsBai) factHits++; else issues.push('missing-bai-suzhen-identity');
35
+ if (mentionsXu) factHits++; else issues.push('missing-xu-xian-identity');
36
+ if (!reversed) factHits++; else issues.push('reversed-species-or-gender');
35
37
  }
36
38
 
37
39
  if (intent === 'acting-era-comparison') {
38
- if (!/In\s+Blossom/i.test(text) || !/Sword\s+and\s+Fairy\s+4/i.test(text)) {
39
- issues.push('incomplete-2024-comparison');
40
- }
41
- if (!/(?:2016|2017|2018|2019)/.test(text)) issues.push('missing-past-work-evidence');
42
- if (!/(?:ตัวตน|identity).{0,100}(?:หยางไฉ่เวย|yang\s+caiwei).{0,160}(?:รูปลักษณ์|ร่าง|appearance).{0,100}(?:ซ่างกวนจื่อ|shangguan\s+zhi)/is.test(text)) {
43
- issues.push('imprecise-in-blossom-role');
44
- }
45
- if (/(?:สลับเล่น\s*2|เล่น\s*2\s*(?:บทบาท|คาแรคเตอร์)|dual-role performance)/i.test(text)) {
46
- issues.push('false-dual-role-framing');
47
- }
40
+ expectedFacts = 4;
41
+ const has2024 = /In\s+Blossom/i.test(text) && /Sword\s+and\s+Fairy\s+4/i.test(text);
42
+ const hasPast = /(?:2016|2017|2018|2019)/.test(text);
43
+ const hasRoleNuance = /(?:ตัวตน|identity).{0,100}(?:หยางไฉ่เวย|yang\s+caiwei).{0,160}(?:รูปลักษณ์|ร่าง|appearance).{0,100}(?:ซ่างกวนจื่อ|shangguan\s+zhi)/is.test(text);
44
+ const hasFalseDual = /(?:สลับเล่น\s*2|เล่น\s*2\s*(?:บทบาท|คาแรคเตอร์)|dual-role performance)/i.test(text);
45
+
46
+ if (has2024) factHits++; else issues.push('incomplete-2024-comparison');
47
+ if (hasPast) factHits++; else issues.push('missing-past-work-evidence');
48
+ if (hasRoleNuance) factHits++; else issues.push('imprecise-in-blossom-role');
49
+ if (!hasFalseDual) factHits++; else issues.push('false-dual-role-framing');
48
50
  }
49
51
 
50
- return { applicable: true, valid: issues.length === 0, issues };
52
+ const coverageScore = Number((factHits / expectedFacts).toFixed(2));
53
+ const penalty = issues.length * 0.25;
54
+ const qualityScore = Number(Math.max(0, 1.0 - penalty).toFixed(2));
55
+ const valid = issues.length === 0;
56
+
57
+ return { applicable: true, coverageScore, qualityScore, valid, issues };
58
+ }
59
+
60
+ export function assessDramaAnswerQuality(query, answer) {
61
+ return evaluateDramaCoverageScore(query, answer);
51
62
  }
52
63
 
53
64
  function bulletList(items = []) {