Skip to content

Commit 37d4b8a

Browse files
committed
Fix some bugs
1 parent d5a756c commit 37d4b8a

30 files changed

+12283
-22
lines changed

.tmp_190f_chat_route.ts

Lines changed: 585 additions & 0 deletions
Large diffs are not rendered by default.

.tmp_190f_mention_constants.ts

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
import type { ChatContext } from '@/stores/panel'
2+
3+
/**
4+
* Mention folder types
5+
*/
6+
export type MentionFolderId =
7+
| 'chats'
8+
| 'workflows'
9+
| 'knowledge'
10+
| 'blocks'
11+
| 'workflow-blocks'
12+
| 'templates'
13+
| 'logs'
14+
15+
/**
16+
* Menu item category types for mention menu (includes folders + docs item)
17+
*/
18+
export type MentionCategory = MentionFolderId | 'docs'
19+
20+
/**
21+
* Configuration interface for folder types
22+
*/
23+
export interface FolderConfig<TItem = any> {
24+
/** Display title in menu */
25+
title: string
26+
/** Data source key in useMentionData return */
27+
dataKey: string
28+
/** Loading state key in useMentionData return */
29+
loadingKey: string
30+
/** Ensure loaded function key in useMentionData return (optional - some folders auto-load) */
31+
ensureLoadedKey?: string
32+
/** Extract label from an item */
33+
getLabel: (item: TItem) => string
34+
/** Extract unique ID from an item */
35+
getId: (item: TItem) => string
36+
/** Empty state message */
37+
emptyMessage: string
38+
/** No match message (when filtering) */
39+
noMatchMessage: string
40+
/** Filter function for matching query */
41+
filterFn: (item: TItem, query: string) => boolean
42+
/** Build the ChatContext object from an item */
43+
buildContext: (item: TItem, workflowId?: string | null) => ChatContext
44+
/** Whether to use insertAtCursor fallback when replaceActiveMentionWith fails */
45+
useInsertFallback?: boolean
46+
}
47+
48+
/**
49+
* Configuration for all folder types in the mention menu
50+
*/
51+
export const FOLDER_CONFIGS: Record<MentionFolderId, FolderConfig> = {
52+
chats: {
53+
title: 'Chats',
54+
dataKey: 'pastChats',
55+
loadingKey: 'isLoadingPastChats',
56+
ensureLoadedKey: 'ensurePastChatsLoaded',
57+
getLabel: (item) => item.title || 'New Chat',
58+
getId: (item) => item.id,
59+
emptyMessage: 'No past chats',
60+
noMatchMessage: 'No matching chats',
61+
filterFn: (item, q) => (item.title || 'New Chat').toLowerCase().includes(q),
62+
buildContext: (item) => ({
63+
kind: 'past_chat',
64+
chatId: item.id,
65+
label: item.title || 'New Chat',
66+
}),
67+
useInsertFallback: false,
68+
},
69+
workflows: {
70+
title: 'All workflows',
71+
dataKey: 'workflows',
72+
loadingKey: 'isLoadingWorkflows',
73+
// No ensureLoadedKey - workflows auto-load from registry store
74+
getLabel: (item) => item.name || 'Untitled Workflow',
75+
getId: (item) => item.id,
76+
emptyMessage: 'No workflows',
77+
noMatchMessage: 'No matching workflows',
78+
filterFn: (item, q) => (item.name || 'Untitled Workflow').toLowerCase().includes(q),
79+
buildContext: (item) => ({
80+
kind: 'workflow',
81+
workflowId: item.id,
82+
label: item.name || 'Untitled Workflow',
83+
}),
84+
useInsertFallback: true,
85+
},
86+
knowledge: {
87+
title: 'Knowledge Bases',
88+
dataKey: 'knowledgeBases',
89+
loadingKey: 'isLoadingKnowledge',
90+
ensureLoadedKey: 'ensureKnowledgeLoaded',
91+
getLabel: (item) => item.name || 'Untitled',
92+
getId: (item) => item.id,
93+
emptyMessage: 'No knowledge bases',
94+
noMatchMessage: 'No matching knowledge bases',
95+
filterFn: (item, q) => (item.name || 'Untitled').toLowerCase().includes(q),
96+
buildContext: (item) => ({
97+
kind: 'knowledge',
98+
knowledgeId: item.id,
99+
label: item.name || 'Untitled',
100+
}),
101+
useInsertFallback: false,
102+
},
103+
blocks: {
104+
title: 'Blocks',
105+
dataKey: 'blocksList',
106+
loadingKey: 'isLoadingBlocks',
107+
ensureLoadedKey: 'ensureBlocksLoaded',
108+
getLabel: (item) => item.name || item.id,
109+
getId: (item) => item.id,
110+
emptyMessage: 'No blocks found',
111+
noMatchMessage: 'No matching blocks',
112+
filterFn: (item, q) => (item.name || item.id).toLowerCase().includes(q),
113+
buildContext: (item) => ({
114+
kind: 'blocks',
115+
blockIds: [item.id],
116+
label: item.name || item.id,
117+
}),
118+
useInsertFallback: false,
119+
},
120+
'workflow-blocks': {
121+
title: 'Workflow Blocks',
122+
dataKey: 'workflowBlocks',
123+
loadingKey: 'isLoadingWorkflowBlocks',
124+
// No ensureLoadedKey - workflow blocks auto-sync from store
125+
getLabel: (item) => item.name || item.id,
126+
getId: (item) => item.id,
127+
emptyMessage: 'No blocks in this workflow',
128+
noMatchMessage: 'No matching blocks',
129+
filterFn: (item, q) => (item.name || item.id).toLowerCase().includes(q),
130+
buildContext: (item, workflowId) => ({
131+
kind: 'workflow_block',
132+
workflowId: workflowId || '',
133+
blockId: item.id,
134+
label: item.name || item.id,
135+
}),
136+
useInsertFallback: true,
137+
},
138+
templates: {
139+
title: 'Templates',
140+
dataKey: 'templatesList',
141+
loadingKey: 'isLoadingTemplates',
142+
ensureLoadedKey: 'ensureTemplatesLoaded',
143+
getLabel: (item) => item.name || 'Untitled Template',
144+
getId: (item) => item.id,
145+
emptyMessage: 'No templates found',
146+
noMatchMessage: 'No matching templates',
147+
filterFn: (item, q) => (item.name || 'Untitled Template').toLowerCase().includes(q),
148+
buildContext: (item) => ({
149+
kind: 'templates',
150+
templateId: item.id,
151+
label: item.name || 'Untitled Template',
152+
}),
153+
useInsertFallback: false,
154+
},
155+
logs: {
156+
title: 'Logs',
157+
dataKey: 'logsList',
158+
loadingKey: 'isLoadingLogs',
159+
ensureLoadedKey: 'ensureLogsLoaded',
160+
getLabel: (item) => item.workflowName,
161+
getId: (item) => item.id,
162+
emptyMessage: 'No executions found',
163+
noMatchMessage: 'No matching executions',
164+
filterFn: (item, q) =>
165+
[item.workflowName, item.trigger || ''].join(' ').toLowerCase().includes(q),
166+
buildContext: (item) => ({
167+
kind: 'logs',
168+
executionId: item.executionId || item.id,
169+
label: item.workflowName,
170+
}),
171+
useInsertFallback: false,
172+
},
173+
}
174+
175+
/**
176+
* Order of folders in the mention menu
177+
*/
178+
export const FOLDER_ORDER: MentionFolderId[] = [
179+
'chats',
180+
'workflows',
181+
'knowledge',
182+
'blocks',
183+
'workflow-blocks',
184+
'templates',
185+
'logs',
186+
]
187+
188+
/**
189+
* Docs item configuration (special case - not a folder)
190+
*/
191+
export const DOCS_CONFIG = {
192+
getLabel: () => 'Docs',
193+
buildContext: (): ChatContext => ({ kind: 'docs', label: 'Docs' }),
194+
} as const
195+
196+
/**
197+
* Total number of items in root menu (folders + docs)
198+
*/
199+
export const ROOT_MENU_ITEM_COUNT = FOLDER_ORDER.length + 1
200+
201+
/**
202+
* Slash command configuration
203+
*/
204+
export interface SlashCommand {
205+
id: string
206+
label: string
207+
}
208+
209+
export const TOP_LEVEL_COMMANDS: readonly SlashCommand[] = [
210+
{ id: 'fast', label: 'Fast' },
211+
{ id: 'research', label: 'Research' },
212+
{ id: 'actions', label: 'Actions' },
213+
] as const
214+
215+
/**
216+
* Maps UI command IDs to API command IDs.
217+
* Some commands have different IDs for display vs API (e.g., "actions" -> "superagent")
218+
*/
219+
export function getApiCommandId(uiCommandId: string): string {
220+
const commandMapping: Record<string, string> = {
221+
actions: 'superagent',
222+
}
223+
return commandMapping[uiCommandId] || uiCommandId
224+
}
225+
226+
export const WEB_COMMANDS: readonly SlashCommand[] = [
227+
{ id: 'search', label: 'Search' },
228+
{ id: 'read', label: 'Read' },
229+
{ id: 'scrape', label: 'Scrape' },
230+
{ id: 'crawl', label: 'Crawl' },
231+
] as const
232+
233+
export const ALL_SLASH_COMMANDS: readonly SlashCommand[] = [...TOP_LEVEL_COMMANDS, ...WEB_COMMANDS]
234+
235+
export const ALL_COMMAND_IDS = ALL_SLASH_COMMANDS.map((cmd) => cmd.id)
236+
237+
/**
238+
* Get display label for a command ID
239+
*/
240+
export function getCommandDisplayLabel(commandId: string): string {
241+
const command = ALL_SLASH_COMMANDS.find((cmd) => cmd.id === commandId)
242+
return command?.label || commandId.charAt(0).toUpperCase() + commandId.slice(1)
243+
}
244+
245+
/**
246+
* Model configuration options
247+
*/
248+
export const MODEL_OPTIONS = [
249+
{ value: 'claude-4.6-opus', label: 'Claude 4.6 Opus' },
250+
{ value: 'claude-4.5-opus', label: 'Claude 4.5 Opus' },
251+
{ value: 'claude-4.5-sonnet', label: 'Claude 4.5 Sonnet' },
252+
{ value: 'claude-4.5-haiku', label: 'Claude 4.5 Haiku' },
253+
{ value: 'gpt-5.2-codex', label: 'GPT 5.2 Codex' },
254+
{ value: 'gpt-5.2-pro', label: 'GPT 5.2 Pro' },
255+
{ value: 'gemini-3-pro', label: 'Gemini 3 Pro' },
256+
] as const
257+
258+
/**
259+
* Threshold for considering input "near top" of viewport (in pixels)
260+
*/
261+
export const NEAR_TOP_THRESHOLD = 300
262+
263+
/**
264+
* Scroll tolerance for mention menu positioning (in pixels)
265+
*/
266+
export const SCROLL_TOLERANCE = 8
267+
268+
/**
269+
* Shared CSS classes for menu state text (loading, empty states)
270+
*/
271+
export const MENU_STATE_TEXT_CLASSES = 'px-[8px] py-[8px] text-[12px] text-[var(--text-muted)]'
272+
273+
/**
274+
* Calculates the next index for circular navigation (wraps around at bounds)
275+
*/
276+
export function getNextIndex(current: number, direction: 'up' | 'down', maxIndex: number): number {
277+
if (direction === 'down') {
278+
return current >= maxIndex ? 0 : current + 1
279+
}
280+
return current <= 0 ? maxIndex : current - 1
281+
}

0 commit comments

Comments
 (0)