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.
- package/README.md +300 -495
- package/package.json +12 -11
- package/public/fonts/GoogleSans-Bold.ttf +0 -0
- package/public/fonts/GoogleSans-Bold.woff2 +0 -0
- package/public/fonts/GoogleSans-BoldItalic.ttf +0 -0
- package/public/fonts/GoogleSans-Italic.ttf +0 -0
- package/public/fonts/GoogleSans-Latin-400.woff2 +0 -0
- package/public/fonts/GoogleSans-Latin-500.woff2 +0 -0
- package/public/fonts/GoogleSans-Latin-600.woff2 +0 -0
- package/public/fonts/GoogleSans-Latin-700.woff2 +0 -0
- package/public/fonts/GoogleSans-Medium.ttf +0 -0
- package/public/fonts/GoogleSans-Medium.woff2 +0 -0
- package/public/fonts/GoogleSans-Regular.ttf +0 -0
- package/public/fonts/GoogleSans-Regular.woff2 +0 -0
- package/public/fonts/GoogleSans-Thai-400.woff2 +0 -0
- package/public/fonts/GoogleSans-Thai-500.woff2 +0 -0
- package/public/fonts/GoogleSans-Thai-600.woff2 +0 -0
- package/public/fonts/GoogleSans-Thai-700.woff2 +0 -0
- package/public/fonts/NotoSansThai-Bold.woff2 +0 -0
- package/public/fonts/NotoSansThai-Regular.woff2 +0 -0
- package/public/fonts/google-sans-official.css +900 -0
- package/public/images/0040jbadgy1id6am4yxqfj61s82dn1kx02.jpg +0 -0
- package/public/images/0040jbadgy1id6am708pbj62c0340qv602.jpg +0 -0
- package/public/images/0040jbadgy1id6amb81uqj62c0340x6q02.jpg +0 -0
- package/public/images/0040jbadgy1id6amcqte3j62c0340qv602.jpg +0 -0
- package/public/images/0040jbadgy1id6amdg3xcj62b232wqv502.jpg +0 -0
- package/public/images/0040jbadgy1id6amexv23j620v2pau0x02.jpg +0 -0
- package/public/images/0040jbadly8icq3if7twwj60qy0qy0uc02.jpg +0 -0
- package/public/images/008ET7Legy1ig9s89nri5j30i20n1gn4.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8a2lmlj30i20ny409.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8ay1hsj30i20nk3zv.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8bab6sj30i20o7go1.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8blqsxj30i20oiq4r.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8cbxkzj30i20mxmzh.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-399915.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-424041.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-588166.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-672198.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-700522.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-839722.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026825-460586.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/263202694-689407.jpg +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-399915.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-424041.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-588166.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-672198.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-700522.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-839722.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026825-460586.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/263202694-689407.mp4 +0 -0
- package/public/images/general/0040jbadgy1id6am4yxqfj61s82dn1kx02.jpg +0 -0
- package/public/images/general/0040jbadgy1id6am708pbj62c0340qv602.jpg +0 -0
- package/public/images/general/0040jbadgy1id6amb81uqj62c0340x6q02.jpg +0 -0
- package/public/images/general/0040jbadgy1id6amcqte3j62c0340qv602.jpg +0 -0
- package/public/images/general/0040jbadgy1id6amdg3xcj62b232wqv502.jpg +0 -0
- package/public/images/general/0040jbadgy1id6amexv23j620v2pau0x02.jpg +0 -0
- package/public/images/general/0040jbadly8icq3if7twwj60qy0qy0uc02.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s89nri5j30i20n1gn4.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8a2lmlj30i20ny409.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8ay1hsj30i20nk3zv.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8bab6sj30i20o7go1.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8blqsxj30i20oiq4r.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8cbxkzj30i20mxmzh.jpg +0 -0
- package/public/images/general/jujingyi_portrait.jpg +0 -0
- package/public/images/jujingyi_portrait.jpg +0 -0
- package/public/images/series/a-millenniums-wait/poster.jpg +0 -0
- package/public/images/series/beauty-of-resilience/poster.jpg +0 -0
- package/public/images/series/detective-samoyeds/poster.jpg +0 -0
- package/public/images/series/everyday/poster.jpg +0 -0
- package/public/images/series/fu-rong-ancient-painting/poster.jpg +0 -0
- package/public/images/series/in-a-class-of-her-own/poster.jpg +0 -0
- package/public/images/series/in-blossom/poster.jpg +0 -0
- package/public/images/series/legend-of-yunxi/poster.jpg +0 -0
- package/public/images/series/lets-fight/poster-2.jpg +0 -0
- package/public/images/series/lets-fight/poster.jpg +0 -0
- package/public/images/series/love-emergency/poster.jpg +0 -0
- package/public/images/series/love-under-the-full-moon/poster.jpg +0 -0
- package/public/images/series/money-bastion/poster.jpg +0 -0
- package/public/images/series/mr-swimmer/poster.jpg +0 -0
- package/public/images/series/novoland-castle-in-the-sky/poster.jpg +0 -0
- package/public/images/series/please-give-me-a-pair-of-wings/poster.jpg +0 -0
- package/public/images/series/rebirth-for-you/poster.jpg +0 -0
- package/public/images/series/sword-and-fairy-4/poster.jpg +0 -0
- package/public/images/series/the-blooms-at-ruyi-pavilion/poster.jpg +0 -0
- package/public/images/series/the-eternal-fragrance/poster-2.jpg +0 -0
- package/public/images/series/the-eternal-fragrance/poster.jpg +0 -0
- package/public/images/series/the-fallen-flowers/poster.jpg +0 -0
- package/public/images/series/the-legend-of-white-snake/poster.jpg +0 -0
- package/public/images/series/veil-of-shadows/poster.jpg +0 -0
- package/public/images/series/wanhua-sin/poster-2.jpg +0 -0
- package/public/images/series/wanhua-sin/poster.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj10yogj62u03s0npe02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj2pdvyj61li25jkjl02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj71177j62672w9e8302.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj7twpfj62192197rw02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj8jfu8j62c03401kx02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj9dhdsj622k2l77wh02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkja31s4j62bw3407r802.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjcf331j61402ey1kx02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjdx8gqj62602pihdt02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjfvxnzj62432n57wh02.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixniv2yj627y4fxb2b02.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixsvbdaj625k2vfu1102.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixxxdaoj62c03501l202.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixzeef9j62c0340npd02.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3iy0vs6tj626t4rbhdu02.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3iy21wwuj62bt34hx6q02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly4wr85j62z629y7wh02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly67oilj623u2t5npd02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly9qtw8j62c0340u0x02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlyem4m4j63402c01kz02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlygkoq4j627s2yeavd02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlyjh0daj62bw340nm102.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlylejlhj62bw3401kx02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiaa8b7fj627j2y67wh02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiagw3ezj61s02ebu0x02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiaivng3j61rv2ebx6p02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiajuixvj62bw340qv502.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiapyartj62bw340hdt02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaibkwu4ij61vy2iq1kx02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaic1fvkpj61u92eo1ky02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaic3cruhj62822ywb2902.jpg +0 -0
- package/public/manifest.json +25 -0
- package/public/mcp-logo-128.png +0 -0
- package/public/mcp-logo-64.png +0 -0
- package/public/mcp-logo.svg +16 -0
- package/server/agent/agentCore.js +542 -74
- package/server/agent/appPaths.js +1 -0
- package/server/agent/audioPlayer.js +4 -2
- package/server/agent/citations.js +294 -0
- package/server/agent/clarificationAnalytics.js +477 -0
- package/server/agent/clarifyingQuestions.js +1089 -0
- package/server/agent/cli.js +549 -125
- package/server/agent/commandFeatures.js +1 -0
- package/server/agent/commandRegistry.js +946 -879
- package/server/agent/commands.js +546 -272
- package/server/agent/config.js +581 -412
- package/server/agent/contextCompactor.js +2 -2
- package/server/agent/debate.js +235 -0
- package/server/agent/eventBus.js +3 -0
- package/server/agent/feedbackLearning.js +217 -0
- package/server/agent/llmClient.js +512 -394
- package/server/agent/mcpClient.js +182 -32
- package/server/agent/memory.js +283 -224
- package/server/agent/modelManager.js +79 -39
- package/server/agent/profiles.js +178 -0
- package/server/agent/publicTools.js +22 -0
- package/server/agent/queryDetails.js +68 -8
- package/server/agent/quizData.js +398 -384
- package/server/agent/research.js +121 -79
- package/server/agent/sanitizer.js +104 -13
- package/server/agent/session.js +319 -11
- package/server/agent/subagent.js +8 -3
- package/server/agent/suggestionTui.js +294 -0
- package/server/agent/suggestions.js +69 -3
- package/server/agent/telemetry.js +119 -0
- package/server/agent/terminalImage.js +146 -27
- package/server/agent/theme.js +17 -0
- package/server/agent/toolRouter.js +16 -1
- package/server/agent/tools.js +424 -225
- package/server/agent/ui.js +486 -64
- package/server/agent/version.js +2 -4
- package/server/agent/webSearch.js +111 -11
- package/server/agent/youtubeAuth.js +24 -6
- package/server/apiRouter.js +4222 -0
- package/server/mcp/data/discography.js +25 -16
- package/server/mcp/data/gallery.js +308 -20
- package/server/mcp/data/profile.js +74 -44
- package/server/mcp/data/timeline.js +1 -1
- package/server/mcp/data/works.js +51 -17
- package/server/mcp/dataAdapter.js +5 -3
- package/server/mcp/handler.js +5 -11
- package/server/mcp/prompts.js +86 -0
- package/server/mcp/security.js +18 -15
- package/server/mcp/server.js +7 -2
- package/server/mcp/tools.js +66 -2
- package/server/web-chat/core/browserLauncher.js +18 -0
- package/server/web-chat/core/connection.js +194 -0
- package/server/web-chat/core/conversationContext.js +82 -0
- package/server/web-chat/core/directCatalogEngine.js +196 -0
- package/server/web-chat/core/dramaKnowledge.js +142 -0
- package/server/web-chat/core/errorHandler.js +51 -0
- package/server/web-chat/core/followUpEngine.js +182 -0
- package/server/web-chat/core/futureEventGuard.js +80 -0
- package/server/web-chat/core/memorySnapshot.js +12 -0
- package/server/web-chat/core/security.js +146 -0
- package/server/web-chat/core/toolInspector.js +52 -0
- package/server/web-chat/router.js +120 -0
- package/server/web-chat/routes/chatRoute.js +397 -0
- package/server/web-chat/routes/clarifyConfigRoute.js +98 -0
- package/server/web-chat/routes/clarifyRoute.js +83 -0
- package/server/web-chat/routes/compareRoute.js +79 -0
- package/server/web-chat/routes/exportRoute.js +59 -0
- package/server/web-chat/routes/feedbackRoute.js +57 -0
- package/server/web-chat/routes/newsRoute.js +33 -0
- package/server/web-chat/routes/quizRoute.js +11 -0
- package/server/web-chat/routes/staticRoute.js +103 -0
- package/server/web-chat/routes/statusRoute.js +52 -0
- package/server/web-chat/routes/suggestRoute.js +77 -0
- package/server/web-chat/routes/timelineRoute.js +34 -0
- package/server/web-chat/routes/verifyRoute.js +57 -0
- package/server/web-chat/server.js +116 -0
- package/server/web-chat/ui/htmlBuilder.js +91 -0
- package/server/web-chat/ui/previews/agentBubble.preview.html +134 -0
- package/server/web-chat/ui/scripts/agentBubbleSystem.js +320 -0
- package/server/web-chat/ui/scripts/client.js +8397 -0
- package/server/web-chat/ui/scripts/kikuDb.js +177 -0
- package/server/web-chat/ui/scripts/suggestionsData.js +118 -0
- package/server/web-chat/ui/scripts/webchatI18n.js +291 -0
- package/server/web-chat/ui/styles/main.css +8568 -0
- package/server/web-chat/ui/templates/body.html +796 -0
- package/server/webChatHtml.js +1 -0
- package/server/webChatServer.js +6 -0
- package/src/data/discographyData.js +892 -866
- package/src/data/galleryData.js +1805 -561
- package/src/data/profileData.js +79 -2
- package/src/data/socialUpdatesData.js +106 -47
- package/src/data/timelineData.js +1 -1
- package/src/data/worksData.js +25 -4
- package/tokens.css +97 -0
- 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));
|