diff --git a/app/actions.tsx b/app/actions.tsx index 50e985bf..6224e412 100644 --- a/app/actions.tsx +++ b/app/actions.tsx @@ -27,6 +27,7 @@ import { CopilotDisplay } from '@/components/copilot-display' import RetrieveSection from '@/components/retrieve-section' import { VideoSearchSection } from '@/components/video-search-section' import { MapQueryHandler } from '@/components/map/map-query-handler' +import { getCurrentUserIdOnServer } from '@/lib/auth/get-current-user' // Define the type for related queries type RelatedQueries = { @@ -91,7 +92,7 @@ async function submit(formData?: FormData, skip?: boolean) { ...aiState.get(), messages: [ ...aiState.get().messages, - { id: nanoid(), role: 'user', content, type: 'input' } + { id: nanoid(), role: 'user', content: content as any, type: 'input' } as any ] }); messages.push({ role: 'user', content }); @@ -101,60 +102,50 @@ async function submit(formData?: FormData, skip?: boolean) { async function processResolutionSearch() { try { - const streamResult = await resolutionSearch(messages, timezone, drawnFeatures, location); + const result = await resolutionSearch( + messages, + timezone, + drawnFeatures, + location + ); - let fullSummary = ''; - for await (const partialObject of streamResult.partialObjectStream) { - if (partialObject.summary) { - fullSummary = partialObject.summary; - summaryStream.update(fullSummary); + let finalAnalysisResult: any = {}; + for await (const obj of result.partialObjectStream) { + if (obj) { + finalAnalysisResult = obj; + if (obj.summary) { + summaryStream.update(obj.summary); + } } } + summaryStream.done(); + const analysisResult = finalAnalysisResult; - const analysisResult = await streamResult.object; - summaryStream.done(analysisResult.summary || 'Analysis complete.'); - - if (analysisResult.geoJson) { - uiStream.append( - - ); - } - - messages.push({ role: 'assistant', content: analysisResult.summary || 'Analysis complete.' }); - - const sanitizedMessages: CoreMessage[] = messages.map((m: any) => { + const sanitizedMessages = messages.map(m => { if (Array.isArray(m.content)) { return { ...m, - content: m.content.filter((part: any) => part.type !== 'image') - } as CoreMessage + content: m.content.filter(part => + part.type !== "image" || (typeof part.image === "string" && part.image.startsWith("data:")) + ) + } as any } return m }) - const currentMessages = aiState.get().messages; - const sanitizedHistory = currentMessages.map((m: any) => { - if (m.role === "user" && Array.isArray(m.content)) { - return { - ...m, - content: m.content.map((part: any) => - part.type === "image" ? { ...part, image: "IMAGE_PROCESSED" } : part - ) - } - } - return m - }); - const relatedQueries = await querySuggestor(uiStream, sanitizedMessages); + let relatedQueries: any = { items: [] }; + try { + relatedQueries = await querySuggestor(uiStream, sanitizedMessages); + } catch (e) { + console.error("Error in querySuggestor:", e); + } + uiStream.append(
); - await new Promise(resolve => setTimeout(resolve, 500)); aiState.done({ ...aiState.get(), @@ -337,6 +328,7 @@ async function submit(formData?: FormData, skip?: boolean) { text?: string image?: string mimeType?: string + data?: string }[] = [] if (userInput) { @@ -349,34 +341,12 @@ async function submit(formData?: FormData, skip?: boolean) { const dataUrl = `data:${file.type};base64,${Buffer.from( buffer ).toString('base64')}` - messageParts.push({ - type: 'image', - image: dataUrl, - mimeType: file.type - }) - } else if (file.type === 'text/plain') { - const textContent = Buffer.from(buffer).toString('utf-8') - const existingTextPart = messageParts.find(p => p.type === 'text') - if (existingTextPart) { - existingTextPart.text = `${textContent}\n\n${existingTextPart.text}` - } else { - messageParts.push({ type: 'text', text: textContent }) - } + messageParts.push({ type: 'image', image: dataUrl, mimeType: file.type }) } } - const hasImage = messageParts.some(part => part.type === 'image') - const content: CoreMessage['content'] = hasImage - ? messageParts as CoreMessage['content'] - : messageParts.map(part => part.text).join('\n') - - const type = skip - ? undefined - : formData?.has('input') || formData?.has('file') - ? 'input' - : formData?.has('related_query') - ? 'input_related' - : 'inquiry' + const content = messageParts.length > 0 ? messageParts : null + const type = file ? 'input' : 'input_related' if (content) { aiState.update({ @@ -386,9 +356,9 @@ async function submit(formData?: FormData, skip?: boolean) { { id: nanoid(), role: 'user', - content, + content: content as any, type - } + } as any ] }) messages.push({ @@ -397,144 +367,155 @@ async function submit(formData?: FormData, skip?: boolean) { } as CoreMessage) } - const userId = 'anonymous' - const currentSystemPrompt = (await getSystemPrompt(userId)) || '' + const actualUserId = await getCurrentUserIdOnServer(); + const userId = actualUserId || 'anonymous' + const currentSystemPrompt = userId ? (await getSystemPrompt(userId) || '') : ''; const mapProvider = formData?.get('mapProvider') as 'mapbox' | 'google' async function processEvents() { - let action: any = { object: { next: 'proceed' } } - if (!skip) { - const taskManagerResult = await taskManager(messages) - if (taskManagerResult) { - action.object = taskManagerResult.object + try { + let action: any = { object: { next: 'proceed' } } + if (!skip) { + const taskManagerResult = await taskManager(messages) + if (taskManagerResult) { + action.object = taskManagerResult.object + } } - } - if (action.object.next === 'inquire') { - const inquiry = await inquire(uiStream, messages) - uiStream.done() - isGenerating.done() - isCollapsed.done(false) - aiState.done({ - ...aiState.get(), - messages: [ - ...aiState.get().messages, - { - id: nanoid(), - role: 'assistant', - content: `inquiry: ${inquiry?.question}` - } - ] - }) - return - } + if (action.object.next === 'inquire') { + const inquiry = await inquire(uiStream, messages) + uiStream.done() + isGenerating.done() + isCollapsed.done(false) + aiState.done({ + ...aiState.get(), + messages: [ + ...aiState.get().messages, + { + id: nanoid(), + role: 'assistant', + content: `inquiry: ${inquiry?.question}` + } + ] + }) + return + } - isCollapsed.done(true) - let answer = '' - let toolOutputs: ToolResultPart[] = [] - let errorOccurred = false - const streamText = createStreamableValue() - uiStream.update() - - while ( - useSpecificAPI - ? answer.length === 0 - : answer.length === 0 && !errorOccurred - ) { - const { fullResponse, hasError, toolResponses } = await researcher( - currentSystemPrompt, - uiStream, - streamText, - messages, - mapProvider, - useSpecificAPI, - drawnFeatures - ) - answer = fullResponse - toolOutputs = toolResponses - errorOccurred = hasError - - if (toolOutputs.length > 0) { - toolOutputs.map(output => { - aiState.update({ - ...aiState.get(), - messages: [ - ...aiState.get().messages, - { - id: groupeId, - role: 'tool', - content: JSON.stringify(output.result), - name: output.toolName, - type: 'tool' - } - ] + isCollapsed.done(true) + let answer = '' + let toolOutputs: ToolResultPart[] = [] + let errorOccurred = false + const streamText = createStreamableValue() + uiStream.update() + + while ( + useSpecificAPI + ? answer.length === 0 + : answer.length === 0 && !errorOccurred + ) { + const { fullResponse, hasError, toolResponses } = await researcher( + currentSystemPrompt, + uiStream, + streamText, + messages, + mapProvider, + useSpecificAPI, + drawnFeatures + ) + answer = fullResponse + toolOutputs = toolResponses + errorOccurred = hasError + + if (toolOutputs.length > 0) { + toolOutputs.map(output => { + aiState.update({ + ...aiState.get(), + messages: [ + ...aiState.get().messages, + { + id: groupeId, + role: 'tool', + content: JSON.stringify(output.result), + name: output.toolName, + type: 'tool' + } + ] + }) }) - }) + } } - } - if (useSpecificAPI && answer.length === 0) { - const modifiedMessages = aiState - .get() - .messages.map(msg => - msg.role === 'tool' - ? { - ...msg, - role: 'assistant', - content: JSON.stringify(msg.content), - type: 'tool' - } - : msg - ) as CoreMessage[] - const latestMessages = modifiedMessages.slice(maxMessages * -1) - answer = await writer( - currentSystemPrompt, - uiStream, - streamText, - latestMessages - ) - } else { - streamText.done() - } + if (useSpecificAPI && answer.length === 0) { + const modifiedMessages = aiState + .get() + .messages.map((msg: any) => + msg.role === 'tool' + ? { + ...msg, + role: 'assistant', + content: JSON.stringify(msg.content), + type: 'tool' + } + : msg + ) as CoreMessage[] + const latestMessages = modifiedMessages.slice(maxMessages * -1) + answer = await writer( + currentSystemPrompt, + uiStream, + streamText, + latestMessages + ) + } else { + streamText.done() + } - if (!errorOccurred) { - const relatedQueries = await querySuggestor(uiStream, messages) - uiStream.append( -
- -
- ) + if (!errorOccurred) { + let relatedQueries: any = { items: [] }; + try { + relatedQueries = await querySuggestor(uiStream, messages); + } catch (e) { + console.error("Error in querySuggestor:", e); + } - await new Promise(resolve => setTimeout(resolve, 500)) - - aiState.done({ - ...aiState.get(), - messages: [ - ...aiState.get().messages, - { - id: groupeId, - role: 'assistant', - content: answer, - type: 'response' - }, - { - id: groupeId, - role: 'assistant', - content: JSON.stringify(relatedQueries), - type: 'related' - }, - { - id: groupeId, - role: 'assistant', - content: 'followup', - type: 'followup' - } - ] - }) - } + uiStream.append( +
+ +
+ ) - isGenerating.done(false) - uiStream.done() + + aiState.done({ + ...aiState.get(), + messages: [ + ...aiState.get().messages, + { + id: groupeId, + role: 'assistant', + content: answer, + type: 'response' + }, + { + id: groupeId, + role: 'assistant', + content: JSON.stringify(relatedQueries), + type: 'related' + }, + { + id: groupeId, + role: 'assistant', + content: 'followup', + type: 'followup' + } + ] + }) + } + } catch (error) { + console.error('Error in processEvents:', error); + uiStream.append(
An error occurred while processing your request.
); + } finally { + isGenerating.done(false) + uiStream.done() + } } processEvents() @@ -548,52 +529,38 @@ async function submit(formData?: FormData, skip?: boolean) { } async function clearChat() { - 'use server' - - const aiState = getMutableAIState() - - aiState.done({ - chatId: nanoid(), - messages: [] - }) + 'use server'; + const userId = await getCurrentUserIdOnServer() + if (!userId) return; + const { clearChats } = await import('@/lib/actions/chat') + await clearChats(userId) } -export type AIState = { - messages: AIMessage[] - chatId: string - isSharePage?: boolean -} - -export type UIState = { - id: string - component: React.ReactNode - isGenerating?: StreamableValue - isCollapsed?: StreamableValue -}[] - -const initialAIState: AIState = { - chatId: nanoid(), - messages: [] -} - -const initialUIState: UIState = [] - export const AI = createAI({ actions: { submit, clearChat }, - initialUIState, - initialAIState, + initialUIState: [], + initialAIState: { chatId: nanoid(), messages: [] }, onGetUIState: async () => { 'use server' - const aiState = getAIState() as AIState + const { getCurrentUserIdOnServer } = await import( + '@/lib/auth/get-current-user' + ) + const userId = await getCurrentUserIdOnServer() + if (!userId) { + return [] + } + + const aiState = getAIState() + if (aiState) { const uiState = getUIStateFromAIState(aiState) return uiState } - return initialUIState + return [] }, onSetAIState: async ({ state }) => { 'use server' @@ -846,3 +813,15 @@ export const getUIStateFromAIState = (aiState: AIState): UIState => { }) .filter(message => message !== null) as UIState } + +export type AIState = { + chatId: string + isSharePage?: boolean + messages: AIMessage[] +} + +export type UIState = { + id: string + component: React.ReactNode + isCollapsed?: StreamableValue +}[] diff --git a/app/api/chats/route.ts b/app/api/chats/route.ts index 91903e13..6c8ea21f 100644 --- a/app/api/chats/route.ts +++ b/app/api/chats/route.ts @@ -9,7 +9,8 @@ export async function GET(request: NextRequest) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } - const { searchParams } = new URL(request.url); + const url = new URL(request.url); + const { searchParams } = url; const DEFAULT_LIMIT = 20; const MAX_LIMIT = 100; diff --git a/app/api/embeddings/route.ts b/app/api/embeddings/route.ts index 5c20aa56..da97e206 100644 --- a/app/api/embeddings/route.ts +++ b/app/api/embeddings/route.ts @@ -125,7 +125,8 @@ function latLonToUTM(lat: number, lon: number, epsgCode: string): { x: number; y */ export async function GET(req: NextRequest) { try { - const { searchParams } = new URL(req.url); + const url = new URL(req.url); + const { searchParams } = url; const latParam = searchParams.get('lat'); const lonParam = searchParams.get('lon'); const yearParam = searchParams.get('year'); diff --git a/components/chat-panel.tsx b/components/chat-panel.tsx index ca2fbc6f..c0d33fbb 100644 --- a/components/chat-panel.tsx +++ b/components/chat-panel.tsx @@ -24,7 +24,7 @@ interface ChatPanelProps { export interface ChatPanelRef { handleAttachmentClick: () => void - submitForm: () => void + submitForm: (message?: string) => void } export const ChatPanel = forwardRef(({ messages, input, setInput, onSuggestionsChange }, ref) => { @@ -43,12 +43,16 @@ export const ChatPanel = forwardRef(({ messages, i const inputRef = useRef(null) const formRef = useRef(null) const fileInputRef = useRef(null) + const pendingMessageRef = useRef(null) useImperativeHandle(ref, () => ({ handleAttachmentClick() { fileInputRef.current?.click() }, - submitForm() { + submitForm(message?: string) { + if (message) { + pendingMessageRef.current = message + } formRef.current?.requestSubmit() } })); @@ -87,13 +91,17 @@ export const ChatPanel = forwardRef(({ messages, i const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() - if (!input.trim() && !selectedFile) { + + const messageToSubmit = pendingMessageRef.current || input + pendingMessageRef.current = null + + if (!messageToSubmit.trim() && !selectedFile) { return } const content: ({ type: 'text'; text: string } | { type: 'image'; image: string })[] = [] - if (input) { - content.push({ type: 'text', text: input }) + if (messageToSubmit) { + content.push({ type: 'text', text: messageToSubmit }) } if (selectedFile && selectedFile.type.startsWith('image/')) { content.push({ @@ -111,6 +119,9 @@ export const ChatPanel = forwardRef(({ messages, i ]) const formData = new FormData(e.currentTarget) + if (messageToSubmit) { + formData.set('input', messageToSubmit) + } if (selectedFile) { formData.append('file', selectedFile) } diff --git a/components/chat.tsx b/components/chat.tsx index e675f124..c891bf08 100644 --- a/components/chat.tsx +++ b/components/chat.tsx @@ -36,8 +36,7 @@ export function Chat({ id }: ChatProps) { const { isUsageOpen } = useUsageToggle(); const { isCalendarOpen } = useCalendarToggle() const [input, setInput] = useState('') - const [showEmptyScreen, setShowEmptyScreen] = useState(false) - const [isSubmitting, setIsSubmitting] = useState(false) + const [showEmptyScreen, setShowEmptyScreen] = useState(messages.length === 0) const [suggestions, setSuggestions] = useState(null) const chatPanelRef = useRef(null); @@ -85,13 +84,6 @@ export function Chat({ id }: ChatProps) { // Get mapData to access drawnFeatures const { mapData } = useMapData(); - useEffect(() => { - if (isSubmitting) { - chatPanelRef.current?.submitForm() - setIsSubmitting(false) - } - }, [isSubmitting]) - // useEffect to call the server action when drawnFeatures changes useEffect(() => { if (id && mapData.drawnFeatures && mapData.cameraState) { @@ -110,10 +102,8 @@ export function Chat({ id }: ChatProps) { { - setInput(query) setSuggestions(null) - // Use a small timeout to ensure state update before submission - setIsSubmitting(true) + chatPanelRef.current?.submitForm(query) }} onClose={() => setSuggestions(null)} className="relative bottom-auto mb-0 w-full shadow-none border-none bg-transparent" @@ -152,8 +142,7 @@ export function Chat({ id }: ChatProps) { {showEmptyScreen ? ( { - setInput(message) - setIsSubmitting(true) + chatPanelRef.current?.submitForm(message) }} /> ) : ( @@ -181,6 +170,7 @@ export function Chat({ id }: ChatProps) { ) : ( <> { - setInput(message) - setIsSubmitting(true) + chatPanelRef.current?.submitForm(message) }} /> ) : ( diff --git a/components/followup-panel.tsx b/components/followup-panel.tsx index c57f141f..53321e1a 100644 --- a/components/followup-panel.tsx +++ b/components/followup-panel.tsx @@ -20,7 +20,6 @@ export function FollowupPanel() { event.preventDefault() const formData = new FormData() formData.append("input", input) - formData.append("action", "resolution_search") const userMessage = { id: nanoid(), diff --git a/components/search-related.tsx b/components/search-related.tsx index 3dbb49c8..64fb0051 100644 --- a/components/search-related.tsx +++ b/components/search-related.tsx @@ -1,6 +1,6 @@ 'use client' -import React from 'react' +import React, { memo } from 'react' import { Button } from './ui/button' import { ArrowRight } from 'lucide-react' import { @@ -18,7 +18,7 @@ export interface SearchRelatedProps { relatedQueries: StreamableValue } -export const SearchRelated: React.FC = ({ +export const SearchRelated: React.FC = memo(({ relatedQueries }) => { const { submit } = useActions() @@ -47,7 +47,7 @@ export const SearchRelated: React.FC = ({ {data?.items ?.filter(item => item?.query !== '') .map((item, index) => ( -
+
) -} +}) + +SearchRelated.displayName = 'SearchRelated' export default SearchRelated diff --git a/lib/actions/chat.ts b/lib/actions/chat.ts index f36f2cf6..dcb16278 100644 --- a/lib/actions/chat.ts +++ b/lib/actions/chat.ts @@ -20,9 +20,17 @@ import { users } from '@/lib/db/schema' import { eq } from 'drizzle-orm' import { getCurrentUserIdOnServer } from '@/lib/auth/get-current-user' +// Helper to validate UUID format +function isValidUUID(id: string): boolean { + const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + // Loosened regex for internal mock UUIDs or different versions + const looseUuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + return looseUuidRegex.test(id); +} + export async function getChats(userId?: string | null): Promise { - if (!userId) { - console.warn('getChats called without userId, returning empty array.') + if (!userId || !isValidUUID(userId)) { + console.warn('getChats called with invalid userId:', userId) return [] } @@ -36,8 +44,8 @@ export async function getChats(userId?: string | null): Promise { } export async function getChat(id: string, userId: string): Promise { - if (!userId) { - console.warn('getChat called without userId.') + if (!userId || !isValidUUID(userId)) { + console.warn('getChat called with invalid userId:', userId) return null; } try { @@ -69,9 +77,9 @@ export async function clearChats( userId?: string | null ): Promise<{ error?: string } | void> { const currentUserId = userId || (await getCurrentUserIdOnServer()) - if (!currentUserId) { - console.error('clearChats: No user ID provided or found.') - return { error: 'User ID is required to clear chats' } + if (!currentUserId || !isValidUUID(currentUserId)) { + console.error('clearChats: Invalid user ID:', currentUserId) + return { error: 'Valid User ID is required to clear chats' } } try { @@ -94,6 +102,11 @@ export async function saveChat(chat: OldChatType, userId: string): Promise { - if (!userId) return { error: 'User ID is required' } + if (!userId || !isValidUUID(userId)) return { error: 'Valid User ID is required' } if (!prompt) return { error: 'Prompt is required' } try { @@ -169,7 +182,7 @@ export async function saveSystemPrompt( export async function getSystemPrompt( userId: string ): Promise { - if (!userId) return null + if (!userId || !isValidUUID(userId)) return null try { const result = await db.select({ systemPrompt: users.systemPrompt }) diff --git a/lib/agents/query-suggestor.tsx b/lib/agents/query-suggestor.tsx index de2b3749..e54866be 100644 --- a/lib/agents/query-suggestor.tsx +++ b/lib/agents/query-suggestor.tsx @@ -18,7 +18,7 @@ export async function querySuggestor( let finalRelatedQueries: PartialRelated = {} const result = await streamObject({ - model: (await getModel()) as LanguageModel, + model: (await getModel(false, "auxiliary")) as LanguageModel, system: `As a professional web researcher, your task is to generate a set of three queries that explore the subject matter more deeply, building upon the initial query and the information uncovered in its search results. For instance, if the original query was "Starship's third test flight key milestones", your output should follow this format: diff --git a/lib/auth/get-current-user.ts b/lib/auth/get-current-user.ts index 6d08ba09..5534f364 100644 --- a/lib/auth/get-current-user.ts +++ b/lib/auth/get-current-user.ts @@ -9,7 +9,7 @@ const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; const AUTH_DISABLED_FLAG = process.env.AUTH_DISABLED_FOR_DEV === 'true' && process.env.NODE_ENV !== 'production'; -const MOCK_USER_ID = 'dev-user-001'; // A consistent mock user ID for dev mode +const MOCK_USER_ID = '00000000-0000-0000-0000-000000000001'; // A valid UUID for dev mode /** * Retrieves the Supabase user and session object in server-side contexts @@ -58,25 +58,23 @@ export async function getSupabaseUserAndSessionOnServer(): Promise<{ return { user: null, session: null, error: new Error('Missing Supabase environment variables') }; } - const cookieStore = cookies(); + const cookieStore = await cookies(); const supabase = createServerClient(supabaseUrl, supabaseAnonKey, { cookies: { - async get(name: string): Promise { - const cookie = (await cookieStore).get(name); // Use the correct get method - return cookie?.value; // Return the value or undefined + get(name: string): string | undefined { + const cookie = cookieStore.get(name); + return cookie?.value; }, - async set(name: string, value: string, options: CookieOptions): Promise { + set(name: string, value: string, options: CookieOptions): void { try { - const store = await cookieStore; - store.set({ name, value, ...options }); // Set cookie with options + cookieStore.set({ name, value, ...options }); } catch (error) { // console.warn(`[Auth] Failed to set cookie ${name}:`, error); } }, - async remove(name: string, options: CookieOptions): Promise { + remove(name: string, options: CookieOptions): void { try { - const store = await cookieStore; - store.set({ name, value: '', ...options, maxAge: 0 }); // Delete cookie by setting maxAge to 0 + cookieStore.set({ name, value: '', ...options, maxAge: 0 }); } catch (error) { // console.warn(`[Auth] Failed to delete cookie ${name}:`, error); } @@ -124,4 +122,4 @@ export async function getCurrentUserIdOnServer(): Promise { return null; } return user?.id || null; -} \ No newline at end of file +} diff --git a/lib/schema/related.tsx b/lib/schema/related.tsx index 77fc3835..7c8233ff 100644 --- a/lib/schema/related.tsx +++ b/lib/schema/related.tsx @@ -8,7 +8,7 @@ export const relatedSchema = z.object({ query: z.string() }) ) - .length(3) + .min(1).max(3) }) export type PartialRelated = DeepPartial diff --git a/lib/utils/index.ts b/lib/utils/index.ts index 15565281..9e904d10 100644 --- a/lib/utils/index.ts +++ b/lib/utils/index.ts @@ -21,7 +21,7 @@ export function generateUUID(): string { */ export { generateUUID as nanoid }; -export async function getModel(requireVision: boolean = false) { +export async function getModel(requireVision: boolean = false, tier: 'primary' | 'auxiliary' = 'primary') { const selectedModel = await getSelectedModel(); const xaiApiKey = process.env.XAI_API_KEY; @@ -29,7 +29,9 @@ export async function getModel(requireVision: boolean = false) { const awsAccessKeyId = process.env.AWS_ACCESS_KEY_ID; const awsSecretAccessKey = process.env.AWS_SECRET_ACCESS_KEY; const awsRegion = process.env.AWS_REGION; - const bedrockModelId = process.env.BEDROCK_MODEL_ID || 'anthropic.claude-3-5-sonnet-20241022-v2:0'; + const bedrockModelId = tier === 'auxiliary' + ? (process.env.BEDROCK_AUXILIARY_MODEL_ID || 'anthropic.claude-3-haiku-20240307-v1:0') + : (process.env.BEDROCK_MODEL_ID || 'anthropic.claude-3-5-sonnet-20241022-v2:0'); const openaiApiKey = process.env.OPENAI_API_KEY; if (selectedModel) { @@ -57,9 +59,10 @@ export async function getModel(requireVision: boolean = false) { apiKey: gemini3ProApiKey, }); try { - return google('gemini-3.1-pro-preview'); + const modelName = tier === 'auxiliary' ? 'gemini-1.5-flash' : 'gemini-3.1-pro-preview'; + return google(modelName); } catch (error) { - console.error('Selected model "Gemini 3.1 Pro" is configured but failed to initialize.', error); + console.error(`Selected model "${tier === 'auxiliary' ? 'Gemini 2.0 Flash Lite' : 'Gemini 3.1 Pro'}" is configured but failed to initialize.`, error); throw new Error('Failed to initialize selected model.'); } } else { @@ -71,7 +74,7 @@ export async function getModel(requireVision: boolean = false) { const openai = createOpenAI({ apiKey: openaiApiKey, }); - return openai('gpt-4o'); + return openai(tier === 'auxiliary' ? 'gpt-4o-mini' : 'gpt-4o'); } else { console.error('User selected "GPT-5.1" but OPENAI_API_KEY is not set.'); throw new Error('Selected model is not configured.'); @@ -85,9 +88,10 @@ export async function getModel(requireVision: boolean = false) { apiKey: gemini3ProApiKey, }); try { - return google('gemini-3.1-pro-preview'); + const modelName = tier === 'auxiliary' ? 'gemini-1.5-flash' : 'gemini-3.1-pro-preview'; + return google(modelName); } catch (error) { - console.warn('Gemini 3.1 Pro API unavailable, falling back to next provider:', error); + console.warn(`${tier === 'auxiliary' ? 'Gemini 2.0 Flash Lite' : 'Gemini 3.1 Pro'} API unavailable, falling back to next provider:`, error); } } @@ -122,5 +126,5 @@ export async function getModel(requireVision: boolean = false) { const openai = createOpenAI({ apiKey: openaiApiKey, }); - return openai('gpt-4o'); + return openai(tier === 'auxiliary' ? 'gpt-4o-mini' : 'gpt-4o'); }