kiku-agent 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +459 -407
  2. package/package.json +27 -3
  3. package/server/agent/agentCore.js +635 -267
  4. package/server/agent/appPaths.js +2 -0
  5. package/server/agent/catalogEntityResolver.js +629 -0
  6. package/server/agent/citations.js +0 -21
  7. package/server/agent/clarificationAnalytics.js +1 -1
  8. package/server/agent/clarifyingQuestions.js +286 -216
  9. package/server/agent/cli.js +528 -73
  10. package/server/agent/commandFeatures.js +123 -24
  11. package/server/agent/commandRegistry.js +128 -62
  12. package/server/agent/commands.js +1410 -95
  13. package/server/agent/config.js +253 -163
  14. package/server/agent/contextCompactor.js +239 -25
  15. package/server/agent/dynamicCatalog.js +192 -0
  16. package/server/agent/envBootstrap.js +46 -0
  17. package/server/agent/envManager.js +317 -0
  18. package/server/agent/eventBus.js +3 -3
  19. package/server/agent/feedbackLearning.js +0 -8
  20. package/server/agent/gpuMetrics.js +188 -55
  21. package/server/agent/ingestionWorker.js +339 -0
  22. package/server/agent/lanDiscovery.js +369 -0
  23. package/server/agent/llmClient.js +111 -39
  24. package/server/agent/lmStudioWakeup.js +144 -0
  25. package/server/agent/mcpClient.js +2 -2
  26. package/server/agent/memory.js +635 -494
  27. package/server/agent/modelConfig.js +487 -0
  28. package/server/agent/newsIntelligence.js +219 -0
  29. package/server/agent/newsWatch.js +85 -0
  30. package/server/agent/prompts.js +146 -0
  31. package/server/agent/publicTools.js +7 -0
  32. package/server/agent/queryDetails.js +62 -1
  33. package/server/agent/research.js +314 -35
  34. package/server/agent/routingInterceptor.js +77 -0
  35. package/server/agent/rumoredProjects.js +15 -0
  36. package/server/agent/sanitizer.js +117 -17
  37. package/server/agent/subagent.js +52 -9
  38. package/server/agent/suggestionTui.js +33 -13
  39. package/server/agent/suggestions.js +29 -13
  40. package/server/agent/telemetry.js +0 -11
  41. package/server/agent/terminalImage.js +0 -1
  42. package/server/agent/textToolProtocol.js +190 -0
  43. package/server/agent/tokenTracker.js +220 -0
  44. package/server/agent/toolRouter.js +362 -216
  45. package/server/agent/tools.js +1155 -194
  46. package/server/agent/ui.js +144 -41
  47. package/server/agent/vramService.js +572 -0
  48. package/server/agent/webSearch.js +2279 -273
  49. package/server/agent/youtubeAuth.js +82 -73
  50. package/server/agent/youtubeTranscript.js +209 -0
  51. package/server/apiRouter.js +383 -179
  52. package/server/mcp/data/brands.js +5 -5
  53. package/server/mcp/data/discography.js +56 -45
  54. package/server/mcp/data/gallery.js +9 -0
  55. package/server/mcp/data/index.js +1 -0
  56. package/server/mcp/data/industryIndex.js +251 -0
  57. package/server/mcp/data/profile.js +5 -5
  58. package/server/mcp/data/timeline.js +2 -2
  59. package/server/mcp/data/works.js +2 -2
  60. package/server/mcp/handler.js +0 -2
  61. package/server/mcp/prompts.js +42 -2
  62. package/server/mcp/resources.js +5 -1
  63. package/server/mcp/security.js +25 -14
  64. package/server/mcp/server.js +92 -3
  65. package/server/mcp/tools.js +20 -11
  66. package/server/web-chat/core/connection.js +218 -194
  67. package/server/web-chat/core/conversationContext.js +1 -1
  68. package/server/web-chat/core/directCatalogEngine.js +190 -184
  69. package/server/web-chat/core/followUpEngine.js +88 -11
  70. package/server/web-chat/core/security.js +7 -4
  71. package/server/web-chat/router.js +54 -17
  72. package/server/web-chat/routes/bilibiliRoute.js +34 -0
  73. package/server/web-chat/routes/chatRoute.js +488 -397
  74. package/server/web-chat/routes/clarifyConfigRoute.js +1 -2
  75. package/server/web-chat/routes/exportRoute.js +2 -1
  76. package/server/web-chat/routes/lanDiscoveryRoute.js +90 -0
  77. package/server/web-chat/routes/newsRoute.js +36 -6
  78. package/server/web-chat/routes/providerRoute.js +87 -0
  79. package/server/web-chat/routes/staticRoute.js +61 -13
  80. package/server/web-chat/routes/statusRoute.js +23 -2
  81. package/server/web-chat/routes/vramRoute.js +22 -0
  82. package/server/web-chat/server.js +135 -106
  83. package/server/web-chat/ui/htmlBuilder.js +8 -8
  84. package/server/web-chat/ui/previews/modelSettings.preview.html +41 -0
  85. package/server/web-chat/ui/scripts/agentBubbleSystem.js +0 -5
  86. package/server/web-chat/ui/scripts/client.js +10526 -8397
  87. package/server/web-chat/ui/scripts/webchatI18n.js +190 -138
  88. package/server/web-chat/ui/styles/main.css +7471 -4246
  89. package/server/web-chat/ui/templates/body.html +305 -179
  90. package/src/data/brandsData.js +29 -6
  91. package/src/data/discographyData.js +918 -900
  92. package/src/data/galleryData.js +66 -0
  93. package/src/data/industryIndexData.js +292 -0
  94. package/src/data/musicVideoData.js +48 -5
  95. package/src/data/profileData.js +15 -1
  96. package/src/data/timelineData.js +4 -3
  97. package/src/data/worksData.js +536 -535
  98. package/src/lib/vram/estimator.js +464 -0
  99. package/src/lib/vram/modelRegistry.js +387 -0
  100. package/tokens.css +33 -2
@@ -1,194 +1,218 @@
1
- import fs from 'node:fs';
2
- import { createHash } from 'node:crypto';
3
- import { spawn } from 'node:child_process';
4
- import { LLMClient, LMStudioClient } from '../../agent/llmClient.js';
5
- import { KikuAgent } from '../../agent/agentCore.js';
6
- import { AgentMemory } from '../../agent/memory.js';
7
- import { ModelManager } from '../../agent/modelManager.js';
8
- import { config } from '../../agent/config.js';
9
- import { PUBLIC_REMOTE_TOOL_NAMES } from '../../agent/publicTools.js';
10
-
11
- export const webAgentCache = new Map();
12
- export let sharedWebMemory = new AgentMemory({ persistence: false });
13
- export let currentAgent = null;
14
- const MAX_WEB_AGENT_SESSIONS = 24;
15
-
16
- export function setSharedWebMemory(mem) {
17
- if (mem) sharedWebMemory = mem;
18
- }
19
-
20
- export function setCurrentAgent(agent) {
21
- if (agent) {
22
- currentAgent = agent;
23
- if (agent.memory) sharedWebMemory = agent.memory;
24
- }
25
- }
26
-
27
- export function normalizeWebSessionId(value) {
28
- const normalized = String(value || '').trim().replace(/[^a-zA-Z0-9_-]+/g, '_').slice(0, 100);
29
- return normalized || null;
30
- }
31
-
32
- export function credentialFingerprint(value) {
33
- if (!value) return 'no-key';
34
- return createHash('sha256').update(String(value)).digest('hex').slice(0, 12);
35
- }
36
-
37
- export function computeSessionHash(messages) {
38
- if (!Array.isArray(messages) || messages.length === 0) return '';
39
- const payload = messages.map(m => (m.role || '') + ':' + (m.parts?.[0]?.content || m.text || '')).join('|');
40
- return createHash('sha256').update(payload).digest('hex').slice(0, 16);
41
- }
42
-
43
- export function getWebAgentCacheKey({ sessionId, provider, model, apiKey }) {
44
- if (!sessionId) return null;
45
- return [sessionId, provider || 'lmstudio', model || 'default', credentialFingerprint(apiKey)].join(':');
46
- }
47
-
48
- export async function prepareWebAgent(agent, options = {}) {
49
- await agent.ready();
50
- if (Array.isArray(options.history) && options.history.length > 0) {
51
- const agentMsgCount = agent.session?.messages?.length || 0;
52
- const frontendMsgCount = options.history.length;
53
- await agent.hydrateConversationHistory(options.history, {
54
- sessionId: options.sessionId,
55
- maxMessages: 24,
56
- force: frontendMsgCount > agentMsgCount,
57
- });
58
- }
59
- return agent;
60
- }
61
-
62
- export function cacheWebAgentConnection(cacheKey, connection) {
63
- if (!cacheKey) return;
64
- webAgentCache.delete(cacheKey);
65
- webAgentCache.set(cacheKey, connection);
66
- while (webAgentCache.size > MAX_WEB_AGENT_SESSIONS) {
67
- const oldestKey = webAgentCache.keys().next().value;
68
- const oldest = webAgentCache.get(oldestKey);
69
- oldest?.agent?.saveSession?.().catch(() => {});
70
- const msgCount = oldest?.agent?.session?.messages?.length || 0;
71
- if (msgCount === 0) {
72
- const storageFile = oldest?.agent?.session?.storageFile;
73
- if (storageFile) {
74
- fs.promises.unlink(storageFile).catch(() => {});
75
- }
76
- }
77
- webAgentCache.delete(oldestKey);
78
- }
79
- }
80
-
81
- export async function waitForLMStudio(baseUrl, timeoutMs = 8000) {
82
- const client = new LMStudioClient({ baseUrl });
83
- const start = Date.now();
84
- while (Date.now() - start < timeoutMs) {
85
- try {
86
- const health = await client.checkConnection();
87
- if (health.connected) return health;
88
- } catch {}
89
- await new Promise(r => setTimeout(r, 600));
90
- }
91
- return null;
92
- }
93
-
94
- export async function attemptAutoStartLMStudio(baseUrl, model) {
95
- const isWin = process.platform === 'win32';
96
- const isMac = process.platform === 'darwin';
97
-
98
- const candidates = isWin
99
- ? [
100
- `${process.env.LOCALAPPDATA}\\LM Studio\\LM Studio.exe`,
101
- `${process.env.LOCALAPPDATA}\\Programs\\LM Studio\\LM Studio.exe`,
102
- 'C:\\Program Files\\LM Studio\\LM Studio.exe'
103
- ]
104
- : isMac
105
- ? ['/Applications/LM Studio.app/Contents/MacOS/LM Studio']
106
- : ['lmstudio', '/usr/local/bin/lmstudio', '/opt/lmstudio/bin/lmstudio'];
107
-
108
- const exe = candidates.find(p => {
109
- if (p.includes('/') || p.includes('\\')) {
110
- try { return fs.existsSync(p); } catch { return false; }
111
- }
112
- return true;
113
- });
114
-
115
- if (!exe) return null;
116
-
117
- try {
118
- let port = '1234';
119
- try { const parsedUrl = new URL(baseUrl); if (parsedUrl.port) port = parsedUrl.port; } catch {}
120
-
121
- const child = spawn(exe, isWin
122
- ? ['--model', model || 'google/gemma-4-e4b', '--serve', '--port', port]
123
- : ['serve', '--model', model || 'google/gemma-4-e4b', '--port', port],
124
- { detached: true, stdio: 'ignore', windowsHide: true }
125
- );
126
- child.unref();
127
-
128
- return await waitForLMStudio(baseUrl, 10000);
129
- } catch {
130
- return null;
131
- }
132
- }
133
-
134
- export async function resolveAgentConnection(customOptions = {}) {
135
- const provider = customOptions.provider || 'lmstudio';
136
- const requestedModel = customOptions.model || config.providers?.[provider]?.model || 'google/gemma-4-e4b';
137
- const baseUrl = customOptions.baseUrl || config.providers?.[provider]?.baseUrl || 'http://127.0.0.1:1234/v1';
138
- const sessionId = normalizeWebSessionId(customOptions.sessionId);
139
- // A remotely reachable endpoint must never inherit provider credentials from
140
- // the server process. Only an explicitly supplied per-request key is used.
141
- const apiKey = customOptions.apiKey || '';
142
-
143
- const client = new LLMClient({
144
- provider,
145
- baseUrl,
146
- model: requestedModel,
147
- apiKey
148
- });
149
-
150
- const modelManager = new ModelManager(client, { requestedModel });
151
- let resolution = await modelManager.resolve({ requestedModel });
152
-
153
- if (provider === 'lmstudio' && resolution.state === 'server_offline' && process.env.NODE_ENV !== 'test' && !process.execArgv.includes('--test')) {
154
- const started = await attemptAutoStartLMStudio(baseUrl, requestedModel);
155
- if (started) {
156
- resolution = await modelManager.resolve({ requestedModel });
157
- }
158
- }
159
-
160
- if (resolution.connected && resolution.selectedModel) {
161
- const agent = new KikuAgent({
162
- client,
163
- thinkingMode: customOptions.thinking || 'show',
164
- memory: new AgentMemory({ persistence: false }),
165
- sessionId,
166
- sessionPersistence: false,
167
- allowedToolNames: PUBLIC_REMOTE_TOOL_NAMES,
168
- });
169
- await prepareWebAgent(agent, { history: customOptions.history, sessionId });
170
- const connection = {
171
- ready: true,
172
- agent,
173
- client,
174
- provider,
175
- model: resolution.selectedModel,
176
- models: resolution.models || [],
177
- usedFallback: resolution.usedFallback || false,
178
- resolution,
179
- contentHash: computeSessionHash(agent.session?.messages || [])
180
- };
181
- return connection;
182
- }
183
-
184
- return {
185
- ready: false,
186
- client,
187
- provider,
188
- model: requestedModel,
189
- models: resolution.models || [],
190
- state: resolution.state,
191
- error: resolution.error || 'Server offline',
192
- resolution
193
- };
194
- }
1
+ import fs from 'node:fs';
2
+ import { createHash } from 'node:crypto';
3
+ import { spawn } from 'node:child_process';
4
+ import { LLMClient } from '../../agent/llmClient.js';
5
+ import { KikuAgent } from '../../agent/agentCore.js';
6
+ import { AgentMemory } from '../../agent/memory.js';
7
+ import { ModelManager } from '../../agent/modelManager.js';
8
+ import { config, getProviderBaseUrl, getPersistedActiveProvider } from '../../agent/config.js';
9
+ import { PUBLIC_REMOTE_TOOL_NAMES } from '../../agent/publicTools.js';
10
+
11
+ export const webAgentCache = new Map();
12
+ export let sharedWebMemory = new AgentMemory({ persistence: false });
13
+ export let currentAgent = null;
14
+ const MAX_WEB_AGENT_SESSIONS = 24;
15
+
16
+ export function setSharedWebMemory(mem) {
17
+ if (mem) sharedWebMemory = mem;
18
+ }
19
+
20
+ export function setCurrentAgent(agent) {
21
+ if (agent) {
22
+ currentAgent = agent;
23
+ if (agent.memory) sharedWebMemory = agent.memory;
24
+ }
25
+ }
26
+
27
+ export function normalizeWebSessionId(value) {
28
+ const normalized = String(value || '').trim().replace(/[^a-zA-Z0-9_-]+/g, '_').slice(0, 100);
29
+ return normalized || null;
30
+ }
31
+
32
+ export function credentialFingerprint(value) {
33
+ if (!value) return 'no-key';
34
+ return createHash('sha256').update(String(value)).digest('hex').slice(0, 12);
35
+ }
36
+
37
+ export function computeSessionHash(messages) {
38
+ if (!Array.isArray(messages) || messages.length === 0) return '';
39
+ const payload = messages.map(m => (m.role || '') + ':' + (m.parts?.[0]?.content || m.text || '')).join('|');
40
+ return createHash('sha256').update(payload).digest('hex').slice(0, 16);
41
+ }
42
+
43
+ export function getWebAgentCacheKey({ sessionId, provider, model, apiKey }) {
44
+ if (!sessionId) return null;
45
+ return [sessionId, provider || 'lmstudio', model || 'default', credentialFingerprint(apiKey)].join(':');
46
+ }
47
+
48
+ export async function prepareWebAgent(agent, options = {}) {
49
+ await agent.ready();
50
+ if (Array.isArray(options.history) && options.history.length > 0) {
51
+ const agentMsgCount = agent.session?.messages?.length || 0;
52
+ const frontendMsgCount = options.history.length;
53
+ await agent.hydrateConversationHistory(options.history, {
54
+ sessionId: options.sessionId,
55
+ maxMessages: 24,
56
+ force: frontendMsgCount > agentMsgCount,
57
+ });
58
+ }
59
+ return agent;
60
+ }
61
+
62
+ export function cacheWebAgentConnection(cacheKey, connection) {
63
+ if (!cacheKey) return;
64
+ webAgentCache.delete(cacheKey);
65
+ webAgentCache.set(cacheKey, connection);
66
+ while (webAgentCache.size > MAX_WEB_AGENT_SESSIONS) {
67
+ const oldestKey = webAgentCache.keys().next().value;
68
+ const oldest = webAgentCache.get(oldestKey);
69
+ oldest?.agent?.saveSession?.().catch(() => {});
70
+ const msgCount = oldest?.agent?.session?.messages?.length || 0;
71
+ if (msgCount === 0) {
72
+ const storageFile = oldest?.agent?.session?.storageFile;
73
+ if (storageFile) {
74
+ fs.promises.unlink(storageFile).catch(() => {});
75
+ }
76
+ }
77
+ webAgentCache.delete(oldestKey);
78
+ }
79
+ }
80
+
81
+ export async function waitForLMStudio(baseUrl, timeoutMs = 8000) {
82
+ const client = new LLMClient({ provider: 'lmstudio', baseUrl });
83
+ const start = Date.now();
84
+ while (Date.now() - start < timeoutMs) {
85
+ try {
86
+ const health = await client.checkConnection();
87
+ if (health.connected) return health;
88
+ } catch {}
89
+ await new Promise(r => setTimeout(r, 600));
90
+ }
91
+ return null;
92
+ }
93
+
94
+ export async function attemptAutoStartLMStudio(baseUrl, model) {
95
+ const isWin = process.platform === 'win32';
96
+ const isMac = process.platform === 'darwin';
97
+
98
+ const candidates = isWin
99
+ ? [
100
+ `${process.env.LOCALAPPDATA}\\LM Studio\\LM Studio.exe`,
101
+ `${process.env.LOCALAPPDATA}\\Programs\\LM Studio\\LM Studio.exe`,
102
+ 'C:\\Program Files\\LM Studio\\LM Studio.exe'
103
+ ]
104
+ : isMac
105
+ ? ['/Applications/LM Studio.app/Contents/MacOS/LM Studio']
106
+ : ['lmstudio', '/usr/local/bin/lmstudio', '/opt/lmstudio/bin/lmstudio'];
107
+
108
+ const exe = candidates.find(p => {
109
+ if (p.includes('/') || p.includes('\\')) {
110
+ try { return fs.existsSync(p); } catch { return false; }
111
+ }
112
+ return true;
113
+ });
114
+
115
+ if (!exe) return null;
116
+
117
+ try {
118
+ let port = '1234';
119
+ try { const parsedUrl = new URL(baseUrl); if (parsedUrl.port) port = parsedUrl.port; } catch {}
120
+
121
+ const safeModel = (typeof model === 'string' && /^[a-zA-Z0-9_\-\.\/]+$/.test(model) && !model.startsWith('-'))
122
+ ? model
123
+ : 'google/gemma-4-e4b';
124
+ const safePort = (/^\d{1,5}$/.test(port) && Number(port) > 0 && Number(port) <= 65535) ? port : '1234';
125
+
126
+ const child = spawn(exe, isWin
127
+ ? ['--model', safeModel, '--serve', '--port', safePort]
128
+ : ['serve', '--model', safeModel, '--port', safePort],
129
+ { detached: true, stdio: 'ignore', windowsHide: true }
130
+ );
131
+ child.unref();
132
+
133
+ return await waitForLMStudio(baseUrl, 10000);
134
+ } catch {
135
+ return null;
136
+ }
137
+ }
138
+
139
+ export async function resolveAgentConnection(customOptions = {}) {
140
+ const isAuto = !customOptions.provider || customOptions.provider === 'auto';
141
+ const serverProvider = currentAgent?.client?.provider || config.activeProvider || 'lmstudio';
142
+ const provider = isAuto ? serverProvider : customOptions.provider;
143
+
144
+ const serverModel = currentAgent?.client?.model || config.providers?.[provider]?.model || 'google/gemma-4-e4b';
145
+ const requestedModel = (customOptions.model && !isAuto)
146
+ ? customOptions.model
147
+ : (provider === serverProvider ? serverModel : (config.providers?.[provider]?.model || 'google/gemma-4-e4b'));
148
+
149
+ const isServerAgentMatch = Boolean(currentAgent?.client && provider === currentAgent.client.provider);
150
+ const baseUrl = (typeof customOptions.baseUrl === 'string' && customOptions.baseUrl.trim()
151
+ ? customOptions.baseUrl.trim().replace(/\/+$/, '')
152
+ : null)
153
+ || (isServerAgentMatch ? currentAgent.client.baseUrl : null)
154
+ || getProviderBaseUrl(provider);
155
+
156
+ const sessionId = normalizeWebSessionId(customOptions.sessionId);
157
+
158
+ const apiKey = customOptions.apiKey
159
+ || (isServerAgentMatch ? currentAgent.client.apiKey : '')
160
+ || '';
161
+
162
+ const client = new LLMClient({
163
+ provider,
164
+ baseUrl,
165
+ model: requestedModel,
166
+ apiKey
167
+ });
168
+
169
+ const modelManager = new ModelManager(client, { requestedModel });
170
+ let resolution = await modelManager.resolve({ requestedModel });
171
+
172
+ if (provider === 'lmstudio' && resolution.state === 'server_offline' && process.env.NODE_ENV !== 'test' && !process.execArgv.includes('--test')) {
173
+ const started = await attemptAutoStartLMStudio(baseUrl, requestedModel);
174
+ if (started) {
175
+ resolution = await modelManager.resolve({ requestedModel });
176
+ }
177
+ }
178
+
179
+ if (provider === 'vllm' && resolution.state === 'server_offline' && !resolution.error?.includes('WSL')) {
180
+ const wslHint = 'vLLM runs on WSL/Linux port 8000 (Windows cannot host it natively). Start it with: wsl -d Ubuntu -- vllm serve <model> --port 8000';
181
+ resolution.error = resolution.error ? `${resolution.error}\n Notice: ${wslHint}` : wslHint;
182
+ }
183
+
184
+ if (resolution.connected && resolution.selectedModel) {
185
+ const agent = new KikuAgent({
186
+ client,
187
+ thinkingMode: customOptions.thinking || 'show',
188
+ memory: new AgentMemory({ persistence: false }),
189
+ sessionId,
190
+ sessionPersistence: false,
191
+ allowedToolNames: PUBLIC_REMOTE_TOOL_NAMES,
192
+ });
193
+ await prepareWebAgent(agent, { history: customOptions.history, sessionId });
194
+ const connection = {
195
+ ready: true,
196
+ agent,
197
+ client,
198
+ provider,
199
+ model: resolution.selectedModel,
200
+ models: resolution.models || [],
201
+ usedFallback: resolution.usedFallback || false,
202
+ resolution,
203
+ contentHash: computeSessionHash(agent.session?.messages || [])
204
+ };
205
+ return connection;
206
+ }
207
+
208
+ return {
209
+ ready: false,
210
+ client,
211
+ provider,
212
+ model: requestedModel,
213
+ models: resolution.models || [],
214
+ state: resolution.state,
215
+ error: resolution.error || 'Server offline',
216
+ resolution
217
+ };
218
+ }
@@ -13,7 +13,7 @@ const DOMAIN_PATTERNS = {
13
13
  fashion_event: /(?:vogue|gala|red\s*carpet|fashion|แฟชั่น|กาล่า|พรมแดง|งานแฟชั่น|盛典|红毯|时尚)/i,
14
14
  drama: /(?:ซีรีส์|ละคร|ภาพยนตร์|หนัง|นักแสดง|บทบาท|ตัวละคร|in\s*blossom|yunxi|ฮวาเจียน|หยุนซี|剧|电视剧|电影|角色)/i,
15
15
  music: /(?:เพลง|อัลบั้ม|ดนตรี|ost|single|concert|คอนเสิร์ต|music|song|歌曲|音乐|演唱会)/i,
16
- brand: /(?:แบรนด์|พรีเซนเตอร์|ambassador|endorsement|versace|luxury|代言|品牌)/i,
16
+ brand: /(?:แบรนด์|พรีเซนเตอร์|พรีเซ็นเตอร์|โฆษณา|แคมเปญ|โปรโมท|โปรโมต|การโปรโมท|ambassador|endorsement|commercial|cf|promo|campaign|versace|prsr|pasha|fred|luxury|代言|品牌|商务|推广)/i,
17
17
  awards: /(?:รางวัล|เกียรติยศ|award|ceremony|获奖|奖项)/i,
18
18
  profile: /(?:ประวัติ|จวีจิ้งอี|ju\s*jingyi|kiku|鞠婧祎|profile|who\s+is)/i
19
19
  };