diff --git a/.gitignore b/.gitignore index 61b33b76..d0abff78 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,10 @@ yarn-error.log* # local env files .env*.local +# log files +dev_server.log +server.log + # vercel .vercel diff --git a/config/model.json b/config/model.json index 6d26ada3..8bfa7dac 100644 --- a/config/model.json +++ b/config/model.json @@ -1 +1,3 @@ -{ "selectedModel": "Grok 4.2" } \ No newline at end of file +{ + "selectedModel": null +} diff --git a/dev_server.log b/dev_server.log index 70d3b005..81e1a748 100644 --- a/dev_server.log +++ b/dev_server.log @@ -1,11 +1,14 @@ $ next dev --turbo - ⚠ Port 3000 is in use, using available port 3001 instead. ▲ Next.js 15.3.6 (Turbopack) - - Local: http://localhost:3001 - - Network: http://192.168.0.2:3001 + - Local: http://localhost:3000 + - Network: http://192.168.0.2:3000 - Environments: .env ✓ Starting... - ○ Compiling middleware ... - ✓ Compiled middleware in 528ms - ✓ Ready in 2.7s +Attention: Next.js now collects completely anonymous telemetry regarding usage. +This information is used to shape Next.js' roadmap and prioritize features. +You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL: +https://nextjs.org/telemetry + + ✓ Compiled middleware in 388ms + ✓ Ready in 1859ms diff --git a/lib/actions/users.ts b/lib/actions/users.ts index 728a4822..65a00de3 100644 --- a/lib/actions/users.ts +++ b/lib/actions/users.ts @@ -1,7 +1,7 @@ // File: lib/actions/users.ts 'use server'; -import { revalidatePath } from 'next/cache'; +import { revalidatePath, unstable_noStore as noStore } from 'next/cache'; import fs from 'fs/promises'; import path from 'path'; @@ -124,24 +124,32 @@ export async function updateSettingsAndUsers( const modelConfigPath = path.resolve(process.cwd(), 'config', 'model.json'); export async function getSelectedModel(): Promise { + noStore(); + console.log(`[DEBUG] getSelectedModel - Reading from path: "${modelConfigPath}"`); try { const data = await fs.readFile(modelConfigPath, 'utf8'); + console.log(`[DEBUG] getSelectedModel - Raw file content: "${data}"`); const config = JSON.parse(data); return config.selectedModel || null; } catch (error) { console.error('Error reading model config:', error); + console.log(`[DEBUG] getSelectedModel - Error reading file:`, error); return null; } } export async function saveSelectedModel(model: string): Promise<{ success: boolean; error?: string }> { + console.log(`[DEBUG] saveSelectedModel - Received model selection: "${model}"`); + console.log(`[DEBUG] saveSelectedModel - Writing to path: "${modelConfigPath}"`); try { const data = JSON.stringify({ selectedModel: model }, null, 2); await fs.writeFile(modelConfigPath, data, 'utf8'); + console.log(`[DEBUG] saveSelectedModel - Successfully wrote to file.`); revalidatePath('/settings'); return { success: true }; } catch (error) { console.error('Error saving model config:', error); + console.log(`[DEBUG] saveSelectedModel - Error writing to file:`, error); return { success: false, error: 'Failed to save selected model.' }; } } diff --git a/lib/agents/inquire.tsx b/lib/agents/inquire.tsx index ee1f9e04..e15926b7 100644 --- a/lib/agents/inquire.tsx +++ b/lib/agents/inquire.tsx @@ -23,7 +23,7 @@ export async function inquire( let finalInquiry: PartialInquiry = {}; const result = await streamObject({ - model: getModel() as LanguageModel, + model: (await getModel()) as LanguageModel, system: `...`, // Your system prompt remains unchanged messages, schema: inquirySchema, diff --git a/lib/agents/query-suggestor.tsx b/lib/agents/query-suggestor.tsx index 0c510314..de2b3749 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: getModel() as LanguageModel, + model: (await getModel()) 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/agents/researcher.tsx b/lib/agents/researcher.tsx index 2b449b31..72a5d737 100644 --- a/lib/agents/researcher.tsx +++ b/lib/agents/researcher.tsx @@ -105,7 +105,7 @@ export async function researcher( ) const result = await nonexperimental_streamText({ - model: getModel(hasImage) as LanguageModel, + model: (await getModel(hasImage)) as LanguageModel, maxTokens: 4096, system: systemPromptToUse, messages, diff --git a/lib/agents/resolution-search.tsx b/lib/agents/resolution-search.tsx index b5682136..862de078 100644 --- a/lib/agents/resolution-search.tsx +++ b/lib/agents/resolution-search.tsx @@ -47,7 +47,7 @@ Analyze the user's prompt and the image to provide a holistic understanding of t // Use generateObject to get the full object at once. const { object } = await generateObject({ - model: getModel(hasImage), + model: await getModel(hasImage), system: systemPrompt, messages: filteredMessages, schema: resolutionSearchSchema, diff --git a/lib/agents/task-manager.tsx b/lib/agents/task-manager.tsx index 7e6b225a..90a72b67 100644 --- a/lib/agents/task-manager.tsx +++ b/lib/agents/task-manager.tsx @@ -16,7 +16,7 @@ export async function taskManager(messages: CoreMessage[]) { } const result = await generateObject({ - model: getModel() as LanguageModel, + model: (await getModel()) as LanguageModel, system: `As a planet computer, your primary objective is to act as an efficient **Task Manager** for the user's query. Your goal is to minimize unnecessary steps and maximize the efficiency of the subsequent exploration phase (researcher agent). You must first analyze the user's input and determine the optimal course of action. You have two options at your disposal: diff --git a/lib/agents/writer.tsx b/lib/agents/writer.tsx index 59249414..f4e4d0ac 100644 --- a/lib/agents/writer.tsx +++ b/lib/agents/writer.tsx @@ -32,7 +32,7 @@ export async function writer( const systemToUse = dynamicSystemPrompt && dynamicSystemPrompt.trim() !== '' ? dynamicSystemPrompt : default_system_prompt; const result = await nonexperimental_streamText({ - model: getModel() as LanguageModel, + model: (await getModel()) as LanguageModel, maxTokens: 2500, system: systemToUse, // Use the dynamic or default system prompt messages diff --git a/lib/utils/index.ts b/lib/utils/index.ts index f82b05ea..64e8a305 100644 --- a/lib/utils/index.ts +++ b/lib/utils/index.ts @@ -1,5 +1,6 @@ import { type ClassValue, clsx } from 'clsx' import { twMerge } from 'tailwind-merge' +import { getSelectedModel } from '@/lib/actions/users' import { openai } from '@ai-sdk/openai' import { createOpenAI } from '@ai-sdk/openai' import { createGoogleGenerativeAI } from '@ai-sdk/google' @@ -16,42 +17,87 @@ export function generateUUID(): string { return uuidv4(); } -export function getModel(requireVision: boolean = false) { - const xaiApiKey = process.env.XAI_API_KEY - const gemini3ProApiKey = process.env.GEMINI_3_PRO_API_KEY - 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' +export async function getModel(requireVision: boolean = false) { + const selectedModel = await getSelectedModel(); - // If vision is required, skip models that don't support it - if (!requireVision && xaiApiKey) { + const xaiApiKey = process.env.XAI_API_KEY; + const gemini3ProApiKey = process.env.GEMINI_3_PRO_API_KEY; + 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 openaiApiKey = process.env.OPENAI_API_KEY; + + if (selectedModel) { + switch (selectedModel) { + case 'Grok 4.2': + if (xaiApiKey) { + const xai = createXai({ + apiKey: xaiApiKey, + baseURL: 'https://api.x.ai/v1', + }); + try { + return xai('grok-4-fast-non-reasoning'); + } catch (error) { + console.error('Selected model "Grok 4.2" is configured but failed to initialize.', error); + throw new Error('Failed to initialize selected model.'); + } + } else { + console.error('User selected "Grok 4.2" but XAI_API_KEY is not set.'); + throw new Error('Selected model is not configured.'); + } + case 'Gemini 3': + if (gemini3ProApiKey) { + const google = createGoogleGenerativeAI({ + apiKey: gemini3ProApiKey, + }); + try { + return google('gemini-3-pro-preview'); + } catch (error) { + console.error('Selected model "Gemini 3" is configured but failed to initialize.', error); + throw new Error('Failed to initialize selected model.'); + } + } else { + console.error('User selected "Gemini 3" but GEMINI_3_PRO_API_KEY is not set.'); + throw new Error('Selected model is not configured.'); + } + case 'GPT-5.1': + if (openaiApiKey) { + const openai = createOpenAI({ + apiKey: openaiApiKey, + }); + return openai('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.'); + } + } + } + + // Default behavior: Grok -> Gemini -> Bedrock -> OpenAI + if (xaiApiKey) { const xai = createXai({ apiKey: xaiApiKey, baseURL: 'https://api.x.ai/v1', - }) - // Optionally, add a check for credit status or skip xAI if credits are exhausted + }); try { - return xai('grok-4-fast-non-reasoning') + return xai('grok-4-fast-non-reasoning'); } catch (error) { - console.warn('xAI API unavailable, falling back to OpenAI:') + console.warn('xAI API unavailable, falling back to next provider:'); } } - // Gemini 3 Pro if (gemini3ProApiKey) { const google = createGoogleGenerativeAI({ apiKey: gemini3ProApiKey, - }) + }); try { - return google('gemini-3-pro-preview') + return google('gemini-3-pro-preview'); } catch (error) { - console.warn('Gemini 3 Pro API unavailable, falling back to next provider:', error) + console.warn('Gemini 3 Pro API unavailable, falling back to next provider:', error); } } - // AWS Bedrock - if (awsAccessKeyId && awsSecretAccessKey) { const bedrock = createAmazonBedrock({ bedrockOptions: { @@ -61,16 +107,15 @@ export function getModel(requireVision: boolean = false) { secretAccessKey: awsSecretAccessKey, }, }, - }) + }); const model = bedrock(bedrockModelId, { additionalModelRequestFields: { top_k: 350 }, - }) - return model + }); + return model; } - // Default fallback (OpenAI gpt-4o supports vision) const openai = createOpenAI({ - apiKey: process.env.OPENAI_API_KEY, - }) - return openai('gpt-4o') + apiKey: openaiApiKey, + }); + return openai('gpt-4o'); } diff --git a/mapbox_mcp/hooks.ts b/mapbox_mcp/hooks.ts index ae317f51..797f8852 100644 --- a/mapbox_mcp/hooks.ts +++ b/mapbox_mcp/hooks.ts @@ -128,7 +128,7 @@ export const useMCPMapClient = () => { setError(null); try { const response = await generateText({ - model: getModel(), + model: await getModel(), tools: toolsRef.current, system: `You are an expert location data processing engine. Your role is to accurately use the available tools to answer location-based queries and provide structured data.