kiku-agent 1.2.0 → 1.3.1
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 +459 -407
- package/package.json +32 -3
- package/server/agent/agentCore.js +635 -267
- package/server/agent/appPaths.js +2 -0
- package/server/agent/catalogEntityResolver.js +637 -0
- package/server/agent/citations.js +0 -21
- package/server/agent/clarificationAnalytics.js +1 -1
- package/server/agent/clarifyingQuestions.js +286 -216
- package/server/agent/cli.js +532 -73
- package/server/agent/commandFeatures.js +123 -24
- package/server/agent/commandRegistry.js +128 -62
- package/server/agent/commands.js +1410 -95
- package/server/agent/config.js +253 -163
- package/server/agent/contextCompactor.js +239 -25
- package/server/agent/dynamicCatalog.js +192 -0
- package/server/agent/envBootstrap.js +46 -0
- package/server/agent/envManager.js +317 -0
- package/server/agent/eventBus.js +3 -3
- package/server/agent/feedbackLearning.js +0 -8
- package/server/agent/gpuMetrics.js +188 -55
- package/server/agent/ingestionWorker.js +339 -0
- package/server/agent/ink/App.jsx +348 -0
- package/server/agent/ink/Panel.jsx +54 -0
- package/server/agent/ink/PromptInput.jsx +321 -0
- package/server/agent/ink/TaskPlanPanel.jsx +88 -0
- package/server/agent/ink/ThinkingPanel.jsx +68 -0
- package/server/agent/ink/ToolResult.jsx +133 -0
- package/server/agent/ink/main.jsx +195 -0
- package/server/agent/ink/useEventBus.js +218 -0
- package/server/agent/lanDiscovery.js +369 -0
- package/server/agent/llmClient.js +111 -39
- package/server/agent/lmStudioWakeup.js +144 -0
- package/server/agent/mcpClient.js +2 -2
- package/server/agent/memory/contextAllocator.js +214 -0
- package/server/agent/memory/hierarchicalStore.js +523 -0
- package/server/agent/memory/reflectionWorker.js +110 -0
- package/server/agent/memory.js +635 -494
- package/server/agent/modelConfig.js +487 -0
- package/server/agent/newsIntelligence.js +219 -0
- package/server/agent/newsWatch.js +85 -0
- package/server/agent/prompts.js +146 -0
- package/server/agent/publicTools.js +7 -0
- package/server/agent/queryDetails.js +62 -1
- package/server/agent/research.js +314 -35
- package/server/agent/routingInterceptor.js +77 -0
- package/server/agent/rumoredProjects.js +15 -0
- package/server/agent/sanitizer.js +117 -17
- package/server/agent/subagent.js +52 -9
- package/server/agent/suggestionTui.js +33 -13
- package/server/agent/suggestions.js +29 -13
- package/server/agent/telemetry/hardwareTiers.js +332 -0
- package/server/agent/telemetry/runtimeWatchdog.js +66 -0
- package/server/agent/telemetry/techPowerUpClient.js +682 -0
- package/server/agent/telemetry/techpowerup-mcp.json +22 -0
- package/server/agent/telemetry.js +0 -11
- package/server/agent/terminalImage.js +0 -1
- package/server/agent/textToolProtocol.js +190 -0
- package/server/agent/tokenTracker.js +220 -0
- package/server/agent/toolRouter.js +362 -216
- package/server/agent/tools.js +1155 -194
- package/server/agent/ui.js +144 -41
- package/server/agent/vramService.js +572 -0
- package/server/agent/webSearch.js +2279 -273
- package/server/agent/websearch/adapters/duckduckgo.js +97 -0
- package/server/agent/websearch/adapters/jina.js +77 -0
- package/server/agent/websearch/adapters/searxng.js +57 -0
- package/server/agent/websearch/core/canonical.js +89 -0
- package/server/agent/websearch/core/fusion.js +81 -0
- package/server/agent/websearch/core/types.js +29 -0
- package/server/agent/websearch/extract.js +605 -0
- package/server/agent/websearch/index.js +43 -0
- package/server/agent/websearch/orchestrator.js +266 -0
- package/server/agent/websearch/resilience/breaker.js +97 -0
- package/server/agent/websearch/resilience/cache.js +59 -0
- package/server/agent/websearch/resilience/limiter.js +93 -0
- package/server/agent/websearch/security/filter.js +36 -0
- package/server/agent/websearch/security/ssrf.js +289 -0
- package/server/agent/websearch/telemetry.js +71 -0
- package/server/agent/youtubeAuth.js +82 -73
- package/server/agent/youtubeTranscript.js +209 -0
- package/server/apiRouter.js +390 -190
- package/server/mcp/data/brands.js +5 -5
- package/server/mcp/data/discography.js +56 -45
- package/server/mcp/data/gallery.js +9 -0
- package/server/mcp/data/index.js +1 -0
- package/server/mcp/data/industryIndex.js +251 -0
- package/server/mcp/data/profile.js +5 -5
- package/server/mcp/data/timeline.js +2 -2
- package/server/mcp/data/works.js +2 -2
- package/server/mcp/handler.js +0 -2
- package/server/mcp/prompts.js +42 -2
- package/server/mcp/resources.js +5 -1
- package/server/mcp/security.js +25 -14
- package/server/mcp/server.js +92 -3
- package/server/mcp/tools.js +20 -11
- package/server/routes/agent.js +55 -0
- package/server/web-chat/core/connection.js +218 -194
- package/server/web-chat/core/conversationContext.js +1 -1
- package/server/web-chat/core/directCatalogEngine.js +190 -184
- package/server/web-chat/core/followUpEngine.js +88 -11
- package/server/web-chat/core/security.js +7 -4
- package/server/web-chat/router.js +54 -17
- package/server/web-chat/routes/bilibiliRoute.js +34 -0
- package/server/web-chat/routes/chatRoute.js +488 -397
- package/server/web-chat/routes/clarifyConfigRoute.js +1 -2
- package/server/web-chat/routes/exportRoute.js +2 -1
- package/server/web-chat/routes/lanDiscoveryRoute.js +90 -0
- package/server/web-chat/routes/newsRoute.js +36 -6
- package/server/web-chat/routes/providerRoute.js +87 -0
- package/server/web-chat/routes/staticRoute.js +61 -13
- package/server/web-chat/routes/statusRoute.js +23 -2
- package/server/web-chat/routes/vramRoute.js +22 -0
- package/server/web-chat/server.js +135 -106
- package/server/web-chat/ui/htmlBuilder.js +8 -8
- package/server/web-chat/ui/previews/modelSettings.preview.html +41 -0
- package/server/web-chat/ui/scripts/agentBubbleSystem.js +0 -5
- package/server/web-chat/ui/scripts/client.js +10526 -8397
- package/server/web-chat/ui/scripts/webchatI18n.js +190 -138
- package/server/web-chat/ui/styles/main.css +7471 -4246
- package/server/web-chat/ui/templates/body.html +305 -179
- package/src/data/brandsData.js +29 -6
- package/src/data/discographyData.js +918 -900
- package/src/data/galleryData.js +66 -0
- package/src/data/industryIndexData.js +292 -0
- package/src/data/musicVideoData.js +48 -5
- package/src/data/profileData.js +15 -1
- package/src/data/timelineData.js +4 -3
- package/src/data/worksData.js +536 -535
- package/src/lib/vram/estimator.js +464 -0
- package/src/lib/vram/modelRegistry.js +387 -0
- package/tokens.css +33 -2
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import { eventBus, Events } from './eventBus.js';
|
|
4
4
|
|
|
5
|
-
// Languages supported by the clarifying-question system. Add a new code here
|
|
6
|
-
// and fill its entries in the per-language `questions` / `options` maps below
|
|
7
|
-
// to extend coverage.
|
|
8
5
|
export const SUPPORTED_LANGUAGES = ['en', 'th', 'zh', 'ja', 'ko'];
|
|
9
6
|
|
|
10
7
|
const LANGUAGE_ALIASES = {
|
|
@@ -23,8 +20,6 @@ const LANGUAGE_ALIASES = {
|
|
|
23
20
|
'ko-kr': 'ko',
|
|
24
21
|
};
|
|
25
22
|
|
|
26
|
-
// Normalize any user/system supplied language tag (e.g. "JP", "zh-TW") to a
|
|
27
|
-
// supported code; unknown tags fall back to English.
|
|
28
23
|
export function normalizeLang(lang, fallback = 'en') {
|
|
29
24
|
if (!lang || typeof lang !== 'string') return fallback;
|
|
30
25
|
const key = lang.trim().toLowerCase().replace('_', '-');
|
|
@@ -247,7 +242,6 @@ const AMBIGUITY_PATTERNS = [
|
|
|
247
242
|
}
|
|
248
243
|
];
|
|
249
244
|
|
|
250
|
-
// Resolve a pattern's question + options for a given language with English fallback.
|
|
251
245
|
function localizePattern(pattern, lang) {
|
|
252
246
|
const questions = pattern.questions || {};
|
|
253
247
|
const options = pattern.options || {};
|
|
@@ -259,6 +253,15 @@ function localizePattern(pattern, lang) {
|
|
|
259
253
|
};
|
|
260
254
|
}
|
|
261
255
|
|
|
256
|
+
const KNOWN_WORKS_OR_CHARACTERS_REGEX = /(花间令|花間令|In Blossom|ฮวาเจียนลิ่ง|芸汐传|芸汐伝|Yunxi|หยุนซี|新白娘子传奇|White Snake|นางพญางูขาว|如意芳霏|The Blooms at Ruyi Pavilion|กรุ่นรักกลิ่นบุปผา|嘉南传|嘉南傳|Rebirth For You|มู่หนานจือ|仙剑四|仙劍四|Sword and Fairy|เซียนกระบี่|杨采薇|上官芷|หยางไฉ่เวย|ซ่างกวนจื่อ|韩芸汐|หานหยุนซี|白素贞|ไป๋ซู่เจิน|傅容|ฟู่หรง|姜保宁|เจียงเป่าหนิง|韩菱纱|หานหลิงซา)/i;
|
|
257
|
+
|
|
258
|
+
const SPECIFICITY_PATTERNS = [
|
|
259
|
+
/\b(exactly|specifically|precisely|particular)\b/i,
|
|
260
|
+
/\b\d{4}\b/,
|
|
261
|
+
/["'"]/,
|
|
262
|
+
/\b(by|from|in|at|on)\b.*\b(Ju Jingyi|鞠婧祎|จวีจิ้งอี)\b/i,
|
|
263
|
+
];
|
|
264
|
+
|
|
262
265
|
export function calculateConfidence(query, detectedAmbiguities = []) {
|
|
263
266
|
if (!query || typeof query !== 'string') {
|
|
264
267
|
return { score: 0, level: 'very_low', shouldAsk: true };
|
|
@@ -267,8 +270,6 @@ export function calculateConfidence(query, detectedAmbiguities = []) {
|
|
|
267
270
|
const trimmed = query.trim();
|
|
268
271
|
let score = 1.0;
|
|
269
272
|
|
|
270
|
-
// CJK scripts do not separate words with spaces, so estimate word count from
|
|
271
|
-
// character length instead to avoid unfairly penalizing zh/ja/ko queries.
|
|
272
273
|
const cjkChars = (trimmed.match(/[\u3040-\u30FF\u3400-\u9FFF\uAC00-\uD7AF]/g) || []).length;
|
|
273
274
|
const wordCount = cjkChars > 0
|
|
274
275
|
? Math.max(1, Math.round(cjkChars / 2))
|
|
@@ -277,8 +278,7 @@ export function calculateConfidence(query, detectedAmbiguities = []) {
|
|
|
277
278
|
else if (wordCount < 5) score -= 0.1;
|
|
278
279
|
else if (wordCount > 15) score += 0.1;
|
|
279
280
|
|
|
280
|
-
|
|
281
|
-
if (knownWorksOrCharacters.test(trimmed)) {
|
|
281
|
+
if (KNOWN_WORKS_OR_CHARACTERS_REGEX.test(trimmed)) {
|
|
282
282
|
return {
|
|
283
283
|
score: 0.95,
|
|
284
284
|
level: 'high',
|
|
@@ -287,14 +287,7 @@ export function calculateConfidence(query, detectedAmbiguities = []) {
|
|
|
287
287
|
};
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
-
const
|
|
291
|
-
/\b(exactly|specifically|precisely|particular)\b/i,
|
|
292
|
-
/\b\d{4}\b/,
|
|
293
|
-
/["'"]/,
|
|
294
|
-
/\b(by|from|in|at|on)\b.*\b(Ju Jingyi|鞠婧祎|จวีจิ้งอี)\b/i,
|
|
295
|
-
];
|
|
296
|
-
|
|
297
|
-
for (const pattern of specificityPatterns) {
|
|
290
|
+
for (const pattern of SPECIFICITY_PATTERNS) {
|
|
298
291
|
if (pattern.test(trimmed)) score += 0.15;
|
|
299
292
|
}
|
|
300
293
|
|
|
@@ -346,8 +339,6 @@ export function detectAmbiguities(query, detectedLang = 'en') {
|
|
|
346
339
|
return detected;
|
|
347
340
|
}
|
|
348
341
|
|
|
349
|
-
// Known Ju Jingyi works/characters so clarifying questions can reference the
|
|
350
|
-
// actual subject of the user's query instead of generic facet questions.
|
|
351
342
|
const KNOWN_ENTITY_PATTERNS = [
|
|
352
343
|
{
|
|
353
344
|
key: 'in_blossom',
|
|
@@ -392,12 +383,22 @@ export function stripSystemDirectives(text) {
|
|
|
392
383
|
|
|
393
384
|
const KNOWN_ENTITY_REGEX = /(花间令|花間令|In Blossom|อินบลอสซัม|ฮวาเจียนหลิง|芸汐传|芸汐伝|Yunxi|ตำนานหยุนซี|หยุนซี|仙剑四|仙劍四|Sword and Fairy|เซียนกระบี่|新白娘子传奇|White Snake|นางพญางูขาว|如意芳霏|Ruyi Pavilion|กรุ่นรักกลิ่นบุปผา|嘉南传|嘉南傳|Rebirth For You|มู่หนานจือ|SNH48)/i;
|
|
394
385
|
|
|
395
|
-
|
|
386
|
+
const ENTITY_REGEX_MAP = {
|
|
387
|
+
in_blossom: /(?:in_blossom|花间令|花間令|In Blossom|อินบลอสซัม|ฮวาเจียนหลิง)/i,
|
|
388
|
+
legend_of_yunxi: /(?:legend_of_yunxi|芸汐传|芸汐伝|Yunxi|ตำนานหยุนซี|หยุนซี)/i,
|
|
389
|
+
sword_fairy_4: /(?:sword_fairy_4|仙剑四|仙劍四|Sword and Fairy|เซียนกระบี่)/i,
|
|
390
|
+
white_snake: /(?:white_snake|新白娘子传奇|White Snake|นางพญางูขาว)/i,
|
|
391
|
+
ruyi_pavilion: /(?:ruyi_pavilion|如意芳霏|Ruyi Pavilion|กรุ่นรักกลิ่นบุปผา)/i,
|
|
392
|
+
rebirth_for_you: /(?:rebirth_for_you|嘉南传|嘉南傳|Rebirth For You|มู่หนานจือ)/i,
|
|
393
|
+
snh48: /(?:snh48|SNH48)/i,
|
|
394
|
+
};
|
|
395
|
+
|
|
396
396
|
function extractQueryTopic(query, lang) {
|
|
397
397
|
if (!query || typeof query !== 'string') return null;
|
|
398
398
|
const clean = stripSystemDirectives(query);
|
|
399
399
|
const hit = KNOWN_ENTITY_PATTERNS.find(p => {
|
|
400
|
-
|
|
400
|
+
const rx = ENTITY_REGEX_MAP[p.key];
|
|
401
|
+
return rx ? rx.test(clean) : false;
|
|
401
402
|
});
|
|
402
403
|
if (hit) return (hit.labels && hit.labels[lang]) || hit.labels.en;
|
|
403
404
|
const rawMatch = clean.match(KNOWN_ENTITY_REGEX);
|
|
@@ -422,7 +423,6 @@ function escapeRegex(str) {
|
|
|
422
423
|
return String(str).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
423
424
|
}
|
|
424
425
|
|
|
425
|
-
// Extract a short noun phrase that likely represents the topic (fallback when no known entity).
|
|
426
426
|
function extractQueryPhrase(query) {
|
|
427
427
|
if (!query || typeof query !== 'string') return '';
|
|
428
428
|
const cleaned = stripSystemDirectives(query)
|
|
@@ -447,8 +447,6 @@ function synthesizeDynamicQuestions(query, lang = 'en', detectedAmbiguities = []
|
|
|
447
447
|
|
|
448
448
|
const topic = extractQueryTopic(query, lang) || extractQueryPhrase(query) || null;
|
|
449
449
|
|
|
450
|
-
// 1) Subject-aware questions that reference the actual topic the user asked about,
|
|
451
|
-
// so the clarifying questions feel directly connected to the query.
|
|
452
450
|
if (topic) {
|
|
453
451
|
const subject = String(topic).trim();
|
|
454
452
|
if (!addedCategories.has('subject_focus') && results.length < maxQuestions) {
|
|
@@ -524,154 +522,219 @@ function synthesizeDynamicQuestions(query, lang = 'en', detectedAmbiguities = []
|
|
|
524
522
|
}
|
|
525
523
|
}
|
|
526
524
|
|
|
527
|
-
|
|
525
|
+
const clean = stripSystemDirectives(query);
|
|
526
|
+
const isMusic = /(เพลง|ร้องเพลง|ดนตรี|ซิงเกิล|อัลบั้ม|สเตจ|คอนเสิร์ต|ost|song|music|single|album|concert|stage|find the way|0\.2s|曲|歌|음악|노래)/i.test(clean);
|
|
527
|
+
const isDrama = /(ซีรีส์|ละคร|หนัง|ภาพยนตร์|การแสดง|บทบาท|ตัวละคร|เรื่อง|ฮวาเจียน|หยุนซี|งูขาว|仙剑|花间令|芸汐|drama|series|movie|acting|character|role|in blossom|yunxi)/i.test(clean);
|
|
528
|
+
const isFashion = /(แบรนด์|พรีเซนเตอร์|แอมบาสเดอร์|แฟชั่น|เครื่องประดับ|ชุด|แต่งหน้า|เมคอัพ|fashion|brand|ambassador|fred|versace|prsr|makeup)/i.test(clean);
|
|
529
|
+
|
|
528
530
|
if (Array.isArray(detectedAmbiguities) && detectedAmbiguities.length > 0) {
|
|
529
531
|
for (const amb of detectedAmbiguities) {
|
|
530
532
|
if (amb.category && amb.question && amb.options && !addedCategories.has(amb.category)) {
|
|
533
|
+
|
|
534
|
+
if (amb.category === 'media_type' && ((isMusic && !isDrama) || (isDrama && !isMusic))) {
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
531
537
|
addQ(amb.category, amb.question, (amb.options || []).slice(0, 4));
|
|
532
538
|
}
|
|
533
539
|
}
|
|
534
540
|
}
|
|
535
541
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
'
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
'
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
'
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
542
|
+
if (isMusic && !isDrama) {
|
|
543
|
+
if (lang === 'th') {
|
|
544
|
+
addQ('music_aspect', 'สนใจผลงานเพลงแนวไหนเป็นพิเศษคะ?', [
|
|
545
|
+
'เพลงประกอบซีรีส์ (OST) ซึ้งๆ',
|
|
546
|
+
'ซิงเกิลเดี่ยวแนวป๊อป/แดนซ์',
|
|
547
|
+
'สเตจแสดงสดไวรัล',
|
|
548
|
+
'ผลงานเพลงทั้งหมด'
|
|
549
|
+
]);
|
|
550
|
+
addQ('stage_vibe', 'อยากเจาะลึกเพลงฮิตช่วงไหนคะ?', [
|
|
551
|
+
'ซิงเกิลใหม่ล่าสุด (0.2s, FIND THE WAY)',
|
|
552
|
+
'เพลงคลาสสิกสร้างชื่อ (叹云兮, 恋爱告急)',
|
|
553
|
+
'เพลงที่มีสเตจเต้นไวรัล'
|
|
554
|
+
]);
|
|
555
|
+
} else if (lang === 'zh') {
|
|
556
|
+
addQ('music_aspect', '想了解哪种风格的音乐作品?', [
|
|
557
|
+
'经典影视原声 (OST)',
|
|
558
|
+
'个人 Solo 唱跳单曲',
|
|
559
|
+
'出圈神级现场舞台',
|
|
560
|
+
'全部音乐作品'
|
|
561
|
+
]);
|
|
562
|
+
addQ('stage_vibe', '想聚焦哪个时期的音乐?', [
|
|
563
|
+
'最新单曲 (0.2s, FIND THE WAY)',
|
|
564
|
+
'经典代表作 (叹云兮, 恋爱告急)',
|
|
565
|
+
'出圈舞台曲目'
|
|
566
|
+
]);
|
|
567
|
+
} else if (lang === 'ja') {
|
|
568
|
+
addQ('music_aspect', 'どのスタイルの音楽作品に興味がありますか?', [
|
|
569
|
+
'ドラマ主題歌 (OST)',
|
|
570
|
+
'ソロ楽曲・ダンス曲',
|
|
571
|
+
'話題のライブステージ',
|
|
572
|
+
'全楽曲'
|
|
573
|
+
]);
|
|
574
|
+
addQ('stage_vibe', '特に注目したい楽曲の時期はありますか?', [
|
|
575
|
+
'最新シングル (0.2s, FIND THE WAY)',
|
|
576
|
+
'定番の代表曲 (叹云兮, 恋爱告急)',
|
|
577
|
+
'バイラルステージ曲'
|
|
578
|
+
]);
|
|
579
|
+
} else if (lang === 'ko') {
|
|
580
|
+
addQ('music_aspect', '어떤 스타일의 음악 작품에 더 관심이 있으신가요?', [
|
|
581
|
+
'드라마 OST',
|
|
582
|
+
'솔로 팝·댄스 곡',
|
|
583
|
+
'화제의 라이브 무대',
|
|
584
|
+
'전체 음악'
|
|
585
|
+
]);
|
|
586
|
+
addQ('stage_vibe', '특히 주목하고 싶은 시기의 곡이 있으신가요?', [
|
|
587
|
+
'최신 싱글 (0.2s, FIND THE WAY)',
|
|
588
|
+
'대표 명곡 (탄윈시, 연애고급)',
|
|
589
|
+
'인기 무대 곡'
|
|
590
|
+
]);
|
|
591
|
+
} else {
|
|
592
|
+
addQ('music_aspect', 'Which musical style are you most interested in?', [
|
|
593
|
+
'Iconic Drama OSTs',
|
|
594
|
+
'Solo Pop & Dance Singles',
|
|
595
|
+
'Viral Live Stages',
|
|
596
|
+
'Complete Discography'
|
|
563
597
|
]);
|
|
564
598
|
}
|
|
565
|
-
} else if (
|
|
566
|
-
|
|
567
|
-
'
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
'
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
'
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
'角色表现与演技',
|
|
589
|
-
'故事与人设解读',
|
|
599
|
+
} else if (isDrama && !isMusic) {
|
|
600
|
+
if (lang === 'th') {
|
|
601
|
+
addQ('drama_genre', 'สนใจซีรีส์สไตล์ไหนเป็นพิเศษคะ?', [
|
|
602
|
+
'พีเรียดสืบสวนเข้มข้น (ฮวาเจียนลิ่ง)',
|
|
603
|
+
'โรแมนติกแฟนตาซี/เทพเซียน',
|
|
604
|
+
'ผลงานล่าสุดที่กำลังจะมา (Veil of Shadows)',
|
|
605
|
+
'ซีรีส์มาสเตอร์พีซทั้งหมด'
|
|
606
|
+
]);
|
|
607
|
+
addQ('drama_aspect', 'อยากเน้นเนื้อหาด้านใดในซีรีส์คะ?', [
|
|
608
|
+
'บทบาทและการแสดงอันเป็นเอกลักษณ์',
|
|
609
|
+
'เคมีคู่และเส้นเรื่องความรัก',
|
|
610
|
+
'กระแสตอบรับ เรตติ้ง และรางวัล'
|
|
611
|
+
]);
|
|
612
|
+
} else if (lang === 'zh') {
|
|
613
|
+
addQ('drama_genre', '想探索哪种类型的影视剧作?', [
|
|
614
|
+
'古装悬疑探案 (如《花间令》)',
|
|
615
|
+
'仙侠奇幻/浪漫言情',
|
|
616
|
+
'即将上线的待播新剧',
|
|
617
|
+
'历年代表作全景'
|
|
618
|
+
]);
|
|
619
|
+
addQ('drama_aspect', '想重点了解影视作品的哪一方面?', [
|
|
620
|
+
'角色塑造与演技高光',
|
|
621
|
+
'CP默契与感情线',
|
|
590
622
|
'热度口碑与奖项'
|
|
591
623
|
]);
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
'
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
'
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
'
|
|
624
|
+
} else if (lang === 'ja') {
|
|
625
|
+
addQ('drama_genre', 'どのジャンルのドラマに興味がありますか?', [
|
|
626
|
+
'時代劇・サスペンス探偵 (花間令など)',
|
|
627
|
+
'仙侠・ロマンスファンタジー',
|
|
628
|
+
'公開予定の最新作',
|
|
629
|
+
'歴代の代表作'
|
|
630
|
+
]);
|
|
631
|
+
addQ('drama_aspect', 'ドラマのどの側面に注目しますか?', [
|
|
632
|
+
'演技と独自のキャラクター',
|
|
633
|
+
'相手役とのケミ・恋愛模様',
|
|
634
|
+
'反響・視聴率・受賞歴'
|
|
635
|
+
]);
|
|
636
|
+
} else if (lang === 'ko') {
|
|
637
|
+
addQ('drama_genre', '어떤 장르의 드라마를 더 알아보고 싶으신가요?', [
|
|
638
|
+
'고장극·미스터리 수사 (화간령 등)',
|
|
639
|
+
'선협·로맨스 판타지',
|
|
640
|
+
'공개 예정 신작',
|
|
641
|
+
'역대 대표작'
|
|
642
|
+
]);
|
|
643
|
+
addQ('drama_aspect', '드라마의 어떤 점에 주목하고 싶으신가요?', [
|
|
644
|
+
'연기와 매력적인 캐릭터',
|
|
645
|
+
'주연 케미와 로맨스',
|
|
646
|
+
'반응·시청률·수상 내역'
|
|
647
|
+
]);
|
|
648
|
+
} else {
|
|
649
|
+
addQ('drama_genre', 'Which drama style would you like to explore?', [
|
|
650
|
+
'Historical Mystery & Investigation',
|
|
651
|
+
'Romance & Xianxia Fantasy',
|
|
652
|
+
'Upcoming & Latest Projects',
|
|
653
|
+
'All Masterpiece Dramas'
|
|
619
654
|
]);
|
|
620
655
|
}
|
|
621
|
-
} else if (
|
|
622
|
-
|
|
623
|
-
'
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
'
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
'
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
'
|
|
645
|
-
|
|
646
|
-
|
|
656
|
+
} else if (isFashion) {
|
|
657
|
+
if (lang === 'th') {
|
|
658
|
+
addQ('fashion_aspect', 'สนใจเจาะลึกด้านแฟชั่นและลุคไวรัลมุมไหนคะ?', [
|
|
659
|
+
'เครื่องประดับชั้นสูง FRED Paris',
|
|
660
|
+
'แบรนด์แอมบาสเดอร์ระดับโลก (Versace, PRSR)',
|
|
661
|
+
'สไตล์การแต่งหน้า Ju Jingyi Makeup ยอดฮิต'
|
|
662
|
+
]);
|
|
663
|
+
} else if (lang === 'zh') {
|
|
664
|
+
addQ('fashion_aspect', '想了解哪方面的时尚与出圈造型?', [
|
|
665
|
+
'法国高级珠宝 FRED Paris',
|
|
666
|
+
'全球品牌代言 (Versace, PRSR)',
|
|
667
|
+
'出圈鞠式美学与妆容'
|
|
668
|
+
]);
|
|
669
|
+
} else if (lang === 'ja') {
|
|
670
|
+
addQ('fashion_aspect', 'どのファッションやビジュアルに注目しますか?', [
|
|
671
|
+
'フランス高級ジュエリー FRED Paris',
|
|
672
|
+
'グローバルブランドアンバサダー (Versace, PRSR)',
|
|
673
|
+
'話題のキク風メイク&スタイル'
|
|
674
|
+
]);
|
|
675
|
+
} else if (lang === 'ko') {
|
|
676
|
+
addQ('fashion_aspect', '어떤 패션과 스타일링에 관심이 있으신가요?', [
|
|
677
|
+
'프랑스 하이주얼리 FRED Paris',
|
|
678
|
+
'글로벌 브랜드 앰버서더 (Versace, PRSR)',
|
|
679
|
+
'화제의 쥐징이 메이크업 & 스타일'
|
|
680
|
+
]);
|
|
681
|
+
} else {
|
|
682
|
+
addQ('fashion_aspect', 'Which fashion aspect would you like to explore?', [
|
|
683
|
+
'FRED Paris High Jewelry',
|
|
684
|
+
'Global Ambassadorships (Versace, PRSR)',
|
|
685
|
+
'Viral Ju Jingyi Makeup & Style'
|
|
647
686
|
]);
|
|
648
687
|
}
|
|
649
688
|
} else {
|
|
650
|
-
|
|
651
|
-
'
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
'
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
'
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
'
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
'
|
|
689
|
+
if (lang === 'th') {
|
|
690
|
+
addQ('fan_focus', 'อยากให้เสี่ยวจวีพาไปสำรวจมิติไหนก่อนดีคะ?', [
|
|
691
|
+
'ผลงานมาสเตอร์พีซและบทบาทสร้างชื่อ',
|
|
692
|
+
'อัปเดตผลงานและกิจกรรมล่าสุด (2024-2026)',
|
|
693
|
+
'เสน่ห์ด้านดนตรีและสเตจแสดงสด'
|
|
694
|
+
]);
|
|
695
|
+
addQ('timeframe_focus', 'ช่วงเวลาที่อยากให้โฟกัสเป็นพิเศษ?', [
|
|
696
|
+
'ผลงานยุคศิลปินเดี่ยวปัจจุบัน',
|
|
697
|
+
'ผลงานสร้างชื่อคลาสสิกในอดีต',
|
|
698
|
+
'ไทม์ไลน์พัฒนาการทั้งหมด'
|
|
699
|
+
]);
|
|
700
|
+
} else if (lang === 'zh') {
|
|
701
|
+
addQ('fan_focus', '想从小鞠的哪个精彩视角开始了解呢?', [
|
|
702
|
+
'出圈代表作与封神角色',
|
|
703
|
+
'近年最新动态与作品 (2024-2026)',
|
|
704
|
+
'舞台魅力与音乐全纪录'
|
|
705
|
+
]);
|
|
706
|
+
addQ('timeframe_focus', '想聚焦哪个成长阶段呢?', [
|
|
707
|
+
'个人 Solo 发展期',
|
|
708
|
+
'经典作品巅峰期',
|
|
709
|
+
'全时段全景历程'
|
|
710
|
+
]);
|
|
711
|
+
} else if (lang === 'ja') {
|
|
712
|
+
addQ('fan_focus', 'キクのどの魅力的なポイントから見ていきましょうか?', [
|
|
713
|
+
'代表作と名キャラクター',
|
|
714
|
+
'最新の活動・作品 (2024-2026)',
|
|
715
|
+
'音楽とステージパフォーマンス'
|
|
716
|
+
]);
|
|
717
|
+
addQ('timeframe_focus', '特に注目したい活動時期はありますか?', [
|
|
718
|
+
'現在のソロ活動期',
|
|
719
|
+
'過去の名作時代',
|
|
720
|
+
'全活動期間'
|
|
721
|
+
]);
|
|
722
|
+
} else if (lang === 'ko') {
|
|
723
|
+
addQ('fan_focus', '쥐징이의 어떤 매력적인 부분부터 살펴볼까요?', [
|
|
724
|
+
'대표작과 인기 캐릭터',
|
|
725
|
+
'최신 활동 및 작품 (2024-2026)',
|
|
726
|
+
'음악과 무대 퍼포먼스'
|
|
727
|
+
]);
|
|
728
|
+
addQ('timeframe_focus', '특별히 주목하고 싶은 활동 시기가 있으신가요?', [
|
|
729
|
+
'현재 솔로 활동기',
|
|
730
|
+
'과거 대표작 시절',
|
|
731
|
+
'전체 활동 기간'
|
|
732
|
+
]);
|
|
733
|
+
} else {
|
|
734
|
+
addQ('fan_focus', 'Which dimension would you like to explore first?', [
|
|
735
|
+
'Signature Masterpieces & Breakthrough Roles',
|
|
736
|
+
'Latest Updates & New Releases (2024-2026)',
|
|
737
|
+
'Music & Stage Highlights'
|
|
675
738
|
]);
|
|
676
739
|
}
|
|
677
740
|
}
|
|
@@ -679,9 +742,18 @@ function synthesizeDynamicQuestions(query, lang = 'en', detectedAmbiguities = []
|
|
|
679
742
|
return results.slice(0, maxQuestions);
|
|
680
743
|
}
|
|
681
744
|
|
|
745
|
+
export function cleanThinkingBlocks(raw) {
|
|
746
|
+
if (!raw || typeof raw !== 'string') return '';
|
|
747
|
+
return raw
|
|
748
|
+
.replace(/<think>[\s\S]*?<\/think>/gi, '')
|
|
749
|
+
.replace(/<thought>[\s\S]*?<\/thought>/gi, '')
|
|
750
|
+
.replace(/```thought[\s\S]*?```/gi, '')
|
|
751
|
+
.trim();
|
|
752
|
+
}
|
|
753
|
+
|
|
682
754
|
function extractJsonArray(text) {
|
|
683
755
|
if (!text || typeof text !== 'string') return null;
|
|
684
|
-
let cleaned = text
|
|
756
|
+
let cleaned = cleanThinkingBlocks(text);
|
|
685
757
|
const fenceMatch = cleaned.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
|
|
686
758
|
if (fenceMatch) {
|
|
687
759
|
cleaned = fenceMatch[1].trim();
|
|
@@ -710,41 +782,45 @@ function extractJsonArray(text) {
|
|
|
710
782
|
|
|
711
783
|
const LLM_SYSTEM_PROMPTS = {
|
|
712
784
|
th:
|
|
713
|
-
'คุณคือ Kiku AI
|
|
714
|
-
'หน้าที่:
|
|
785
|
+
'คุณคือ Kiku AI ระบบผู้ช่วยอัจฉริยะทางการและเพื่อนรู้ใจของแฟนคลับ "จวีจิ้งอี" (Ju Jingyi / 鞠婧祎 / เสี่ยวจวี)\n' +
|
|
786
|
+
'หน้าที่: วิเคราะห์คำถามของแฟนคลับ และคิดคำถามชวนคุย/เจาะลึก 2-3 ข้ออย่างเป็นกันเอง สุภาพ น่ารัก (ลงท้ายด้วย คะ/นะคะ) เพื่อให้แฟนคลับเลือกมุมมองที่อยากรู้จริงก่อนเริ่มค้นหาข้อมูล\n' +
|
|
715
787
|
'เป้าหมาย:\n' +
|
|
716
|
-
'1.
|
|
717
|
-
'2.
|
|
718
|
-
'3.
|
|
788
|
+
'1. วิเคราะห์สิ่งที่แฟนคลับถามจริงๆ แล้วสร้างคำถามเจาะลึกที่สร้างสรรค์ ตรงประเด็น และเข้าถึงใจแฟนคลับ เช่น หากถามถึงซีรีส์ ให้ชวนคุยมิติของตัวละคร/เคมีคู่/เบื้องหลังการแสดง หรือหากถามถึงเพลง ให้ชวนคุยสไตล์เพลง/ความหมาย/สเตจไวรัล\n' +
|
|
789
|
+
'2. หลีกเลี่ยงการถามคำถามแบบฟอร์มแห้งๆ ทั่วไป (ห้ามถามซ้ำซากเรื่อง ละครหรือเพลง, SNH48 หรือเดี่ยว, ปีไหน, ละเอียดแค่ไหน หากบริบทคำถามเดิมชัดเจนอยู่แล้ว)\n' +
|
|
790
|
+
'3. แต่ละข้อให้ตัวเลือกคำตอบ 2-4 ตัวเลือกที่สั้นกระชับ ชัดเจน น่ากดเลือก\n' +
|
|
719
791
|
'4. ส่งคำตอบเป็น JSON Array เท่านั้น ห้ามใส่คำอธิบายอื่นนอกเหนือจาก JSON:\n' +
|
|
720
|
-
'[{"category": "
|
|
792
|
+
'[{"category": "drama_aspect", "question": "เกี่ยวกับฮวาเจียนลิ่ง สนใจเจาะลึกมิติตัวละครหรือเคมีคู่กับพานเยว่คะ?", "options": ["มิติตัวละครหยางไฉ่เวย", "เคมีคู่พานเยว่", "เบื้องหลังการถ่ายทำ"]}]',
|
|
721
793
|
zh:
|
|
722
|
-
'你是鞠婧祎官方智能粉丝助手 Kiku
|
|
723
|
-
'
|
|
724
|
-
'
|
|
725
|
-
'
|
|
726
|
-
'
|
|
794
|
+
'你是鞠婧祎官方智能粉丝助手 Kiku,也是粉丝们的贴心同好小伙伴。\n' +
|
|
795
|
+
'任务:根据粉丝的具体提问,主动构思 2-3 个亲切有爱、针对性极强的追问,让粉丝选择最想探索的专属视角。\n' +
|
|
796
|
+
'要求:\n' +
|
|
797
|
+
'1. 紧密结合问题核心(如角色内心、名场面解析、声乐/舞台魅力、造型出圈点、幕后花絮),切忌千篇一律的固定模板问卷(切勿生硬追问影视还是音乐、SNH还是Solo等已明确的信息)。\n' +
|
|
798
|
+
'2. 每个追问提供 2-4 个精炼、有吸引力的点击选项。\n' +
|
|
799
|
+
'3. 仅输出合法的 JSON Array 格式,不要包含任何多余文字:\n' +
|
|
800
|
+
'[{"category": "highlight_aspect", "question": "追问内容?", "options": ["选项1", "选项2", "选项3"]}]',
|
|
801
|
+
en:
|
|
802
|
+
'You are Kiku AI, the official intelligent assistant and companion for Ju Jingyi (鞠婧祎) fans.\n' +
|
|
803
|
+
'Task: Analyze the fan\'s inquiry and generate 2-3 engaging, creative, and highly targeted follow-up questions tailored specifically to what they asked, allowing them to explore deeper nuances.\n' +
|
|
804
|
+
'Guidelines:\n' +
|
|
805
|
+
'1. Craft personalized exploration angles (e.g., character psychology, signature scenes, vocal style/stage presence, fashion highlights, behind-the-scenes) instead of dry, robotic survey templates.\n' +
|
|
806
|
+
'2. Never ask redundant boilerplate questions (like "drama vs music" or "solo vs SNH48" if the context is already implied).\n' +
|
|
807
|
+
'3. Provide 2-4 concise, clickable options per question.\n' +
|
|
808
|
+
'4. Output ONLY a valid JSON Array with no other text:\n' +
|
|
809
|
+
'[{"category": "fan_focus", "question": "Question?", "options": ["Option 1", "Option 2", "Option 3"]}]',
|
|
727
810
|
ja:
|
|
728
811
|
'あなたは鞠婧祎(Ju Jingyi)公式ファンアシスタント「Kiku AI」です。\n' +
|
|
729
|
-
'
|
|
730
|
-
'
|
|
731
|
-
'JSON
|
|
732
|
-
'[{"category": "
|
|
812
|
+
'タスク:ファンの質問を分析し、より深く魅力的な回答を届けるために、文脈に合わせた2〜3件の親しみやすく的確な確認質問を日本語で生成してください。\n' +
|
|
813
|
+
'固定的なアンケート調(毎回ドラマか音楽か、時期はいつか等)を避け、役柄の魅力、名シーン、楽曲の世界観などファンがワクワクする切り口を提示してください。\n' +
|
|
814
|
+
'各質問には2〜4個の簡潔な選択肢を付けて、JSON配列のみを出力してください:\n' +
|
|
815
|
+
'[{"category": "fan_focus", "question": "質問内容?", "options": ["選択肢1", "選択肢2", "選択肢3"]}]',
|
|
733
816
|
ko:
|
|
734
817
|
'당신은 Ju Jingyi(鞠婧祎) 공식 팬 어시스턴트 Kiku AI입니다.\n' +
|
|
735
|
-
'임무:
|
|
736
|
-
'
|
|
737
|
-
'
|
|
738
|
-
'[{"category": "
|
|
739
|
-
en:
|
|
740
|
-
'You are Kiku AI, the official assistant for Ju Jingyi (鞠婧祎).\n' +
|
|
741
|
-
'Task: Analyze the user inquiry and generate 3-4 polite, targeted clarifying questions across relevant dimensions (specific role/work, drama vs music, solo vs SNH48, timeframe, depth) to pinpoint their intent before the Timeline Agent begins searching.\n' +
|
|
742
|
-
'Provide 2-4 concise clickable options per question.\n' +
|
|
743
|
-
'Output ONLY a valid JSON Array with no other text:\n' +
|
|
744
|
-
'[{"category": "subject_focus", "question": "Question?", "options": ["Option 1", "Option 2", "Option 3"]}]',
|
|
818
|
+
'임무: 팬의 질문을 분석하여 더 깊이 있고 매력적인 답변을 제공할 수 있도록, 문맥에 맞춘 2-3개의 다정하고 흥미로운 탐색 질문을 한국어로 생성하세요.\n' +
|
|
819
|
+
'기계적인 정형화된 설문(매번 드라마vs음악, 시기 등)을 지양하고, 캐릭터 매력, 명장면, 음악 분위기 등 팬 맞춤형 포인트를 제시하세요.\n' +
|
|
820
|
+
'각 질문에는 2-4개의 간결한 선택지를 제공하고, JSON 배열만 출력하세요:\n' +
|
|
821
|
+
'[{"category": "fan_focus", "question": "질문 내용?", "options": ["선택지1", "선택지2", "선택지3"]}]'
|
|
745
822
|
};
|
|
746
823
|
|
|
747
|
-
// Shown when a model-generated question arrives without usable options.
|
|
748
824
|
const FALLBACK_OPTIONS = {
|
|
749
825
|
en: ['Yes', 'No', 'All'],
|
|
750
826
|
th: ['ใช่', 'ไม่ใช่', 'ทั้งหมด'],
|
|
@@ -765,11 +841,8 @@ export async function generateClarifyingQuestions(query, lang = 'en', options =
|
|
|
765
841
|
.filter(line => !line.startsWith('system:'))
|
|
766
842
|
.join('\n');
|
|
767
843
|
|
|
768
|
-
const ambiguityAreas = detectedAmbiguities.map(a => a.category).join(', ');
|
|
769
|
-
|
|
770
844
|
const systemPrompt = LLM_SYSTEM_PROMPTS[normalizedLang] || LLM_SYSTEM_PROMPTS.en;
|
|
771
|
-
|
|
772
|
-
const userPrompt = `User inquiry: "${cleanQuery}"\n${ambiguityAreas ? `Ambiguity cues: ${ambiguityAreas}\n` : ''}${recentHistory ? `Recent history:\n${recentHistory}` : ''}\n\nFormulate 3-4 targeted clarifying questions as JSON array:`;
|
|
845
|
+
const userPrompt = `User inquiry: "${cleanQuery}"\n${recentHistory ? `Recent conversation context:\n${recentHistory}\n` : ''}Formulate 2-3 creative, engaging, fan-centric follow-up questions tailored specifically to this inquiry as a JSON array:`;
|
|
773
846
|
|
|
774
847
|
try {
|
|
775
848
|
const controller = new AbortController();
|
|
@@ -795,7 +868,7 @@ export async function generateClarifyingQuestions(query, lang = 'en', options =
|
|
|
795
868
|
const validated = rawQuestions
|
|
796
869
|
.filter(q => q && typeof q.question === 'string' && q.question.trim().length > 0)
|
|
797
870
|
.map((q, idx) => ({
|
|
798
|
-
category: String(q.category || `
|
|
871
|
+
category: String(q.category || `fan_focus_${idx + 1}`).toLowerCase().replace(/[^a-z0-9_]/g, '_'),
|
|
799
872
|
question: q.question.trim(),
|
|
800
873
|
options: Array.isArray(q.options) && q.options.length > 0
|
|
801
874
|
? q.options.map(opt => String(opt).trim()).filter(Boolean).slice(0, 4)
|
|
@@ -807,7 +880,6 @@ export async function generateClarifyingQuestions(query, lang = 'en', options =
|
|
|
807
880
|
}
|
|
808
881
|
}
|
|
809
882
|
} catch {
|
|
810
|
-
// Fast fallback to dynamic contextual synthesis if model call fails/times out
|
|
811
883
|
}
|
|
812
884
|
}
|
|
813
885
|
|
|
@@ -821,7 +893,6 @@ export function isQueryTooVague(query) {
|
|
|
821
893
|
|
|
822
894
|
if (trimmed.length < 3) return true;
|
|
823
895
|
|
|
824
|
-
// Punctuation/symbols only — treat as vague (includes CJK punctuation).
|
|
825
896
|
if (/^[^\w\u0E00-\u0E7F\u3040-\u30FF\u3400-\u9FFF\uAC00-\uD7AF]+$/.test(trimmed)) return true;
|
|
826
897
|
|
|
827
898
|
const words = trimmed.split(/\s+/);
|
|
@@ -882,7 +953,6 @@ export function formatQuestionsForSSE(questions, lang = 'en') {
|
|
|
882
953
|
};
|
|
883
954
|
}
|
|
884
955
|
|
|
885
|
-
// Words that mean "skip these questions" across the supported languages.
|
|
886
956
|
const SKIP_WORDS = new Set([
|
|
887
957
|
'skip', 'next', 'no', 'none', 'pass', 'proceed',
|
|
888
958
|
'ข้าม', 'ไม่', 'ไม่ต้อง', 'ไม่ต้องถาม', 'ผ่าน',
|
|
@@ -948,33 +1018,33 @@ export function parseClarifyingAnswers(answersText, questions) {
|
|
|
948
1018
|
};
|
|
949
1019
|
}
|
|
950
1020
|
|
|
1021
|
+
const ANSWER_SHORTHAND_MAP = Object.freeze({
|
|
1022
|
+
'detailed': 'Very detailed (full analysis)',
|
|
1023
|
+
'brief': 'Brief (quick summary)',
|
|
1024
|
+
'quick': 'Brief (quick summary)',
|
|
1025
|
+
'summary': 'Brief (quick summary)',
|
|
1026
|
+
'comprehensive': 'Comprehensive overview',
|
|
1027
|
+
'overview': 'Comprehensive overview',
|
|
1028
|
+
'focused': 'Focused summary',
|
|
1029
|
+
'highlights': 'Key highlights only',
|
|
1030
|
+
'2026': '2026 (current year)',
|
|
1031
|
+
'this year': '2026 (current year)',
|
|
1032
|
+
'2025': '2025',
|
|
1033
|
+
'last year': '2025',
|
|
1034
|
+
'all': 'All types',
|
|
1035
|
+
'everything': 'All types',
|
|
1036
|
+
'skip': 'skip',
|
|
1037
|
+
'proceed': 'skip',
|
|
1038
|
+
'no': 'skip',
|
|
1039
|
+
'english': 'English',
|
|
1040
|
+
'thai': 'Thai',
|
|
1041
|
+
'chinese': 'Chinese',
|
|
1042
|
+
});
|
|
1043
|
+
|
|
951
1044
|
function interpretFreeTextAnswers(text, questions) {
|
|
952
1045
|
const answers = {};
|
|
953
1046
|
const lowerText = text.toLowerCase().trim();
|
|
954
1047
|
|
|
955
|
-
const shorthandMap = {
|
|
956
|
-
'detailed': 'Very detailed (full analysis)',
|
|
957
|
-
'brief': 'Brief (quick summary)',
|
|
958
|
-
'quick': 'Brief (quick summary)',
|
|
959
|
-
'summary': 'Brief (quick summary)',
|
|
960
|
-
'comprehensive': 'Comprehensive overview',
|
|
961
|
-
'overview': 'Comprehensive overview',
|
|
962
|
-
'focused': 'Focused summary',
|
|
963
|
-
'highlights': 'Key highlights only',
|
|
964
|
-
'2026': '2026 (current year)',
|
|
965
|
-
'this year': '2026 (current year)',
|
|
966
|
-
'2025': '2025',
|
|
967
|
-
'last year': '2025',
|
|
968
|
-
'all': 'All types',
|
|
969
|
-
'everything': 'All types',
|
|
970
|
-
'skip': 'skip',
|
|
971
|
-
'proceed': 'skip',
|
|
972
|
-
'no': 'skip',
|
|
973
|
-
'english': 'English',
|
|
974
|
-
'thai': 'Thai',
|
|
975
|
-
'chinese': 'Chinese',
|
|
976
|
-
};
|
|
977
|
-
|
|
978
1048
|
for (const q of questions) {
|
|
979
1049
|
if (!q.options || q.options.length === 0) continue;
|
|
980
1050
|
|
|
@@ -986,7 +1056,7 @@ function interpretFreeTextAnswers(text, questions) {
|
|
|
986
1056
|
}
|
|
987
1057
|
|
|
988
1058
|
if (!answers[q.category]) {
|
|
989
|
-
for (const [key, value] of Object.entries(
|
|
1059
|
+
for (const [key, value] of Object.entries(ANSWER_SHORTHAND_MAP)) {
|
|
990
1060
|
if (lowerText.includes(key) && q.options.includes(value)) {
|
|
991
1061
|
answers[q.category] = value;
|
|
992
1062
|
break;
|
|
@@ -1050,7 +1120,6 @@ export function buildClarificationContext(clarifyingResult, originalQuery) {
|
|
|
1050
1120
|
parts.push(`Clarifications provided:`);
|
|
1051
1121
|
|
|
1052
1122
|
for (const [category, value] of Object.entries(answers)) {
|
|
1053
|
-
// If the query is strictly about a drama but clarification was marked as Music, skip the contradictory field
|
|
1054
1123
|
if (category === 'media_type' && isDramaQuery && !isMusicQuery && String(value).toLowerCase().includes('music')) {
|
|
1055
1124
|
continue;
|
|
1056
1125
|
}
|
|
@@ -1082,6 +1151,7 @@ export default {
|
|
|
1082
1151
|
parseClarifyingAnswers,
|
|
1083
1152
|
emitClarifyingQuestions,
|
|
1084
1153
|
calculateConfidence,
|
|
1154
|
+
cleanThinkingBlocks,
|
|
1085
1155
|
generateClarifyingQuestions,
|
|
1086
1156
|
buildClarificationContext,
|
|
1087
1157
|
getAmbiguityPatterns,
|