kiku-agent 1.0.11 → 1.2.0

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 (221) hide show
  1. package/README.md +300 -495
  2. package/package.json +12 -11
  3. package/public/fonts/GoogleSans-Bold.ttf +0 -0
  4. package/public/fonts/GoogleSans-Bold.woff2 +0 -0
  5. package/public/fonts/GoogleSans-BoldItalic.ttf +0 -0
  6. package/public/fonts/GoogleSans-Italic.ttf +0 -0
  7. package/public/fonts/GoogleSans-Latin-400.woff2 +0 -0
  8. package/public/fonts/GoogleSans-Latin-500.woff2 +0 -0
  9. package/public/fonts/GoogleSans-Latin-600.woff2 +0 -0
  10. package/public/fonts/GoogleSans-Latin-700.woff2 +0 -0
  11. package/public/fonts/GoogleSans-Medium.ttf +0 -0
  12. package/public/fonts/GoogleSans-Medium.woff2 +0 -0
  13. package/public/fonts/GoogleSans-Regular.ttf +0 -0
  14. package/public/fonts/GoogleSans-Regular.woff2 +0 -0
  15. package/public/fonts/GoogleSans-Thai-400.woff2 +0 -0
  16. package/public/fonts/GoogleSans-Thai-500.woff2 +0 -0
  17. package/public/fonts/GoogleSans-Thai-600.woff2 +0 -0
  18. package/public/fonts/GoogleSans-Thai-700.woff2 +0 -0
  19. package/public/fonts/NotoSansThai-Bold.woff2 +0 -0
  20. package/public/fonts/NotoSansThai-Regular.woff2 +0 -0
  21. package/public/fonts/google-sans-official.css +900 -0
  22. package/public/images/0040jbadgy1id6am4yxqfj61s82dn1kx02.jpg +0 -0
  23. package/public/images/0040jbadgy1id6am708pbj62c0340qv602.jpg +0 -0
  24. package/public/images/0040jbadgy1id6amb81uqj62c0340x6q02.jpg +0 -0
  25. package/public/images/0040jbadgy1id6amcqte3j62c0340qv602.jpg +0 -0
  26. package/public/images/0040jbadgy1id6amdg3xcj62b232wqv502.jpg +0 -0
  27. package/public/images/0040jbadgy1id6amexv23j620v2pau0x02.jpg +0 -0
  28. package/public/images/0040jbadly8icq3if7twwj60qy0qy0uc02.jpg +0 -0
  29. package/public/images/008ET7Legy1ig9s89nri5j30i20n1gn4.jpg +0 -0
  30. package/public/images/008ET7Legy1ig9s8a2lmlj30i20ny409.jpg +0 -0
  31. package/public/images/008ET7Legy1ig9s8ay1hsj30i20nk3zv.jpg +0 -0
  32. package/public/images/008ET7Legy1ig9s8bab6sj30i20o7go1.jpg +0 -0
  33. package/public/images/008ET7Legy1ig9s8blqsxj30i20oiq4r.jpg +0 -0
  34. package/public/images/008ET7Legy1ig9s8cbxkzj30i20mxmzh.jpg +0 -0
  35. package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-399915.jpg +0 -0
  36. package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-424041.jpg +0 -0
  37. package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-588166.jpg +0 -0
  38. package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-672198.jpg +0 -0
  39. package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-700522.jpg +0 -0
  40. package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-839722.jpg +0 -0
  41. package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026825-460586.jpg +0 -0
  42. package/public/images/douyin-update/thumbs//346/212/226/351/237/263202694-689407.jpg +0 -0
  43. package/public/images/douyin-update//346/212/226/351/237/2632026820-399915.mp4 +0 -0
  44. package/public/images/douyin-update//346/212/226/351/237/2632026820-424041.mp4 +0 -0
  45. package/public/images/douyin-update//346/212/226/351/237/2632026820-588166.mp4 +0 -0
  46. package/public/images/douyin-update//346/212/226/351/237/2632026820-672198.mp4 +0 -0
  47. package/public/images/douyin-update//346/212/226/351/237/2632026820-700522.mp4 +0 -0
  48. package/public/images/douyin-update//346/212/226/351/237/2632026820-839722.mp4 +0 -0
  49. package/public/images/douyin-update//346/212/226/351/237/2632026825-460586.mp4 +0 -0
  50. package/public/images/douyin-update//346/212/226/351/237/263202694-689407.mp4 +0 -0
  51. package/public/images/general/0040jbadgy1id6am4yxqfj61s82dn1kx02.jpg +0 -0
  52. package/public/images/general/0040jbadgy1id6am708pbj62c0340qv602.jpg +0 -0
  53. package/public/images/general/0040jbadgy1id6amb81uqj62c0340x6q02.jpg +0 -0
  54. package/public/images/general/0040jbadgy1id6amcqte3j62c0340qv602.jpg +0 -0
  55. package/public/images/general/0040jbadgy1id6amdg3xcj62b232wqv502.jpg +0 -0
  56. package/public/images/general/0040jbadgy1id6amexv23j620v2pau0x02.jpg +0 -0
  57. package/public/images/general/0040jbadly8icq3if7twwj60qy0qy0uc02.jpg +0 -0
  58. package/public/images/general/008ET7Legy1ig9s89nri5j30i20n1gn4.jpg +0 -0
  59. package/public/images/general/008ET7Legy1ig9s8a2lmlj30i20ny409.jpg +0 -0
  60. package/public/images/general/008ET7Legy1ig9s8ay1hsj30i20nk3zv.jpg +0 -0
  61. package/public/images/general/008ET7Legy1ig9s8bab6sj30i20o7go1.jpg +0 -0
  62. package/public/images/general/008ET7Legy1ig9s8blqsxj30i20oiq4r.jpg +0 -0
  63. package/public/images/general/008ET7Legy1ig9s8cbxkzj30i20mxmzh.jpg +0 -0
  64. package/public/images/general/jujingyi_portrait.jpg +0 -0
  65. package/public/images/jujingyi_portrait.jpg +0 -0
  66. package/public/images/series/a-millenniums-wait/poster.jpg +0 -0
  67. package/public/images/series/beauty-of-resilience/poster.jpg +0 -0
  68. package/public/images/series/detective-samoyeds/poster.jpg +0 -0
  69. package/public/images/series/everyday/poster.jpg +0 -0
  70. package/public/images/series/fu-rong-ancient-painting/poster.jpg +0 -0
  71. package/public/images/series/in-a-class-of-her-own/poster.jpg +0 -0
  72. package/public/images/series/in-blossom/poster.jpg +0 -0
  73. package/public/images/series/legend-of-yunxi/poster.jpg +0 -0
  74. package/public/images/series/lets-fight/poster-2.jpg +0 -0
  75. package/public/images/series/lets-fight/poster.jpg +0 -0
  76. package/public/images/series/love-emergency/poster.jpg +0 -0
  77. package/public/images/series/love-under-the-full-moon/poster.jpg +0 -0
  78. package/public/images/series/money-bastion/poster.jpg +0 -0
  79. package/public/images/series/mr-swimmer/poster.jpg +0 -0
  80. package/public/images/series/novoland-castle-in-the-sky/poster.jpg +0 -0
  81. package/public/images/series/please-give-me-a-pair-of-wings/poster.jpg +0 -0
  82. package/public/images/series/rebirth-for-you/poster.jpg +0 -0
  83. package/public/images/series/sword-and-fairy-4/poster.jpg +0 -0
  84. package/public/images/series/the-blooms-at-ruyi-pavilion/poster.jpg +0 -0
  85. package/public/images/series/the-eternal-fragrance/poster-2.jpg +0 -0
  86. package/public/images/series/the-eternal-fragrance/poster.jpg +0 -0
  87. package/public/images/series/the-fallen-flowers/poster.jpg +0 -0
  88. package/public/images/series/the-legend-of-white-snake/poster.jpg +0 -0
  89. package/public/images/series/veil-of-shadows/poster.jpg +0 -0
  90. package/public/images/series/wanhua-sin/poster-2.jpg +0 -0
  91. package/public/images/series/wanhua-sin/poster.jpg +0 -0
  92. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj10yogj62u03s0npe02.jpg +0 -0
  93. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj2pdvyj61li25jkjl02.jpg +0 -0
  94. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj71177j62672w9e8302.jpg +0 -0
  95. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj7twpfj62192197rw02.jpg +0 -0
  96. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj8jfu8j62c03401kx02.jpg +0 -0
  97. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj9dhdsj622k2l77wh02.jpg +0 -0
  98. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkja31s4j62bw3407r802.jpg +0 -0
  99. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjcf331j61402ey1kx02.jpg +0 -0
  100. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjdx8gqj62602pihdt02.jpg +0 -0
  101. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjfvxnzj62432n57wh02.jpg +0 -0
  102. package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixniv2yj627y4fxb2b02.jpg +0 -0
  103. package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixsvbdaj625k2vfu1102.jpg +0 -0
  104. package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixxxdaoj62c03501l202.jpg +0 -0
  105. package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixzeef9j62c0340npd02.jpg +0 -0
  106. package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3iy0vs6tj626t4rbhdu02.jpg +0 -0
  107. package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3iy21wwuj62bt34hx6q02.jpg +0 -0
  108. package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly4wr85j62z629y7wh02.jpg +0 -0
  109. package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly67oilj623u2t5npd02.jpg +0 -0
  110. package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly9qtw8j62c0340u0x02.jpg +0 -0
  111. package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlyem4m4j63402c01kz02.jpg +0 -0
  112. package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlygkoq4j627s2yeavd02.jpg +0 -0
  113. package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlyjh0daj62bw340nm102.jpg +0 -0
  114. package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlylejlhj62bw3401kx02.jpg +0 -0
  115. package/public/images/weibo-update/today/0040jbadgy1igaiaa8b7fj627j2y67wh02.jpg +0 -0
  116. package/public/images/weibo-update/today/0040jbadgy1igaiagw3ezj61s02ebu0x02.jpg +0 -0
  117. package/public/images/weibo-update/today/0040jbadgy1igaiaivng3j61rv2ebx6p02.jpg +0 -0
  118. package/public/images/weibo-update/today/0040jbadgy1igaiajuixvj62bw340qv502.jpg +0 -0
  119. package/public/images/weibo-update/today/0040jbadgy1igaiapyartj62bw340hdt02.jpg +0 -0
  120. package/public/images/weibo-update/today/0040jbadgy1igaibkwu4ij61vy2iq1kx02.jpg +0 -0
  121. package/public/images/weibo-update/today/0040jbadgy1igaic1fvkpj61u92eo1ky02.jpg +0 -0
  122. package/public/images/weibo-update/today/0040jbadgy1igaic3cruhj62822ywb2902.jpg +0 -0
  123. package/public/manifest.json +25 -0
  124. package/public/mcp-logo-128.png +0 -0
  125. package/public/mcp-logo-64.png +0 -0
  126. package/public/mcp-logo.svg +16 -0
  127. package/server/agent/agentCore.js +542 -74
  128. package/server/agent/appPaths.js +1 -0
  129. package/server/agent/audioPlayer.js +4 -2
  130. package/server/agent/citations.js +294 -0
  131. package/server/agent/clarificationAnalytics.js +477 -0
  132. package/server/agent/clarifyingQuestions.js +1089 -0
  133. package/server/agent/cli.js +549 -125
  134. package/server/agent/commandFeatures.js +1 -0
  135. package/server/agent/commandRegistry.js +946 -879
  136. package/server/agent/commands.js +546 -272
  137. package/server/agent/config.js +581 -412
  138. package/server/agent/contextCompactor.js +2 -2
  139. package/server/agent/debate.js +235 -0
  140. package/server/agent/eventBus.js +3 -0
  141. package/server/agent/feedbackLearning.js +217 -0
  142. package/server/agent/llmClient.js +512 -394
  143. package/server/agent/mcpClient.js +182 -32
  144. package/server/agent/memory.js +283 -224
  145. package/server/agent/modelManager.js +79 -39
  146. package/server/agent/profiles.js +178 -0
  147. package/server/agent/publicTools.js +22 -0
  148. package/server/agent/queryDetails.js +68 -8
  149. package/server/agent/quizData.js +398 -384
  150. package/server/agent/research.js +121 -79
  151. package/server/agent/sanitizer.js +104 -13
  152. package/server/agent/session.js +319 -11
  153. package/server/agent/subagent.js +8 -3
  154. package/server/agent/suggestionTui.js +294 -0
  155. package/server/agent/suggestions.js +69 -3
  156. package/server/agent/telemetry.js +119 -0
  157. package/server/agent/terminalImage.js +146 -27
  158. package/server/agent/theme.js +17 -0
  159. package/server/agent/toolRouter.js +16 -1
  160. package/server/agent/tools.js +424 -225
  161. package/server/agent/ui.js +486 -64
  162. package/server/agent/version.js +2 -4
  163. package/server/agent/webSearch.js +111 -11
  164. package/server/agent/youtubeAuth.js +24 -6
  165. package/server/apiRouter.js +4222 -0
  166. package/server/mcp/data/discography.js +25 -16
  167. package/server/mcp/data/gallery.js +308 -20
  168. package/server/mcp/data/profile.js +74 -44
  169. package/server/mcp/data/timeline.js +1 -1
  170. package/server/mcp/data/works.js +51 -17
  171. package/server/mcp/dataAdapter.js +5 -3
  172. package/server/mcp/handler.js +5 -11
  173. package/server/mcp/prompts.js +86 -0
  174. package/server/mcp/security.js +18 -15
  175. package/server/mcp/server.js +7 -2
  176. package/server/mcp/tools.js +66 -2
  177. package/server/web-chat/core/browserLauncher.js +18 -0
  178. package/server/web-chat/core/connection.js +194 -0
  179. package/server/web-chat/core/conversationContext.js +82 -0
  180. package/server/web-chat/core/directCatalogEngine.js +196 -0
  181. package/server/web-chat/core/dramaKnowledge.js +142 -0
  182. package/server/web-chat/core/errorHandler.js +51 -0
  183. package/server/web-chat/core/followUpEngine.js +182 -0
  184. package/server/web-chat/core/futureEventGuard.js +80 -0
  185. package/server/web-chat/core/memorySnapshot.js +12 -0
  186. package/server/web-chat/core/security.js +146 -0
  187. package/server/web-chat/core/toolInspector.js +52 -0
  188. package/server/web-chat/router.js +120 -0
  189. package/server/web-chat/routes/chatRoute.js +397 -0
  190. package/server/web-chat/routes/clarifyConfigRoute.js +98 -0
  191. package/server/web-chat/routes/clarifyRoute.js +83 -0
  192. package/server/web-chat/routes/compareRoute.js +79 -0
  193. package/server/web-chat/routes/exportRoute.js +59 -0
  194. package/server/web-chat/routes/feedbackRoute.js +57 -0
  195. package/server/web-chat/routes/newsRoute.js +33 -0
  196. package/server/web-chat/routes/quizRoute.js +11 -0
  197. package/server/web-chat/routes/staticRoute.js +103 -0
  198. package/server/web-chat/routes/statusRoute.js +52 -0
  199. package/server/web-chat/routes/suggestRoute.js +77 -0
  200. package/server/web-chat/routes/timelineRoute.js +34 -0
  201. package/server/web-chat/routes/verifyRoute.js +57 -0
  202. package/server/web-chat/server.js +116 -0
  203. package/server/web-chat/ui/htmlBuilder.js +91 -0
  204. package/server/web-chat/ui/previews/agentBubble.preview.html +134 -0
  205. package/server/web-chat/ui/scripts/agentBubbleSystem.js +320 -0
  206. package/server/web-chat/ui/scripts/client.js +8397 -0
  207. package/server/web-chat/ui/scripts/kikuDb.js +177 -0
  208. package/server/web-chat/ui/scripts/suggestionsData.js +118 -0
  209. package/server/web-chat/ui/scripts/webchatI18n.js +291 -0
  210. package/server/web-chat/ui/styles/main.css +8568 -0
  211. package/server/web-chat/ui/templates/body.html +796 -0
  212. package/server/webChatHtml.js +1 -0
  213. package/server/webChatServer.js +6 -0
  214. package/src/data/discographyData.js +892 -866
  215. package/src/data/galleryData.js +1805 -561
  216. package/src/data/profileData.js +79 -2
  217. package/src/data/socialUpdatesData.js +106 -47
  218. package/src/data/timelineData.js +1 -1
  219. package/src/data/worksData.js +25 -4
  220. package/tokens.css +97 -0
  221. package/server/agent/updateManager.js +0 -456
@@ -0,0 +1,91 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { CATEGORIZED_SUGGESTIONS, DEFAULT_SUGGESTIONS } from './scripts/suggestionsData.js';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+
9
+ const STYLES_PATH = path.join(__dirname, 'styles', 'main.css');
10
+ const TOKENS_PATH = path.resolve(__dirname, '../../../tokens.css');
11
+ const BODY_PATH = path.join(__dirname, 'templates', 'body.html');
12
+ const AGENT_BUBBLE_SCRIPT_PATH = path.join(__dirname, 'scripts', 'agentBubbleSystem.js');
13
+ const WEBCHAT_I18N_SCRIPT_PATH = path.join(__dirname, 'scripts', 'webchatI18n.js');
14
+ const SCRIPT_PATH = path.join(__dirname, 'scripts', 'client.js');
15
+
16
+ let cachedStyles = null;
17
+ let cachedTokens = null;
18
+ let cachedBody = null;
19
+ let cachedAgentBubbleScript = null;
20
+ let cachedI18nScript = null;
21
+ let cachedScriptTemplate = null;
22
+
23
+ function loadAssets() {
24
+ if (process.env.NODE_ENV === 'production' && cachedTokens && cachedStyles && cachedBody && cachedAgentBubbleScript && cachedI18nScript && cachedScriptTemplate) {
25
+ return {
26
+ tokens: cachedTokens,
27
+ styles: cachedStyles,
28
+ body: cachedBody,
29
+ agentBubbleScript: cachedAgentBubbleScript,
30
+ i18nScript: cachedI18nScript,
31
+ script: cachedScriptTemplate
32
+ };
33
+ }
34
+
35
+ cachedTokens = fs.readFileSync(TOKENS_PATH, 'utf8');
36
+ cachedStyles = fs.readFileSync(STYLES_PATH, 'utf8');
37
+ cachedBody = fs.readFileSync(BODY_PATH, 'utf8');
38
+ cachedAgentBubbleScript = fs.readFileSync(AGENT_BUBBLE_SCRIPT_PATH, 'utf8');
39
+ cachedI18nScript = fs.readFileSync(WEBCHAT_I18N_SCRIPT_PATH, 'utf8');
40
+ cachedScriptTemplate = fs.readFileSync(SCRIPT_PATH, 'utf8');
41
+
42
+ return {
43
+ tokens: cachedTokens,
44
+ styles: cachedStyles,
45
+ body: cachedBody,
46
+ agentBubbleScript: cachedAgentBubbleScript,
47
+ i18nScript: cachedI18nScript,
48
+ script: cachedScriptTemplate
49
+ };
50
+ }
51
+
52
+ export function getWebChatHtml(_port = 3001) {
53
+ const { tokens, styles, body, agentBubbleScript, i18nScript, script } = loadAssets();
54
+ const categoriesJson = JSON.stringify(CATEGORIZED_SUGGESTIONS);
55
+
56
+ const resolvedScript = script.replace(/const ALL_CATEGORIES = [^;]+;/, `const ALL_CATEGORIES = ${categoriesJson};`);
57
+
58
+ return [
59
+ '<!DOCTYPE html>',
60
+ '<html lang="th" data-theme="dark" class="scroll-smooth">',
61
+ '<head>',
62
+ ' <meta charset="UTF-8">',
63
+ ' <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">',
64
+ ' <title>鞠婧祎 (Ju Jingyi) — Official AI Assistant</title>',
65
+ ' <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'><text y=\'.9em\' font-size=\'90\'>🌸</text></svg>" />',
66
+ ' <link rel="preconnect" href="https://fonts.googleapis.com">',
67
+ ' <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>',
68
+ ' <link href="https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;600&family=Noto+Sans+SC:wght@300;400;500;600;700;900&family=Ma+Shan+Zheng&display=swap" rel="stylesheet">',
69
+ ' <link rel="stylesheet" href="/fonts/google-sans-official.css">',
70
+ ' <style>',
71
+ tokens,
72
+ styles,
73
+ ' </style>',
74
+ '</head>',
75
+ '<body>',
76
+ body,
77
+ ' <script>',
78
+ agentBubbleScript,
79
+ ' </script>',
80
+ ' <script>',
81
+ i18nScript,
82
+ ' </script>',
83
+ ' <script>',
84
+ resolvedScript,
85
+ ' </script>',
86
+ '</body>',
87
+ '</html>'
88
+ ].join('\n');
89
+ }
90
+
91
+ export { CATEGORIZED_SUGGESTIONS, DEFAULT_SUGGESTIONS };
@@ -0,0 +1,134 @@
1
+ <!doctype html>
2
+ <html lang="en" data-theme="dark">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Agent Bubble — eight states</title>
7
+ <link rel="stylesheet" href="../../../../tokens.css">
8
+ <link rel="stylesheet" href="../styles/main.css">
9
+ <style>
10
+ html,
11
+ body {
12
+ min-width: 0;
13
+ overflow-x: clip;
14
+ }
15
+
16
+ body.preview-page {
17
+ display: block;
18
+ width: 100%;
19
+ min-height: 100vh;
20
+ height: auto;
21
+ overflow-y: auto;
22
+ padding: var(--space-lg);
23
+ background: var(--color-paper);
24
+ color: var(--color-ink);
25
+ font-family: var(--font-body) !important;
26
+ }
27
+
28
+ .preview-shell {
29
+ width: min(100%, 58rem);
30
+ margin-inline: auto;
31
+ }
32
+
33
+ .preview-heading {
34
+ margin-block-end: var(--space-xl);
35
+ font-family: var(--font-display) !important;
36
+ font-size: var(--text-xl);
37
+ font-style: normal;
38
+ line-height: 1.15;
39
+ overflow-wrap: anywhere;
40
+ }
41
+
42
+ .preview-grid {
43
+ display: grid;
44
+ grid-template-columns: repeat(2, minmax(0, 1fr));
45
+ gap: var(--space-lg);
46
+ }
47
+
48
+ .preview-case {
49
+ min-width: 0;
50
+ }
51
+
52
+ .preview-label {
53
+ display: block;
54
+ margin-block-end: var(--space-xs);
55
+ color: var(--color-muted);
56
+ font-family: var(--font-mono) !important;
57
+ font-size: var(--text-xs);
58
+ text-transform: uppercase;
59
+ letter-spacing: 0.08em;
60
+ }
61
+
62
+ .preview-case .msg-group {
63
+ max-width: 100%;
64
+ }
65
+
66
+ .preview-case .bubble {
67
+ min-height: 7rem;
68
+ }
69
+
70
+ @media (max-width: 48rem) {
71
+ body.preview-page { padding: var(--space-md); }
72
+ .preview-grid { grid-template-columns: minmax(0, 1fr); }
73
+ }
74
+ </style>
75
+ </head>
76
+ <body class="preview-page">
77
+ <main class="preview-shell">
78
+ <h1 class="preview-heading">Agent bubble — eight states</h1>
79
+ <div class="preview-grid" id="preview-grid"></div>
80
+ </main>
81
+
82
+ <script src="../scripts/agentBubbleSystem.js"></script>
83
+ <script>
84
+ const cases = [
85
+ { label: 'Default', status: 'idle', text: 'Ready for a new request.' },
86
+ { label: 'Hover', status: 'idle', forceClass: 'is-hover', text: 'Pointer feedback changes the boundary only.' },
87
+ { label: 'Focus', status: 'idle', forceClass: 'is-focus', text: 'Keyboard focus uses an immediate, visible ring.' },
88
+ { label: 'Active', status: 'streaming', forceClass: 'is-active', text: 'The answer is arriving one part at a time.' },
89
+ { label: 'Disabled', status: 'stopped', forceClass: 'is-disabled', text: 'This response is no longer interactive.', disabled: true },
90
+ { label: 'Loading', status: 'working', text: 'Searching the official works catalog…' },
91
+ { label: 'Error', status: 'error', text: 'The agent could not finish this response. Try again.' },
92
+ { label: 'Success', status: 'done', text: 'The response and its sources are ready.' }
93
+ ];
94
+
95
+ const grid = document.getElementById('preview-grid');
96
+ cases.forEach((item) => {
97
+ const cell = document.createElement('section');
98
+ cell.className = 'preview-case';
99
+
100
+ const label = document.createElement('span');
101
+ label.className = 'preview-label';
102
+ label.textContent = item.label;
103
+
104
+ const group = document.createElement('article');
105
+ group.className = 'msg-group msg-bot' + (item.forceClass ? ' ' + item.forceClass : '');
106
+ if (item.disabled) group.setAttribute('aria-disabled', 'true');
107
+
108
+ const meta = document.createElement('div');
109
+ meta.className = 'msg-meta';
110
+ meta.textContent = '✿ KIKU Research Agent • now';
111
+
112
+ const bubble = document.createElement('div');
113
+ bubble.className = 'bubble bubble-bot';
114
+ bubble.tabIndex = 0;
115
+
116
+ const answer = document.createElement('div');
117
+ answer.className = 'answer-content';
118
+ answer.textContent = item.text;
119
+ bubble.appendChild(answer);
120
+ group.append(meta, bubble);
121
+ cell.append(label, group);
122
+ grid.appendChild(cell);
123
+
124
+ AgentBubbleSystem.mount({
125
+ group,
126
+ bubble,
127
+ language: 'en',
128
+ status: item.status,
129
+ agent: { id: 'research', name: 'KIKU Research Agent', avatar: '✿' }
130
+ });
131
+ });
132
+ </script>
133
+ </body>
134
+ </html>
@@ -0,0 +1,320 @@
1
+ /* Hallmark · component: agent bubble · genre: atmospheric · theme: existing Kiku
2
+ * states: default · hover · focus · active · disabled · loading · error · success
3
+ * contrast: token-backed
4
+ */
5
+ /* Hallmark · pre-emit critique: P5 H4 E4 S5 R4 V4 */
6
+ (function initializeAgentBubbleSystem(globalScope) {
7
+ const STATUS = Object.freeze({
8
+ IDLE: 'idle',
9
+ QUEUED: 'queued',
10
+ THINKING: 'thinking',
11
+ WORKING: 'working',
12
+ STREAMING: 'streaming',
13
+ WAITING: 'waiting',
14
+ DONE: 'done',
15
+ ERROR: 'error',
16
+ STOPPED: 'stopped'
17
+ });
18
+
19
+ const ACTIVE_STATUSES = new Set([
20
+ STATUS.QUEUED,
21
+ STATUS.THINKING,
22
+ STATUS.WORKING,
23
+ STATUS.STREAMING
24
+ ]);
25
+
26
+ const STATUS_LABELS = Object.freeze({
27
+ th: {
28
+ idle: 'พร้อม',
29
+ queued: 'รอเริ่ม',
30
+ thinking: 'กำลังวิเคราะห์',
31
+ working: 'กำลังใช้เครื่องมือ',
32
+ streaming: 'กำลังตอบ',
33
+ waiting: 'รอคำตอบจากคุณ',
34
+ done: 'เสร็จแล้ว',
35
+ error: 'ต้องลองใหม่',
36
+ stopped: 'หยุดแล้ว'
37
+ },
38
+ zh: {
39
+ idle: '就绪',
40
+ queued: '等待开始',
41
+ thinking: '正在分析',
42
+ working: '正在使用工具',
43
+ streaming: '正在回答',
44
+ waiting: '等待您的回复',
45
+ done: '已完成',
46
+ error: '需要重试',
47
+ stopped: '已停止'
48
+ },
49
+ en: {
50
+ idle: 'Ready',
51
+ queued: 'Queued',
52
+ thinking: 'Analyzing',
53
+ working: 'Using tools',
54
+ streaming: 'Answering',
55
+ waiting: 'Waiting for you',
56
+ done: 'Complete',
57
+ error: 'Retry needed',
58
+ stopped: 'Stopped'
59
+ }
60
+ });
61
+
62
+ const stateByGroup = new WeakMap();
63
+ let fallbackId = 0;
64
+
65
+ function makeId(prefix = 'bubble') {
66
+ if (globalScope.crypto && typeof globalScope.crypto.randomUUID === 'function') {
67
+ return prefix + '_' + globalScope.crypto.randomUUID();
68
+ }
69
+ fallbackId += 1;
70
+ return prefix + '_' + Date.now().toString(36) + '_' + fallbackId.toString(36);
71
+ }
72
+
73
+ function normalizeAgent(agent = {}) {
74
+ return {
75
+ id: String(agent.id || agent.key || 'kiku'),
76
+ name: String(agent.name || 'KIKU Agent'),
77
+ avatar: String(agent.avatar || '✿'),
78
+ image: agent.image ? String(agent.image) : ''
79
+ };
80
+ }
81
+
82
+ function createModel(options = {}) {
83
+ return {
84
+ id: String(options.id || makeId('message')),
85
+ role: options.role === 'user' ? 'user' : 'bot',
86
+ agent: normalizeAgent(options.agent),
87
+ status: options.status || (options.role === 'user' ? STATUS.DONE : STATUS.IDLE),
88
+ parts: Array.isArray(options.parts) ? options.parts.slice() : [],
89
+ createdAt: options.createdAt || Date.now(),
90
+ updatedAt: Date.now(),
91
+ error: null
92
+ };
93
+ }
94
+
95
+ function findPart(model, type, predicate = () => true) {
96
+ for (let index = model.parts.length - 1; index >= 0; index -= 1) {
97
+ const part = model.parts[index];
98
+ if (part.type === type && predicate(part)) return part;
99
+ }
100
+ return null;
101
+ }
102
+
103
+ function ensureTextPart(model, type) {
104
+ let part = findPart(model, type);
105
+ if (!part) {
106
+ part = { id: makeId(type), type, content: '' };
107
+ model.parts.push(part);
108
+ }
109
+ return part;
110
+ }
111
+
112
+ function reduceEvent(model, event = {}) {
113
+ const eventAgent = event.agent || (event.agentId ? {
114
+ id: event.agentId,
115
+ name: event.agentName || event.agentId,
116
+ avatar: event.agentAvatar || model.agent.avatar,
117
+ image: event.agentImage || model.agent.image
118
+ } : null);
119
+ const next = {
120
+ ...model,
121
+ agent: eventAgent ? normalizeAgent(eventAgent) : model.agent,
122
+ parts: model.parts.map(part => ({ ...part })),
123
+ updatedAt: Date.now(),
124
+ error: model.error
125
+ };
126
+
127
+ if (event.type === 'queued') {
128
+ next.status = STATUS.QUEUED;
129
+ } else if (event.type === 'reasoning') {
130
+ next.status = STATUS.THINKING;
131
+ ensureTextPart(next, 'reasoning').content += String(event.chunk || '');
132
+ } else if (event.type === 'tool_start') {
133
+ next.status = STATUS.WORKING;
134
+ next.parts.push({
135
+ id: String(event.callId || makeId('tool')),
136
+ type: 'tool',
137
+ name: String(event.name || 'tool'),
138
+ input: event.input,
139
+ result: null,
140
+ status: 'running',
141
+ startedAt: Date.now()
142
+ });
143
+ } else if (event.type === 'tool_result') {
144
+ next.status = STATUS.WORKING;
145
+ let tool = findPart(next, 'tool', part => {
146
+ if (event.callId) return part.id === String(event.callId);
147
+ return part.name === String(event.name || 'tool') && part.status === 'running';
148
+ });
149
+ if (!tool) {
150
+ tool = {
151
+ id: String(event.callId || makeId('tool')),
152
+ type: 'tool',
153
+ name: String(event.name || 'tool'),
154
+ status: 'running'
155
+ };
156
+ next.parts.push(tool);
157
+ }
158
+ tool.status = 'done';
159
+ tool.result = event.result;
160
+ tool.durationMs = event.durationMs;
161
+ tool.finishedAt = Date.now();
162
+ } else if (event.type === 'subagent_progress') {
163
+ next.status = STATUS.WORKING;
164
+ const subject = String(event.subject || '');
165
+ let subagent = null;
166
+ for (let index = next.parts.length - 1; index >= 0; index -= 1) {
167
+ const part = next.parts[index];
168
+ if (part.type === 'subagent' && part.source === String(event.source || 'subagent') &&
169
+ (part.subject || '') === subject && part.status === 'running') {
170
+ subagent = part;
171
+ break;
172
+ }
173
+ }
174
+ if (!subagent || event.kind === 'start') {
175
+ if (subagent && event.kind === 'start') subagent.status = 'done';
176
+ subagent = {
177
+ id: makeId('subagent'),
178
+ type: 'subagent',
179
+ source: String(event.source || 'subagent'),
180
+ subject,
181
+ status: 'running',
182
+ iterations: 0,
183
+ tools: [],
184
+ startedAt: Date.now()
185
+ };
186
+ next.parts.push(subagent);
187
+ }
188
+ if (Number.isFinite(event.iteration)) subagent.iterations = event.iteration;
189
+ if (event.tool && !subagent.tools.includes(String(event.tool))) subagent.tools.push(String(event.tool));
190
+ if (event.kind === 'complete') subagent.status = 'done';
191
+ subagent.updatedAt = Date.now();
192
+ } else if (event.type === 'token') {
193
+ next.status = STATUS.STREAMING;
194
+ ensureTextPart(next, 'text').content += String(event.chunk || '');
195
+ } else if (event.type === 'clarifying_questions') {
196
+ next.status = STATUS.WAITING;
197
+ } else if (event.type === 'final' || event.type === 'done') {
198
+ next.status = STATUS.DONE;
199
+ if (event.answer !== undefined) ensureTextPart(next, 'text').content = String(event.answer || '');
200
+ } else if (event.type === 'error') {
201
+ next.status = STATUS.ERROR;
202
+ next.error = String(event.error || event.message || 'Agent execution failed.');
203
+ next.parts.push({ id: makeId('error'), type: 'error', content: next.error });
204
+ } else if (event.type === 'stopped') {
205
+ next.status = STATUS.STOPPED;
206
+ }
207
+
208
+ return next;
209
+ }
210
+
211
+ function statusLabel(status, language = 'th') {
212
+ const labels = STATUS_LABELS[language] || STATUS_LABELS.en;
213
+ return labels[status] || labels.idle;
214
+ }
215
+
216
+ function updateDom(group, model, language = 'th') {
217
+ if (!group || !model) return;
218
+ const status = model.status || STATUS.IDLE;
219
+ group.dataset.agentId = model.agent.id;
220
+ group.dataset.agentStatus = status;
221
+ group.dataset.messageId = model.id;
222
+ group.setAttribute('aria-busy', String(ACTIVE_STATUSES.has(status)));
223
+
224
+ const meta = group.querySelector('.msg-meta');
225
+ if (meta) {
226
+ const avatarImage = meta.querySelector('.msg-avatar-thumb');
227
+ const agentName = meta.querySelector('.msg-agent-name');
228
+ if (avatarImage && model.agent.image) {
229
+ avatarImage.src = model.agent.image;
230
+ avatarImage.alt = model.agent.name;
231
+ }
232
+ if (agentName) agentName.textContent = model.agent.avatar + ' ' + model.agent.name;
233
+
234
+ let badge = meta.querySelector('.agent-status-badge');
235
+ if (!badge) {
236
+ badge = document.createElement('span');
237
+ badge.className = 'agent-status-badge';
238
+ badge.setAttribute('role', 'status');
239
+ badge.setAttribute('aria-live', 'polite');
240
+ meta.appendChild(badge);
241
+ }
242
+ badge.dataset.status = status;
243
+ badge.textContent = statusLabel(status, language);
244
+ if (status === STATUS.ERROR) {
245
+ badge.setAttribute('role', 'button');
246
+ badge.setAttribute('tabindex', '0');
247
+ badge.title = language === 'th' ? 'คลิกเพื่อลองใหม่อีกครั้ง (Click to retry)' : (language === 'zh' ? '点击重试 (Click to retry)' : 'Click to retry');
248
+ badge.onclick = (e) => {
249
+ e.stopPropagation();
250
+ if (typeof globalScope.regenerateMessage === 'function') {
251
+ globalScope.regenerateMessage(group);
252
+ }
253
+ };
254
+ badge.onkeydown = (e) => {
255
+ if (e.key === 'Enter' || e.key === ' ') {
256
+ e.preventDefault();
257
+ badge.click();
258
+ }
259
+ };
260
+ } else {
261
+ badge.setAttribute('role', 'status');
262
+ badge.removeAttribute('tabindex');
263
+ badge.removeAttribute('title');
264
+ badge.onclick = null;
265
+ badge.onkeydown = null;
266
+ }
267
+ }
268
+
269
+ const bubble = group.querySelector('.bubble');
270
+ if (bubble) {
271
+ bubble.dataset.state = status;
272
+ bubble.setAttribute('aria-label', model.agent.name + ' — ' + statusLabel(status, language));
273
+ }
274
+
275
+ const actions = group.querySelector('.msg-actions');
276
+ if (actions) {
277
+ const ready = status === STATUS.DONE || status === STATUS.ERROR || status === STATUS.STOPPED;
278
+ actions.hidden = !ready;
279
+ actions.setAttribute('aria-hidden', String(!ready));
280
+ actions.querySelectorAll('button, a').forEach(control => {
281
+ control.tabIndex = ready ? 0 : -1;
282
+ });
283
+ }
284
+ }
285
+
286
+ function mount(options = {}) {
287
+ const group = options.group;
288
+ if (!group) throw new TypeError('AgentBubbleSystem.mount requires a message group.');
289
+ const model = createModel(options);
290
+ stateByGroup.set(group, model);
291
+ group.classList.add('agent-bubble-system');
292
+ updateDom(group, model, options.language);
293
+ return model;
294
+ }
295
+
296
+ function dispatch(group, event, language = 'th') {
297
+ const current = stateByGroup.get(group);
298
+ if (!current) return null;
299
+ const next = reduceEvent(current, event);
300
+ stateByGroup.set(group, next);
301
+ updateDom(group, next, language);
302
+ group.dispatchEvent(new CustomEvent('agentbubblechange', { detail: next }));
303
+ return next;
304
+ }
305
+
306
+ function getState(group) {
307
+ return stateByGroup.get(group) || null;
308
+ }
309
+
310
+ globalScope.AgentBubbleSystem = Object.freeze({
311
+ STATUS,
312
+ STATUS_LABELS,
313
+ createModel,
314
+ reduceEvent,
315
+ mount,
316
+ dispatch,
317
+ getState,
318
+ statusLabel
319
+ });
320
+ }(typeof window !== 'undefined' ? window : globalThis));