kiku-agent 1.3.2 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -12
- package/package.json +2 -1
- package/server/agent/agentCore.js +319 -46
- package/server/agent/appPaths.js +31 -0
- package/server/agent/audioPlayer.js +339 -12
- package/server/agent/autoContext.js +193 -0
- package/server/agent/catalogEntityResolver.js +170 -28
- package/server/agent/clarifyingQuestions.js +137 -35
- package/server/agent/cli.js +177 -48
- package/server/agent/cloudAdapters.js +116 -0
- package/server/agent/commandRegistry.js +409 -80
- package/server/agent/commands.js +1056 -125
- package/server/agent/config.js +1088 -690
- package/server/agent/contextCompactor.js +213 -21
- package/server/agent/cpuMetrics.js +199 -0
- package/server/agent/engineService.js +591 -0
- package/server/agent/eval/modules/evaluation/runner.js +4 -14
- package/server/agent/eval/modules/evaluation/scoring.js +15 -3
- package/server/agent/eval/modules/server/evalApi.js +5 -5
- package/server/agent/eval/modules/ui/evalUi.js +7 -4
- package/server/agent/eventBus.js +1 -0
- package/server/agent/ink/main.jsx +196 -191
- package/server/agent/jobManager.js +11 -1
- package/server/agent/lanDiscovery.js +721 -23
- package/server/agent/llmClient.js +317 -71
- package/server/agent/lmStudioWakeup.js +517 -65
- package/server/agent/locationService.js +797 -0
- package/server/agent/mcpQuizData.js +509 -0
- package/server/agent/memory/contextAllocator.js +19 -7
- package/server/agent/memory.js +3 -1
- package/server/agent/modelCompatibility.js +352 -0
- package/server/agent/modelDefaults.js +1 -0
- package/server/agent/modelManager.js +46 -2
- package/server/agent/newsWatch.js +9 -3
- package/server/agent/processManager.js +229 -0
- package/server/agent/prompts.js +174 -8
- package/server/agent/routingInterceptor.js +9 -1
- package/server/agent/sanitizer.js +41 -6
- package/server/agent/searchQueryPlanner.js +202 -0
- package/server/agent/session.js +59 -4
- package/server/agent/tests/agentFollowUps.test.js +128 -2
- package/server/agent/tests/agentMusicControl.test.js +245 -0
- package/server/agent/tests/apiRouter.test.js +1 -1
- package/server/agent/tests/autoContext.test.js +176 -0
- package/server/agent/tests/bilibiliEmbed.test.js +45 -0
- package/server/agent/tests/chatHardening.test.js +121 -0
- package/server/agent/tests/chineseWebSearchEscalation.test.js +62 -0
- package/server/agent/tests/clarifyingQuestions.test.js +33 -0
- package/server/agent/tests/cliAutomation.test.js +38 -1
- package/server/agent/tests/cloudProviders.test.js +91 -3
- package/server/agent/tests/commands.test.js +92 -3
- package/server/agent/tests/contextIntelligence.test.js +50 -3
- package/server/agent/tests/cpuMetrics.test.js +101 -0
- package/server/agent/tests/decisionAndSearchEnhancements.test.js +32 -0
- package/server/agent/tests/edgeCasesScenarios.test.js +82 -0
- package/server/agent/tests/engineService.test.js +436 -0
- package/server/agent/tests/engineUnload.test.js +94 -0
- package/server/agent/tests/envCommand.test.js +69 -0
- package/server/agent/tests/evalMetrics.test.js +8 -0
- package/server/agent/tests/fetchApi.test.js +99 -0
- package/server/agent/tests/hybridWebSearch.test.js +2 -0
- package/server/agent/tests/lanDiscovery.test.js +5 -0
- package/server/agent/tests/lanInterfaces.test.js +510 -0
- package/server/agent/tests/lmStudioWakeup.test.js +278 -0
- package/server/agent/tests/locationTool.test.js +278 -0
- package/server/agent/tests/memoryRoute.test.js +173 -0
- package/server/agent/tests/memoryUiNormalization.test.js +75 -0
- package/server/agent/tests/modelCompatibility.test.js +148 -0
- package/server/agent/tests/modelManager.test.js +32 -0
- package/server/agent/tests/musicControlUiBridge.test.js +113 -0
- package/server/agent/tests/nextGenArchitecture.test.js +4 -2
- package/server/agent/tests/openDomainPrompt.test.js +197 -0
- package/server/agent/tests/openDomainSearch.test.js +121 -0
- package/server/agent/tests/processManager.test.js +144 -0
- package/server/agent/tests/providerPersistence.test.js +10 -5
- package/server/agent/tests/quickActionsAndModals.test.js +180 -0
- package/server/agent/tests/searchQueryPlanner.test.js +108 -0
- package/server/agent/tests/securityAuditRemediation.test.js +16 -9
- package/server/agent/tests/securityAuditRemediations2.test.js +154 -0
- package/server/agent/tests/securityHardening.test.js +4 -4
- package/server/agent/tests/sessionAndCopy.test.js +39 -4
- package/server/agent/tests/sidebarControls.test.js +34 -0
- package/server/agent/tests/statefulMemory.test.js +8 -1
- package/server/agent/tests/textProtocolLoop.test.js +71 -0
- package/server/agent/tests/textToolParsing.test.js +48 -0
- package/server/agent/tests/toolRoutingRegressions.test.js +41 -0
- package/server/agent/tests/unifiedEndpoints.test.js +13 -6
- package/server/agent/tests/v110Features.test.js +1 -1
- package/server/agent/tests/vramEstimator.test.js +75 -0
- package/server/agent/tests/webChatDocs.test.js +199 -0
- package/server/agent/tests/webChatInferenceControl.test.js +298 -0
- package/server/agent/tests/webChatInspection.test.js +256 -5
- package/server/agent/tests/webChatMcp.test.js +94 -0
- package/server/agent/tests/webExtract.test.js +23 -0
- package/server/agent/tests/webSources.test.js +46 -12
- package/server/agent/textToolProtocol.js +173 -46
- package/server/agent/toolRouter.js +318 -44
- package/server/agent/tools.js +877 -96
- package/server/agent/ui.js +39 -34
- package/server/agent/vramService.js +35 -54
- package/server/agent/webSearch.js +441 -162
- package/server/agent/websearch/orchestrator.js +15 -5
- package/server/agent/websearch/resilience/cache.js +9 -2
- package/server/agent/websearch/security/ssrf.js +41 -10
- package/server/agent/youtubeAuth.js +5 -20
- package/server/apiRouter.js +5558 -4422
- package/server/mcp/data/discography.js +2 -2
- package/server/mcp/examples/antigravity.example.json +17 -0
- package/server/mcp/handler.js +8 -4
- package/server/mcp/security.js +220 -21
- package/server/mcp/server.js +2 -1
- package/server/mcp/tests/connection.test.js +14 -8
- package/server/mcp/tests/mcpKeyAuth.test.js +156 -0
- package/server/mcp/tools.js +7 -1
- package/server/supabase_migration_prefix.sql +102 -0
- package/server/supabase_schema.sql +18 -18
- package/server/web-chat/core/connection.js +179 -218
- package/server/web-chat/core/conversationContext.js +9 -2
- package/server/web-chat/core/directCatalogEngine.js +52 -28
- package/server/web-chat/core/dramaKnowledge.js +33 -22
- package/server/web-chat/core/followUpEngine.js +735 -81
- package/server/web-chat/core/futureEventGuard.js +5 -1
- package/server/web-chat/core/musicControlAction.js +70 -0
- package/server/web-chat/core/security.js +32 -21
- package/server/web-chat/core/toolInspector.js +6 -6
- package/server/web-chat/core/urlGuard.js +45 -0
- package/server/web-chat/router.js +204 -157
- package/server/web-chat/routes/chatRoute.js +885 -488
- package/server/web-chat/routes/engineRoute.js +270 -0
- package/server/web-chat/routes/exportRoute.js +1 -1
- package/server/web-chat/routes/lanDiscoveryRoute.js +17 -2
- package/server/web-chat/routes/lmStudioLoadRoute.js +172 -0
- package/server/web-chat/routes/mcpRoute.js +386 -0
- package/server/web-chat/routes/memoryRoute.js +253 -0
- package/server/web-chat/routes/providerRoute.js +49 -16
- package/server/web-chat/routes/staticRoute.js +306 -17
- package/server/web-chat/routes/statusRoute.js +194 -73
- package/server/web-chat/routes/suggestRoute.js +11 -3
- package/server/web-chat/routes/verifyRoute.js +26 -4
- package/server/web-chat/server.js +7 -6
- package/server/web-chat/ui/docsBuilder.js +3558 -0
- package/server/web-chat/ui/htmlBuilder.js +12 -0
- package/server/web-chat/ui/scripts/client.js +3401 -1302
- package/server/web-chat/ui/scripts/webchatI18n.js +155 -153
- package/server/web-chat/ui/settingsBuilder.js +7870 -0
- package/server/web-chat/ui/styles/lan-card.css +165 -0
- package/server/web-chat/ui/styles/main.css +4289 -447
- package/server/web-chat/ui/sw.js +64 -0
- package/server/web-chat/ui/templates/body.html +407 -281
- package/src/data/awardsData.js +146 -146
- package/src/data/musicVideoData.js +6 -4
- package/src/lib/vram/estimator.js +195 -19
|
@@ -11,7 +11,7 @@ let cachedIndex = null;
|
|
|
11
11
|
|
|
12
12
|
const PLATFORM_PATTERNS = [
|
|
13
13
|
{ key: 'bilibili', regex: /(?:bilibili|b站|b-site|บิลิบิลิ|บิลิ)/i, cnName: 'B站' },
|
|
14
|
-
{ key: 'youtube', regex: /(?:youtube
|
|
14
|
+
{ key: 'youtube', regex: /(?:youtube|\byt\b|ยูทูป|ยูทูบ)/i, cnName: 'YouTube' },
|
|
15
15
|
{ key: 'weibo', regex: /(?:weibo|微博|เวยปั๋ว|เว่ยป๋อ)/i, cnName: '微博' },
|
|
16
16
|
{ key: 'douyin', regex: /(?:douyin|tiktok|抖音|โต่วอิน)/i, cnName: '抖音' },
|
|
17
17
|
{ key: 'qqmusic', regex: /(?:qqmusic|qq\s*音乐|qq\s*music|คิวคิว)/i, cnName: 'QQ音乐' },
|
|
@@ -211,7 +211,14 @@ export function buildCatalogIndex() {
|
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
|
|
214
|
-
const songs = Array.isArray(discographyData)
|
|
214
|
+
const songs = Array.isArray(discographyData)
|
|
215
|
+
? discographyData
|
|
216
|
+
: ([
|
|
217
|
+
...(discographyData?.songs || []),
|
|
218
|
+
...(discographyData?.latestReleases || []),
|
|
219
|
+
...(discographyData?.singles || []),
|
|
220
|
+
...(discographyData?.ostList || []),
|
|
221
|
+
]);
|
|
215
222
|
for (const s of songs) {
|
|
216
223
|
const enName = s.titleEn || '';
|
|
217
224
|
const cnName = s.titleCn || '';
|
|
@@ -224,8 +231,8 @@ export function buildCatalogIndex() {
|
|
|
224
231
|
if (s.id?.includes('0-2s') || s.id === '0.2s' || s.id?.includes('02s') || enName.toLowerCase().includes('0.2s') || cnName.includes('0.2秒')) {
|
|
225
232
|
aliases.push('0.2s', '0.2秒', '0.2 s', '0.2 seconds', 'point two seconds', '0.2 วินาที');
|
|
226
233
|
}
|
|
227
|
-
if (s.id?.includes('dewdrop') || s.id?.includes('dew-before-dawn') || enName.toLowerCase().includes('dew') || cnName.includes('露珠')) {
|
|
228
|
-
aliases.push('dewdrops', 'dewdrops dissipate before dawn', 'dew before dawn', 'morning dew', '露珠在晨曦前消散', 'หยาดน้ำค้างสลายก่อนรุ่งอรุณ', 'หยาดน้ำค้าง', 'd-vepdasblo');
|
|
234
|
+
if (s.id?.includes('dewdrop') || s.id?.includes('dew-before-dawn') || enName.toLowerCase().includes('dew') || cnName.includes('露珠') || cnName.includes('露水')) {
|
|
235
|
+
aliases.push('dewdrops', 'dewdrops dissipate before dawn', 'dew before dawn', 'morning dew', '露珠在晨曦前消散', '露水', 'หยาดน้ำค้างสลายก่อนรุ่งอรุณ', 'หยาดน้ำค้าง', 'd-vepdasblo');
|
|
229
236
|
}
|
|
230
237
|
|
|
231
238
|
entities.push({
|
|
@@ -246,13 +253,13 @@ export function buildCatalogIndex() {
|
|
|
246
253
|
|
|
247
254
|
const awards = Array.isArray(awardsData) ? awardsData : (awardsData?.awards || []);
|
|
248
255
|
for (const a of awards) {
|
|
249
|
-
const title = a.award || a.title || '';
|
|
256
|
+
const title = a.award || a.title || a.titleEn || a.titleTh || a.titleCn || a.ceremony || '';
|
|
250
257
|
const ceremony = a.ceremony || '';
|
|
251
|
-
const aliases = extractAliases(title, ceremony, a.category);
|
|
258
|
+
const aliases = extractAliases(a.id, title, ceremony, a.category, a.titleTh, a.titleCn, a.work);
|
|
252
259
|
|
|
253
260
|
entities.push({
|
|
254
261
|
type: 'AWARD',
|
|
255
|
-
id: `award-${a.year || ''}-${title.slice(0, 15).toLowerCase().replace(/\s+/g, '-')}`,
|
|
262
|
+
id: a.id || `award-${a.year || ''}-${title.slice(0, 15).toLowerCase().replace(/\s+/g, '-')}`,
|
|
256
263
|
canonicalName: `${title} (${ceremony})`.trim(),
|
|
257
264
|
nameEn: title,
|
|
258
265
|
nameCn: ceremony,
|
|
@@ -299,9 +306,10 @@ export function buildCatalogIndex() {
|
|
|
299
306
|
|
|
300
307
|
const mvs = Array.isArray(musicVideoData) ? musicVideoData : [];
|
|
301
308
|
for (const m of mvs) {
|
|
302
|
-
const enName = m.titleCn || '';
|
|
309
|
+
const enName = m.titleEn || m.titlePinyin || m.titleCn || '';
|
|
310
|
+
const cnName = m.titleCn || '';
|
|
303
311
|
const thName = m.titleTh || '';
|
|
304
|
-
const aliases = extractAliases(m.id, enName, thName, m.titlePinyin, m.dramaName);
|
|
312
|
+
const aliases = extractAliases(m.id, enName, cnName, thName, m.titlePinyin, m.dramaName);
|
|
305
313
|
if (Array.isArray(m.aliases)) aliases.push(...m.aliases);
|
|
306
314
|
if (Array.isArray(m.keywords)) aliases.push(...m.keywords);
|
|
307
315
|
|
|
@@ -311,8 +319,8 @@ export function buildCatalogIndex() {
|
|
|
311
319
|
if (m.id?.includes('0-2s') || m.id?.includes('02s') || String(m.titleCn || '').includes('0.2s') || String(m.titleTh || '').includes('0.2')) {
|
|
312
320
|
aliases.push('0.2s', '0.2秒', '0.2 s', '0.2 seconds', 'point two seconds', '0.2 วินาที', 'n0yv3dge_z4');
|
|
313
321
|
}
|
|
314
|
-
if (m.id?.includes('dewdrop') || String(m.titleCn || '').includes('露珠') || String(m.
|
|
315
|
-
aliases.push('dewdrops', 'dewdrops dissipate before dawn', 'dew before dawn', 'morning dew', '露珠在晨曦前消散', 'หยาดน้ำค้างสลายก่อนรุ่งอรุณ', 'หยาดน้ำค้าง', 'd-vepdasblo');
|
|
322
|
+
if (m.id?.includes('dewdrop') || String(m.titleCn || '').includes('露珠') || String(m.titleCn || '').includes('露水') || String(m.titleTh || '').includes('หยาดน้ำค้าง')) {
|
|
323
|
+
aliases.push('dewdrops', 'dewdrops dissipate before dawn', 'dew before dawn', 'morning dew', '露水', '露珠在晨曦前消散', 'หยาดน้ำค้างสลายก่อนรุ่งอรุณ', 'หยาดน้ำค้าง', 'd-vepdasblo');
|
|
316
324
|
}
|
|
317
325
|
|
|
318
326
|
entities.push({
|
|
@@ -320,7 +328,7 @@ export function buildCatalogIndex() {
|
|
|
320
328
|
id: m.id,
|
|
321
329
|
canonicalName: `${thName || enName}`.trim(),
|
|
322
330
|
nameEn: enName,
|
|
323
|
-
nameCn:
|
|
331
|
+
nameCn: cnName,
|
|
324
332
|
nameTh: thName,
|
|
325
333
|
category: m.category || 'Music Video',
|
|
326
334
|
year: m.releaseYear || m.year,
|
|
@@ -545,13 +553,18 @@ export function resolveQueryEntities(query = '') {
|
|
|
545
553
|
brands.length > 0
|
|
546
554
|
);
|
|
547
555
|
|
|
556
|
+
const isAwardIntent = Boolean(
|
|
557
|
+
/รางวัล|เกียรติยศ|awards?|honou?rs?|奖项|荣誉|weibo\s+night|微博之夜/i.test(text) ||
|
|
558
|
+
awards.length > 0
|
|
559
|
+
);
|
|
560
|
+
|
|
548
561
|
const recommendedTools = new Set();
|
|
549
562
|
if (timelines.length > 0 || isTimelineIntent) recommendedTools.add('get_career_timeline');
|
|
550
563
|
if (brands.length > 0 || isBrandIntent) recommendedTools.add('get_brand_endorsements');
|
|
551
564
|
if (works.length > 0) recommendedTools.add('search_works');
|
|
552
565
|
if (songs.length > 0) recommendedTools.add('search_discography');
|
|
553
566
|
if (medias.length > 0) recommendedTools.add('get_music_videos');
|
|
554
|
-
if (awards.length > 0) recommendedTools.add('get_awards');
|
|
567
|
+
if (awards.length > 0 || isAwardIntent) recommendedTools.add('get_awards');
|
|
555
568
|
|
|
556
569
|
const result = {
|
|
557
570
|
matchedEntities,
|
|
@@ -559,10 +572,11 @@ export function resolveQueryEntities(query = '') {
|
|
|
559
572
|
hasWork: works.length > 0,
|
|
560
573
|
hasSong: songs.length > 0,
|
|
561
574
|
hasMedia: medias.length > 0,
|
|
562
|
-
hasAward: awards.length > 0,
|
|
575
|
+
hasAward: awards.length > 0 || isAwardIntent,
|
|
563
576
|
hasTimeline: timelines.length > 0 || isTimelineIntent,
|
|
564
577
|
isTimelineIntent,
|
|
565
578
|
isBrandIntent,
|
|
579
|
+
isAwardIntent,
|
|
566
580
|
recommendedTools: Array.from(recommendedTools),
|
|
567
581
|
platforms,
|
|
568
582
|
isPromotion,
|
|
@@ -580,29 +594,132 @@ export function resolveQueryEntities(query = '') {
|
|
|
580
594
|
return result;
|
|
581
595
|
}
|
|
582
596
|
|
|
597
|
+
export const THAI_INTENT_MAP = [
|
|
598
|
+
[/(?:เต้น|dance|dancing|เต้นเพลง)/i, '舞蹈 舞台'],
|
|
599
|
+
[/(?:ร้องสด|live|เปิดไมค์|ไลฟ์|全开麦)/i, '现场 全开麦'],
|
|
600
|
+
[/(?:เวที|stage|สเตจ)/i, '舞台'],
|
|
601
|
+
[/(?:คอนเสิร์ต|concert)/i, '演唱会'],
|
|
602
|
+
[/(?:สัมภาษณ์|interview)/i, '采访'],
|
|
603
|
+
[/(?:เบื้องหลัง|bts|behind)/i, '花絮'],
|
|
604
|
+
[/(?:แฟนแคม|fancam|แคม|直拍)/i, '直拍'],
|
|
605
|
+
[/(?:วาไรตี้|variety|รายการ|综艺)/i, '综艺'],
|
|
606
|
+
[/(?:พรมแดง|แฟชั่น|fashion|red\s*carpet|红毯)/i, '红毯 造型'],
|
|
607
|
+
[/(?:เพลงประกอบ|ost|原声带)/i, 'OST 原声带'],
|
|
608
|
+
[/(?:เอ็มวี|\bmv\b|มิวสิควิดีโอ)/i, 'MV 官方'],
|
|
609
|
+
[/(?:ซีรีส์|ละคร|drama|电视剧)/i, '电视剧'],
|
|
610
|
+
[/(?:ภาพยนตร์|หนัง|movie|电影)/i, '电影'],
|
|
611
|
+
[/(?:โฆษณา|แคมเปญ|commercial|cf|广告)/i, '广告 代言'],
|
|
612
|
+
[/(?:ข่าวล่าสุด|ข่าวใหม่|ข่าวสาร|ข่าว|news|update|ความเคลื่อนไหว|อัปเดต|近况|最新)/i, '最新消息 动态'],
|
|
613
|
+
[/(?:เว่ยป๋อ|เว่ยโป๋|weibo|微博|超话)/i, '微博 超话'],
|
|
614
|
+
[/(?:งาน|อีเวนต์|ออกงาน|งานอีเวนต์|กิจกรรม|event)/i, '活动 现场'],
|
|
615
|
+
[/(?:ถ่ายทำ|กองถ่าย|เปิดกล้อง|ปิดกล้อง|路透|开机)/i, '剧组 路透 开机'],
|
|
616
|
+
[/(?:สไตล์|การแต่งตัว|ชุด|เสื้อผ้า|ลุค)/i, '穿搭 造型 美图'],
|
|
617
|
+
];
|
|
618
|
+
|
|
619
|
+
export const ARTIST_DIRECT_PATTERN = /(ju\s?jing\s?yi|jujingyi|จวีจิ้งอี|จิ้งอี|เสี่ยวจวี|เสี่ยว|鞠婧祎|小鞠|\bkiku\b|kikuuu?|蜜橘)/i;
|
|
620
|
+
|
|
621
|
+
const EXPLICIT_OPEN_DOMAIN_PATTERNS = [
|
|
622
|
+
|
|
623
|
+
/(?:คณิตศาสตร์|คณิต|แก้โจทย์|โจทย์เลข|คิดเลข|คำนวณ|สมการ|อสมการ|แคลคูลัส|อินทิกรัล|อินทิเกรต|อนุพันธ์|ดิฟ|เมทริกซ์|เวกเตอร์|พีชคณิต|เรขาคณิต|ตรีโกณมิติ|สถิติ|ความน่าจะเป็น|ทฤษฎีบท|พิสูจน์|จำนวนจริง|จำนวนเต็ม|ตรรกศาสตร์|\bmath(?:ematics)?\b|\bcalculus\b|\balgebra\b|\bintegral\b|\bderivative\b|\bmatrix(?:es)?\b|\bvectors?\b|\bgeometry\b|\btrigonometry\b|\bprobability\b|\bstatistics\b|\bequations?\b|\bdifferential\b|\barithmetic\b|\bdiscrete math\b|\blinear algebra\b)/i,
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
/(?:วิทยาศาสตร์|ฟิสิกส์|เคมี|ชีววิทยา|ดาราศาสตร์|กลศาสตร์|เทอร์โมไดนามิกส์|พันธุศาสตร์|เซลล์|อะตอม|โมเลกุล|ตารางธาตุ|ปฏิกิริยาเคมี|กรดเบส|ไฟฟ้าสถิต|วงจรไฟฟ้า|แม่เหล็กไฟฟ้า|คลื่นแม่เหล็ก|โฟตอน|สัมพัทธภาพ|ควอนตัม|หลุมดำ|ระบบสุริยะ|ดวงอาทิตย์|แรงโน้มถ่วง|ไอน์สไตน์|นิวตัน|\bscience\b|\bphysics\b|\bchemistry\b|\bbiology\b|\bastronomy\b|\bthermodynamics\b|\bgenetics\b|\bperiodic table\b|\bquantum\b|\brelativity\b|\bnewton\b|\beinstein\b|\belectromagnetism\b|\bcellular\b)/i,
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
/(?:เขียนโค้ด|เขียนโปรแกรม|แก้บั๊ก|แก้โค้ด|สอนเขียนโค้ด|ตัวอย่างโค้ด|อัลกอริทึม|โครงสร้างข้อมูล|สคริปต์|ฟังก์ชัน|ภาษา\s*(?:python|javascript|typescript|c\+\+|java|golang|rust|php|swift|kotlin|c#|sql|ruby)|html|css|react|vue|node|api|fastapi|express|docker|kubernetes|linux|windows|database|sql|nosql|github|git|backend|frontend|fullstack|\bcoding\b|\bprogramming\b|\bwrite code\b|\bdebug(?:ging)?\b|\brefactor(?:ing)?\b|\balgorithms?\b|\bdata structures?\b|\bpython\b|\bjavascript\b|\btypescript\b|\bgolang\b|\brustlang\b|\bc\+\+\b|\bjava\b)/i,
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
/(?:วิธีทำ|สูตรทำ|สูตรอาหาร|ต้มยำ|ผัดกะเพรา|แกง|ขนม|ทำอาหาร|cooking|recipe|food|ingredients|ส่วนผสม)/i,
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
/(?:สภาพอากาศ|พยากรณ์อากาศ|อุณหภูมิ|ฝนตกไหม|weather|forecast|temperature|rain)/i,
|
|
636
|
+
/(?:ท่องเที่ยว|สถานที่ท่องเที่ยว|ที่เที่ยวใน|โรงแรม|การเดินทาง|attractions|places\s*to\s*visit|travel\s*to)/i,
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
/(?:bitcoin|บิตคอยน์|crypto|คริปโต|หุ้น|ตลาดหุ้น|ราคาทอง|ดอลลาร์|อัตราแลกเปลี่ยน|exchange\s*rate|inflation|เงินเฟ้อ)/i,
|
|
640
|
+
/(?:ประธานาธิบดี|นายกรัฐมนตรี|รัฐบาล|การเลือกตั้ง|president|prime\s*minister|parliament|election)/i,
|
|
641
|
+
/(?:ฟุตบอล|พรีเมียร์ลีก|ลิเวอร์พูล|แมนยู|อาร์เซนอล|เชลซี|เรอัลมาดริด|บาร์เซโลนา|football|premier\s*league|liverpool|manchester|world\s*cup)/i,
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
/(?:taylor\s*swift|เทย์เลอร์|blackpink|แบล็กพิงก์|bts|บีทีเอส|lisa|ลิซ่า|rose|jennie|jisoo|jackson\s*wang|แจ็คสัน|bai\s*lu|ไป๋ลู่|yang\s*zi|หยางจื่อ|zhao\s*lusi|จ้าวลู่ซือ|dilraba|ตี๋ลี่เร่อปา|wang\s*yibo|หวังอี้ป๋อ|xiao\s*zhan|เซียวจ้าน|chen\s*zheyuan|เฉินเจ๋อย่วน)/i,
|
|
645
|
+
/(?:harry\s*potter|แฮร์รี่\s*พอตเตอร์|star\s*wars|สตาร์วอร์ส|marvel|avengers|อเวนเจอร์ส|batman|superman|spiderman|game\s*of\s*thrones|lord\s*of\s*the\s*rings|crash\s*landing|queen\s*of\s*tears)/i,
|
|
646
|
+
];
|
|
647
|
+
|
|
648
|
+
export const QUERY_DOMAINS = Object.freeze({
|
|
649
|
+
IN_DOMAIN: 'IN_DOMAIN',
|
|
650
|
+
OPEN_DOMAIN: 'OPEN_DOMAIN'
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
export function classifyQuerySpectrum(query = '') {
|
|
654
|
+
const text = String(query || '').trim();
|
|
655
|
+
if (!text) {
|
|
656
|
+
return { bias: 1.0, inScore: 1, openScore: 0, verdict: 'IN_DOMAIN' };
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
let inScore = 0;
|
|
660
|
+
let openScore = 0;
|
|
661
|
+
|
|
662
|
+
if (ARTIST_DIRECT_PATTERN.test(text)) inScore += 6;
|
|
663
|
+
if (/^(?:สวัสดี(?:ครับ|ค่ะ|จ้า|นะ)?|hello|hi|hey|หวัดดี(?:ครับ|ค่ะ)?|ดีครับ|ดีค่ะ|good\s*(?:morning|afternoon|evening))[\s!.]*$/i.test(text)) inScore += 5;
|
|
664
|
+
if (/(?:เล่นเพลง|หยุดเพลง|พักเพลง|เล่นต่อ|ข้ามเพลง|เพลงถัดไป|เพลงก่อนหน้า|ปิดเพลง|ปรับเสียง|เพิ่มเสียง|ลดเสียง|ปิดเสียง|เปิดเสียง|\bmute\b|\bunmute\b|\bvolume\b|\bpause\b|\bresume\b|\bnext\b|\bprev\b|\bseek\b|\bshuffle\b|\brepeat\b|\bloop\b|วนซ้ำ|สุ่มเพลง|กรอเพลง|toggle_video)/i.test(text)) inScore += 5;
|
|
665
|
+
if (/(?:4000[\s-]*(?:ปี|year)|ไอดอล\s*4000|snh48|塞纳河|kikufan|เสี่ยวจวี)/i.test(text)) inScore += 4;
|
|
666
|
+
if (/(ตาม|ใน|จาก)?(แค็ตตาล็อก|catalog|ฐานข้อมูล|database|ข้อมูลทางการ|บันทึกทางการ)/i.test(text)) inScore += 4;
|
|
667
|
+
|
|
668
|
+
const resolution = resolveQueryEntities(text);
|
|
669
|
+
if (resolution.matchedEntities.length > 0) inScore += 4 * resolution.matchedEntities.length;
|
|
670
|
+
if (resolution.hasBrand) inScore += 4;
|
|
671
|
+
if (resolution.hasWork) inScore += 4;
|
|
672
|
+
if (resolution.hasSong) inScore += 4;
|
|
673
|
+
if (resolution.isPromotion) inScore += 3;
|
|
674
|
+
|
|
675
|
+
for (const pat of EXPLICIT_OPEN_DOMAIN_PATTERNS) {
|
|
676
|
+
if (pat.test(text)) openScore += 5;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (/(?:เธอ|เขา|ศิลปิน|she|her)\b/i.test(text) && /(?:ผลงาน|เพลง|ละคร|ซีรีส์|วันเกิด|ประวัติ|ดนตรี|แฟนคลับ|แบรนด์|รางวัล|song|drama|music|album|award|birthday|concert|profile|fandom)/i.test(text)) {
|
|
680
|
+
inScore += 3;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (/(?:ผลงานล่าสุด|ผลงานใหม่|เพลงล่าสุด|เพลงใหม่|ละครล่าสุด|ซีรีส์ล่าสุด|รูปสวยๆ|ภาพถ่ายล่าสุด|แฟชั่นล่าสุด|ข่าวล่าสุด|ตารางงาน|งานแฟนมีต)/i.test(text)) {
|
|
684
|
+
inScore += 3;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (inScore === 0 && openScore === 0) {
|
|
688
|
+
openScore = 1;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const bias = inScore / (inScore + openScore);
|
|
692
|
+
const verdict = inScore > openScore ? 'IN_DOMAIN' : 'OPEN_DOMAIN';
|
|
693
|
+
|
|
694
|
+
return { bias, inScore, openScore, verdict };
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
export function classifyQueryDomain(query = '') {
|
|
698
|
+
return classifyQuerySpectrum(query).verdict;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
export function isQueryInDomain(query = '') {
|
|
702
|
+
return classifyQueryDomain(query) === 'IN_DOMAIN';
|
|
703
|
+
}
|
|
704
|
+
|
|
583
705
|
export function getContextualizedSearchQuery(keyword = '', platform = 'bilibili') {
|
|
584
706
|
const raw = String(keyword || '').trim();
|
|
585
707
|
if (!raw) return '鞠婧祎';
|
|
586
708
|
|
|
587
|
-
const
|
|
588
|
-
|
|
589
|
-
const ARTIST_RE = /(ju\s?jing\s?yi|jujingyi|จวีจิ้งอี|จิ้งอี|鞠婧祎|小鞠|kikuuu?|蜜橘)/i;
|
|
590
|
-
const mentionsArtist = ARTIST_RE.test(raw);
|
|
591
|
-
const hasCatalogMatch = resolution.matchedEntities.length > 0;
|
|
592
|
-
|
|
593
|
-
if (!mentionsArtist && !hasCatalogMatch) {
|
|
709
|
+
const domain = classifyQueryDomain(raw);
|
|
710
|
+
if (domain === 'OPEN_DOMAIN') {
|
|
594
711
|
return raw;
|
|
595
712
|
}
|
|
596
713
|
|
|
714
|
+
const resolution = resolveQueryEntities(raw);
|
|
715
|
+
|
|
597
716
|
if (platform === 'bilibili' || platform === 'chinese_web') {
|
|
598
717
|
const parts = ['鞠婧祎'];
|
|
599
718
|
|
|
600
|
-
|
|
601
719
|
if (resolution.primaryBrand) {
|
|
602
720
|
const cleanCn = resolution.primaryBrand.nameCn.replace(/\s*\([^)]*\)/g, '').trim();
|
|
603
721
|
if (cleanCn && !parts.includes(cleanCn)) parts.push(cleanCn);
|
|
604
722
|
if (resolution.primaryBrand.nameEn && !parts.includes(resolution.primaryBrand.nameEn)) {
|
|
605
|
-
|
|
606
723
|
const acronym = resolution.primaryBrand.nameEn.split('/')[0].trim();
|
|
607
724
|
if (acronym && !parts.includes(acronym)) parts.push(acronym);
|
|
608
725
|
}
|
|
@@ -612,13 +729,39 @@ export function getContextualizedSearchQuery(keyword = '', platform = 'bilibili'
|
|
|
612
729
|
} else if (resolution.primarySong) {
|
|
613
730
|
const cleanCn = resolution.primarySong.nameCn.replace(/\s*\([^)]*\)/g, '').trim();
|
|
614
731
|
if (cleanCn && !parts.includes(cleanCn)) parts.push(cleanCn);
|
|
732
|
+
if (resolution.primarySong.titleEn && !parts.includes(resolution.primarySong.titleEn)) {
|
|
733
|
+
parts.push(resolution.primarySong.titleEn);
|
|
734
|
+
}
|
|
615
735
|
} else {
|
|
616
|
-
|
|
617
|
-
const cleanTokens = raw
|
|
736
|
+
let cleanTokens = raw
|
|
618
737
|
.replace(/^(?:ค้นหา|หา|ดู|เปิด|คลิป|การโปรโมท|โปรโมท|บน|ใน|ที่|bilibili|b站)\s*/gi, '')
|
|
619
738
|
.trim();
|
|
739
|
+
for (const [re, cnTerm] of THAI_INTENT_MAP) {
|
|
740
|
+
if (re.test(cleanTokens)) {
|
|
741
|
+
if (!parts.includes(cnTerm)) parts.push(cnTerm);
|
|
742
|
+
cleanTokens = cleanTokens.replace(re, ' ');
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
cleanTokens = cleanTokens
|
|
746
|
+
.replace(/(?:จวีจิ้งอี|จิ้งอี|鞠婧祎|小鞠|ju\s*jing\s*yi|kiku)/gi, ' ')
|
|
747
|
+
.replace(/(?:มีไหม|อะไร|บ้าง|ของ|หน่อย|ล่าสุด|ไหม|ข่าวล่าสุด|ข่าวใหม่|ข่าวสาร|ข่าว|หรือมี|ช่วงนี้|ตอนนี้|ที่ไหน|อย่างไร|เป็นไง|ใหม่ๆ|มี|ใน)/gi, ' ')
|
|
748
|
+
.replace(/\s+/g, ' ')
|
|
749
|
+
.trim();
|
|
620
750
|
if (cleanTokens && !cleanTokens.includes('鞠婧祎')) {
|
|
621
|
-
|
|
751
|
+
if (platform === 'chinese_web' || platform === 'bilibili') {
|
|
752
|
+
const nonThaiWords = cleanTokens.split(/\s+/).filter(w => !/^[\u0E00-\u0E7F]+$/.test(w));
|
|
753
|
+
if (nonThaiWords.length > 0) {
|
|
754
|
+
parts.push(nonThaiWords.join(' '));
|
|
755
|
+
}
|
|
756
|
+
} else {
|
|
757
|
+
parts.push(cleanTokens);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
for (const [re, cnTerm] of THAI_INTENT_MAP) {
|
|
763
|
+
if (re.test(raw) && !parts.some(p => p.includes(cnTerm.split(' ')[0]))) {
|
|
764
|
+
parts.push(cnTerm);
|
|
622
765
|
}
|
|
623
766
|
}
|
|
624
767
|
|
|
@@ -626,10 +769,9 @@ export function getContextualizedSearchQuery(keyword = '', platform = 'bilibili'
|
|
|
626
769
|
parts.push('代言');
|
|
627
770
|
}
|
|
628
771
|
|
|
629
|
-
return parts.join(' ').trim();
|
|
772
|
+
return parts.join(' ').replace(/\s+/g, ' ').trim();
|
|
630
773
|
}
|
|
631
774
|
|
|
632
|
-
|
|
633
775
|
if (!raw.toLowerCase().includes('ju jingyi') && !raw.includes('鞠婧祎')) {
|
|
634
776
|
return `Ju Jingyi 鞠婧祎 ${raw}`;
|
|
635
777
|
}
|
|
@@ -53,7 +53,7 @@ const AMBIGUITY_PATTERNS = [
|
|
|
53
53
|
{
|
|
54
54
|
category: 'scope',
|
|
55
55
|
weight: 0.6,
|
|
56
|
-
detection: /\b(all|every|list|show|tell me about|what about|how about|comprehensive|complete)\b|(
|
|
56
|
+
detection: /\b(all|every|list|show|tell me about|what about|how about|comprehensive|complete|explain)\b|(ทั้งหมด|ทุกอย่าง|ภาพรวม|เล่าเรื่อง|ช่วยเล่า|เรื่องย่อ|เป็นยังไง|สนุกไหม|全部|一覧|まとめ|모든|전체|목록)/i,
|
|
57
57
|
questions: {
|
|
58
58
|
en: 'Would you like a comprehensive overview or a focused summary on a specific aspect?',
|
|
59
59
|
th: 'ต้องการภาพรวมที่ครอบคลุม หรือต้องการเน้นเฉพาะด้านใดด้านหนึ่ง?',
|
|
@@ -253,7 +253,7 @@ function localizePattern(pattern, lang) {
|
|
|
253
253
|
};
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
-
const KNOWN_WORKS_OR_CHARACTERS_REGEX = /(花间令|花間令|In Blossom
|
|
256
|
+
export const KNOWN_WORKS_OR_CHARACTERS_REGEX = /(花间令|花間令|In Blossom|ฮวาเจียนลิ่ง|ฮวาเจียนหลิง|อินบลอสซัม|芸汐传|芸汐伝|Yunxi|หยุนซี|ตำนานหยุนซี|新白娘子传奇|White Snake|นางพญางูขาว|如意芳霏|The Blooms at Ruyi Pavilion|กรุ่นรักกลิ่นบุปผา|嘉南传|嘉南傳|Rebirth For You|มู่หนานจือ|กำเนิดใหม่เพื่อเธอ|仙剑四|仙劍四|Sword and Fairy|เซียนกระบี่|千香|千香引|The Eternal Fragrance|พันกลิ่นบุปผา|花戎|Beauty of Resilience|ฮวาหรง|ตำนานเลือดฟีนิกซ์|月鳞绮纪|Veil of Shadows|เยว่หลินฉีจี้|ม่านเงาจันทรา|杨采薇|上官芷|หยางไฉ่เวย|ซ่างกวนจื่อ|韩芸汐|หานหยุนซี|白素贞|ไป๋ซู่เจิน|傅容|ฟู่หรง|姜保宁|เจียงเป่าหนิง|韩菱纱|หานหลิงซา|魏枝|เว่ยจือ|雷修远|เหลยซิวหย่วน|陆吾一|ลู่หวู่อี้)/i;
|
|
257
257
|
|
|
258
258
|
const SPECIFICITY_PATTERNS = [
|
|
259
259
|
/\b(exactly|specifically|precisely|particular)\b/i,
|
|
@@ -278,13 +278,17 @@ export function calculateConfidence(query, detectedAmbiguities = []) {
|
|
|
278
278
|
else if (wordCount < 5) score -= 0.1;
|
|
279
279
|
else if (wordCount > 15) score += 0.1;
|
|
280
280
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
281
|
+
const hasKnownWork = KNOWN_WORKS_OR_CHARACTERS_REGEX.test(trimmed);
|
|
282
|
+
if (hasKnownWork) {
|
|
283
|
+
if (detectedAmbiguities.length === 0) {
|
|
284
|
+
return {
|
|
285
|
+
score: 0.95,
|
|
286
|
+
level: 'high',
|
|
287
|
+
shouldAsk: false,
|
|
288
|
+
reasoning: 'High confidence: specific work or character explicitly identified in query without ambiguities.'
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
score += 0.2;
|
|
288
292
|
}
|
|
289
293
|
|
|
290
294
|
for (const pattern of SPECIFICITY_PATTERNS) {
|
|
@@ -342,7 +346,19 @@ export function detectAmbiguities(query, detectedLang = 'en') {
|
|
|
342
346
|
const KNOWN_ENTITY_PATTERNS = [
|
|
343
347
|
{
|
|
344
348
|
key: 'in_blossom',
|
|
345
|
-
labels: { en: 'In Blossom (花间令)', th: '
|
|
349
|
+
labels: { en: 'In Blossom (花间令)', th: 'ฮวาเจียนลิ่ง/ฮวาเจียนหลิง (In Blossom)', zh: '《花间令》', ja: '『花間令』(In Blossom)', ko: 'In Blossom (화간령)' },
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
key: 'the_eternal_fragrance',
|
|
353
|
+
labels: { en: 'The Eternal Fragrance (千香)', th: 'พันกลิ่นบุปผา (The Eternal Fragrance)', zh: '《千香》', ja: '『千香』(The Eternal Fragrance)', ko: 'The Eternal Fragrance (천향)' },
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
key: 'beauty_of_resilience',
|
|
357
|
+
labels: { en: 'Beauty of Resilience (花戎)', th: 'ตำนานเลือดฟีนิกซ์/ฮวาหรง (Beauty of Resilience)', zh: '《花戎》', ja: '『花戎』(Beauty of Resilience)', ko: 'Beauty of Resilience (화융)' },
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
key: 'veil_of_shadows',
|
|
361
|
+
labels: { en: 'Veil of Shadows (月鳞绮纪)', th: 'เยว่หลินฉีจี้/ม่านเงาจันทรา (Veil of Shadows)', zh: '《月鳞绮纪》', ja: '『月鱗綺紀』(Veil of Shadows)', ko: 'Veil of Shadows (월린기기)' },
|
|
346
362
|
},
|
|
347
363
|
{
|
|
348
364
|
key: 'legend_of_yunxi',
|
|
@@ -381,15 +397,18 @@ export function stripSystemDirectives(text) {
|
|
|
381
397
|
.trim();
|
|
382
398
|
}
|
|
383
399
|
|
|
384
|
-
const KNOWN_ENTITY_REGEX = /(花间令|花間令|In Blossom
|
|
400
|
+
const KNOWN_ENTITY_REGEX = /(花间令|花間令|In Blossom|อินบลอสซัม|ฮวาเจียนหลิง|ฮวาเจียนลิ่ง|千香|千香引|The Eternal Fragrance|พันกลิ่นบุปผา|花戎|Beauty of Resilience|ฮวาหรง|ตำนานเลือดฟีนิกซ์|月鳞绮纪|Veil of Shadows|เยว่หลินฉีจี้|ม่านเงาจันทรา|ลู่หวู่อี้|芸汐传|芸汐伝|Yunxi|ตำนานหยุนซี|หยุนซี|仙剑四|仙劍四|Sword and Fairy|เซียนกระบี่|新白娘子传奇|White Snake|นางพญางูขาว|如意芳霏|Ruyi Pavilion|กรุ่นรักกลิ่นบุปผา|嘉南传|嘉南傳|Rebirth For You|มู่หนานจือ|กำเนิดใหม่เพื่อเธอ|SNH48)/i;
|
|
385
401
|
|
|
386
402
|
const ENTITY_REGEX_MAP = {
|
|
387
|
-
in_blossom: /(?:in_blossom|花间令|花間令|In Blossom
|
|
403
|
+
in_blossom: /(?:in_blossom|花间令|花間令|In Blossom|อินบลอสซัม|ฮวาเจียนหลิง|ฮวาเจียนลิ่ง)/i,
|
|
404
|
+
the_eternal_fragrance: /(?:the_eternal_fragrance|千香|千香引|The Eternal Fragrance|พันกลิ่นบุปผา)/i,
|
|
405
|
+
beauty_of_resilience: /(?:beauty_of_resilience|花戎|Beauty of Resilience|ฮวาหรง|ตำนานเลือดฟีนิกซ์)/i,
|
|
406
|
+
veil_of_shadows: /(?:veil_of_shadows|月鳞绮纪|Veil of Shadows|เยว่หลินฉีจี้|ม่านเงาจันทรา|ลู่หวู่อี้)/i,
|
|
388
407
|
legend_of_yunxi: /(?:legend_of_yunxi|芸汐传|芸汐伝|Yunxi|ตำนานหยุนซี|หยุนซี)/i,
|
|
389
408
|
sword_fairy_4: /(?:sword_fairy_4|仙剑四|仙劍四|Sword and Fairy|เซียนกระบี่)/i,
|
|
390
409
|
white_snake: /(?:white_snake|新白娘子传奇|White Snake|นางพญางูขาว)/i,
|
|
391
410
|
ruyi_pavilion: /(?:ruyi_pavilion|如意芳霏|Ruyi Pavilion|กรุ่นรักกลิ่นบุปผา)/i,
|
|
392
|
-
rebirth_for_you: /(?:rebirth_for_you|嘉南传|嘉南傳|Rebirth For You
|
|
411
|
+
rebirth_for_you: /(?:rebirth_for_you|嘉南传|嘉南傳|Rebirth For You|มู่หนานจือ|กำเนิดใหม่เพื่อเธอ)/i,
|
|
393
412
|
snh48: /(?:snh48|SNH48)/i,
|
|
394
413
|
};
|
|
395
414
|
|
|
@@ -408,12 +427,15 @@ function extractQueryTopic(query, lang) {
|
|
|
408
427
|
|
|
409
428
|
function entityRegexFor(key) {
|
|
410
429
|
const map = {
|
|
411
|
-
in_blossom: '花间令|花間令|In Blossom
|
|
430
|
+
in_blossom: '花间令|花間令|In Blossom|อินบลอสซัม|ฮวาเจียนหลิง|ฮวาเจียนลิ่ง',
|
|
431
|
+
the_eternal_fragrance: '千香|千香引|The Eternal Fragrance|พันกลิ่นบุปผา',
|
|
432
|
+
beauty_of_resilience: '花戎|Beauty of Resilience|ฮวาหรง|ตำนานเลือดฟีนิกซ์',
|
|
433
|
+
veil_of_shadows: '月鳞绮纪|Veil of Shadows|เยว่หลินฉีจี้|ม่านเงาจันทรา|ลู่หวู่อี้',
|
|
412
434
|
legend_of_yunxi: '芸汐传|芸汐伝|Yunxi|ตำนานหยุนซี|หยุนซี',
|
|
413
435
|
sword_fairy_4: '仙剑四|仙劍四|Sword and Fairy|เซียนกระบี่',
|
|
414
436
|
white_snake: '新白娘子传奇|White Snake|นางพญางูขาว',
|
|
415
437
|
ruyi_pavilion: '如意芳霏|Ruyi Pavilion|กรุ่นรักกลิ่นบุปผา',
|
|
416
|
-
rebirth_for_you: '嘉南传|嘉南傳|Rebirth For You
|
|
438
|
+
rebirth_for_you: '嘉南传|嘉南傳|Rebirth For You|มู่หนานจือ|กำเนิดใหม่เพื่อเธอ',
|
|
417
439
|
snh48: 'SNH48',
|
|
418
440
|
};
|
|
419
441
|
return map[key] || '';
|
|
@@ -829,10 +851,42 @@ const FALLBACK_OPTIONS = {
|
|
|
829
851
|
ko: ['네', '아니요', '전체'],
|
|
830
852
|
};
|
|
831
853
|
|
|
854
|
+
const clarificationCache = new Map();
|
|
855
|
+
const CLARIFY_CACHE_TTL_MS = 10 * 60 * 1000;
|
|
856
|
+
const MAX_CLARIFY_CACHE_ENTRIES = 100;
|
|
857
|
+
|
|
858
|
+
function getCachedClarification(key) {
|
|
859
|
+
const item = clarificationCache.get(key);
|
|
860
|
+
if (!item) return null;
|
|
861
|
+
if (Date.now() > item.expiresAt) {
|
|
862
|
+
clarificationCache.delete(key);
|
|
863
|
+
return null;
|
|
864
|
+
}
|
|
865
|
+
return item.data;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function setCachedClarification(key, data) {
|
|
869
|
+
if (clarificationCache.size >= MAX_CLARIFY_CACHE_ENTRIES) {
|
|
870
|
+
const oldestKey = clarificationCache.keys().next().value;
|
|
871
|
+
clarificationCache.delete(oldestKey);
|
|
872
|
+
}
|
|
873
|
+
clarificationCache.set(key, { data, expiresAt: Date.now() + CLARIFY_CACHE_TTL_MS });
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
export function clearClarificationCache() {
|
|
877
|
+
clarificationCache.clear();
|
|
878
|
+
}
|
|
879
|
+
|
|
832
880
|
export async function generateClarifyingQuestions(query, lang = 'en', options = {}) {
|
|
833
881
|
const { conversationHistory = [], client = null, maxQuestions = 4, detectedAmbiguities = [] } = options;
|
|
834
882
|
const cleanQuery = stripSystemDirectives(query) || (typeof query === 'string' ? query.trim() : '');
|
|
835
883
|
const normalizedLang = normalizeLang(lang);
|
|
884
|
+
const cacheKey = `${normalizedLang}:${cleanQuery.toLowerCase()}`;
|
|
885
|
+
|
|
886
|
+
if (!client) {
|
|
887
|
+
const cached = getCachedClarification(cacheKey);
|
|
888
|
+
if (cached) return cached;
|
|
889
|
+
}
|
|
836
890
|
|
|
837
891
|
if (client && typeof client.chat === 'function') {
|
|
838
892
|
const recentHistory = conversationHistory
|
|
@@ -876,14 +930,18 @@ export async function generateClarifyingQuestions(query, lang = 'en', options =
|
|
|
876
930
|
}));
|
|
877
931
|
|
|
878
932
|
if (validated.length >= 2) {
|
|
879
|
-
|
|
933
|
+
const res = validated.slice(0, maxQuestions);
|
|
934
|
+
setCachedClarification(cacheKey, res);
|
|
935
|
+
return res;
|
|
880
936
|
}
|
|
881
937
|
}
|
|
882
938
|
} catch {
|
|
883
939
|
}
|
|
884
940
|
}
|
|
885
941
|
|
|
886
|
-
|
|
942
|
+
const synthesized = synthesizeDynamicQuestions(cleanQuery, normalizedLang, detectedAmbiguities, maxQuestions);
|
|
943
|
+
setCachedClarification(cacheKey, synthesized);
|
|
944
|
+
return synthesized;
|
|
887
945
|
}
|
|
888
946
|
|
|
889
947
|
export function isQueryTooVague(query) {
|
|
@@ -972,30 +1030,61 @@ export function parseClarifyingAnswers(answersText, questions) {
|
|
|
972
1030
|
return { answers: {}, summary: 'User chose to skip clarifying questions.' };
|
|
973
1031
|
}
|
|
974
1032
|
|
|
1033
|
+
let extraNote = '';
|
|
1034
|
+
let cleanAnswersText = answersText;
|
|
1035
|
+
const extraMatch = answersText.match(/(?:เพิ่มเติม|additional|extra|note):\s*(.+)$/i);
|
|
1036
|
+
if (extraMatch) {
|
|
1037
|
+
extraNote = extraMatch[1].trim();
|
|
1038
|
+
cleanAnswersText = answersText.replace(/(?:เพิ่มเติม|additional|extra|note):\s*(.+)$/i, '').trim().replace(/;\s*$/, '');
|
|
1039
|
+
}
|
|
1040
|
+
|
|
975
1041
|
const answers = {};
|
|
976
1042
|
const summaryParts = [];
|
|
977
1043
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
const
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1044
|
+
// 1. Try structured colon-delimited key-values (e.g. "drama_aspect: บทบาท; timeframe: 2024-2026")
|
|
1045
|
+
if (cleanAnswersText.includes(':')) {
|
|
1046
|
+
const pairs = cleanAnswersText.split(';').map(p => p.trim()).filter(Boolean);
|
|
1047
|
+
for (const pair of pairs) {
|
|
1048
|
+
const idx = pair.indexOf(':');
|
|
1049
|
+
if (idx !== -1) {
|
|
1050
|
+
const catKey = pair.slice(0, idx).trim();
|
|
1051
|
+
const val = pair.slice(idx + 1).trim();
|
|
1052
|
+
if (catKey && val) {
|
|
1053
|
+
const matchedQ = questions.find(q => q.category === catKey || q.category?.toLowerCase() === catKey.toLowerCase());
|
|
1054
|
+
const actualKey = matchedQ ? matchedQ.category : catKey;
|
|
1055
|
+
answers[actualKey] = val;
|
|
1056
|
+
summaryParts.push(`${actualKey}: ${val}`);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
986
1061
|
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1062
|
+
// 2. Try structured shorthand pattern (e.g. "1a 2c" or "a")
|
|
1063
|
+
if (Object.keys(answers).length === 0) {
|
|
1064
|
+
const cleanLower = cleanAnswersText.toLowerCase().trim();
|
|
1065
|
+
const isStructured = /^[\da-d\s,;]+$/.test(cleanLower) && cleanLower.length <= 20;
|
|
1066
|
+
|
|
1067
|
+
if (isStructured) {
|
|
1068
|
+
const structuredPattern = /(\d+)?([a-d])/g;
|
|
1069
|
+
let match;
|
|
1070
|
+
while ((match = structuredPattern.exec(cleanLower)) !== null) {
|
|
1071
|
+
const questionIndex = match[1] ? parseInt(match[1], 10) - 1 : 0;
|
|
1072
|
+
const optionIndex = match[2].charCodeAt(0) - 97;
|
|
1073
|
+
|
|
1074
|
+
if (questionIndex >= 0 && questionIndex < questions.length) {
|
|
1075
|
+
const q = questions[questionIndex];
|
|
1076
|
+
if (optionIndex >= 0 && optionIndex < (q.options?.length || 0)) {
|
|
1077
|
+
answers[q.category] = q.options[optionIndex];
|
|
1078
|
+
summaryParts.push(`${q.category}: ${q.options[optionIndex]}`);
|
|
1079
|
+
}
|
|
992
1080
|
}
|
|
993
1081
|
}
|
|
994
1082
|
}
|
|
995
1083
|
}
|
|
996
1084
|
|
|
997
|
-
|
|
998
|
-
|
|
1085
|
+
// 3. Fallback to free text interpretation against questions options
|
|
1086
|
+
if (Object.keys(answers).length === 0 && cleanAnswersText.trim().length > 0) {
|
|
1087
|
+
const interpretedAnswers = interpretFreeTextAnswers(cleanAnswersText, questions);
|
|
999
1088
|
|
|
1000
1089
|
if (Object.keys(interpretedAnswers).length > 0) {
|
|
1001
1090
|
Object.assign(answers, interpretedAnswers);
|
|
@@ -1004,12 +1093,18 @@ export function parseClarifyingAnswers(answersText, questions) {
|
|
|
1004
1093
|
}
|
|
1005
1094
|
} else {
|
|
1006
1095
|
if (questions.length > 0) {
|
|
1007
|
-
answers[questions[0].category] =
|
|
1008
|
-
summaryParts.push(`${questions[0].category}: ${
|
|
1096
|
+
answers[questions[0].category] = cleanAnswersText.trim();
|
|
1097
|
+
summaryParts.push(`${questions[0].category}: ${cleanAnswersText.trim()}`);
|
|
1009
1098
|
}
|
|
1010
1099
|
}
|
|
1011
1100
|
}
|
|
1012
1101
|
|
|
1102
|
+
// 4. Attach extra note if present
|
|
1103
|
+
if (extraNote) {
|
|
1104
|
+
answers._additional = extraNote;
|
|
1105
|
+
summaryParts.push(`Additional note: ${extraNote}`);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1013
1108
|
return {
|
|
1014
1109
|
answers,
|
|
1015
1110
|
summary: summaryParts.length > 0
|
|
@@ -1111,7 +1206,7 @@ export function buildClarificationContext(clarifyingResult, originalQuery) {
|
|
|
1111
1206
|
|
|
1112
1207
|
const { answers, summary } = clarifyingResult;
|
|
1113
1208
|
|
|
1114
|
-
const isDramaQuery = /(ซีรีส์|ละคร|drama|series|in blossom
|
|
1209
|
+
const isDramaQuery = /(ซีรีส์|ละคร|drama|series|in blossom|ฮวาเจียนลิ่ง|ฮวาเจียนหลิง|花间令|花間令|หยุนซี|yunxi|นางพญางูขาว|드라마|ドラマ)/i.test(originalQuery);
|
|
1115
1210
|
const isMusicQuery = /(เพลง|song|music|ost|album|single|find the way|曲|歌|음악|노래)/i.test(originalQuery);
|
|
1116
1211
|
|
|
1117
1212
|
const parts = [];
|
|
@@ -1120,6 +1215,9 @@ export function buildClarificationContext(clarifyingResult, originalQuery) {
|
|
|
1120
1215
|
parts.push(`Clarifications provided:`);
|
|
1121
1216
|
|
|
1122
1217
|
for (const [category, value] of Object.entries(answers)) {
|
|
1218
|
+
if (category.startsWith('_')) {
|
|
1219
|
+
continue;
|
|
1220
|
+
}
|
|
1123
1221
|
if (category === 'media_type' && isDramaQuery && !isMusicQuery && String(value).toLowerCase().includes('music')) {
|
|
1124
1222
|
continue;
|
|
1125
1223
|
}
|
|
@@ -1129,6 +1227,10 @@ export function buildClarificationContext(clarifyingResult, originalQuery) {
|
|
|
1129
1227
|
parts.push(`- ${category}: ${value}`);
|
|
1130
1228
|
}
|
|
1131
1229
|
|
|
1230
|
+
if (answers._additional) {
|
|
1231
|
+
parts.push(`- additional_user_note: ${answers._additional}`);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1132
1234
|
if (summary) {
|
|
1133
1235
|
parts.push(`Summary: ${summary}`);
|
|
1134
1236
|
}
|