Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions components/header-search-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export function HeaderSearchButton() {
variant="ghost"
size="icon"
onClick={handleResolutionSearch}
disabled={isAnalyzing || !map || !actions}
disabled={isAnalyzing || (mapProvider === "mapbox" && !map) || !actions}
title="Analyze current map view"
>
{isAnalyzing ? (
Expand All @@ -183,7 +183,7 @@ export function HeaderSearchButton() {
)

const mobileButton = (
<Button variant="ghost" size="icon" onClick={handleResolutionSearch} disabled={isAnalyzing || !map || !actions} data-testid="mobile-search-button">
<Button variant="ghost" size="icon" onClick={handleResolutionSearch} disabled={isAnalyzing || (mapProvider === "mapbox" && !map) || !actions} data-testid="mobile-search-button">
{isAnalyzing ? (
<div className="h-[1.2rem] w-[1.2rem] animate-spin rounded-full border-b-2 border-current"></div>
) : (
Expand Down
2 changes: 1 addition & 1 deletion components/settings/components/model-selection-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export function ModelSelectionForm({ form }: ModelSelectionFormProps) {
</RadioGroup>
</FormControl>
<FormDescription>
Select the AI model that will power your planetary copilot.
Select the AI model that will power your planet computer.
Different models have different capabilities and performance
characteristics.
</FormDescription>
Expand Down
11 changes: 4 additions & 7 deletions components/settings/components/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ import { SettingsSkeleton } from './settings-skeleton'
const settingsFormSchema = z.object({
systemPrompt: z
.string()
.min(10, {
message: "System prompt must be at least 10 characters.",
})
.min(0)
.max(2000, {
message: "System prompt cannot exceed 2000 characters.",
}),
Comment on lines 28 to 34
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify handling of empty system prompts in saveSystemPrompt and chat logic

# Search for saveSystemPrompt implementation
echo "=== saveSystemPrompt implementation ==="
ast-grep --pattern 'export async function saveSystemPrompt($$$) {
  $$$
}'

# Search for system prompt usage in chat/AI logic
echo -e "\n=== System prompt usage in chat logic ==="
rg -n -A 5 -B 2 'systemPrompt' --type ts --glob '**/chat/**' --glob '**/ai/**' --glob '**/lib/actions/**'

# Look for potential string concatenation or template usage
echo -e "\n=== Potential template/concatenation usage ==="
rg -n -P 'systemPrompt.*\+|`.*\$\{.*systemPrompt.*\}' --type ts

Repository: QueueLab/QCX

Length of output: 2454


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Locate saveSystemPrompt / getSystemPrompt definitions ==="
rg -n --hidden --no-ignore -S "function saveSystemPrompt|saveSystemPrompt\s*\(|function getSystemPrompt|getSystemPrompt\s*\(" -t ts -g '!*node_modules*'

echo -e "\n=== Inspect lib/actions/chat.ts around getSystemPrompt/saveSystemPrompt and model usage ==="
FILE="lib/actions/chat.ts"
if [ -f "$FILE" ]; then
  nl -ba "$FILE" | sed -n '1,260p'
else
  echo "Missing $FILE"
fi

echo -e "\n=== Inspect settings schema and defaults in components/settings/components/settings.tsx ==="
SFILE="components/settings/components/settings.tsx"
if [ -f "$SFILE" ]; then
  nl -ba "$SFILE" | sed -n '1,160p'
else
  echo "Missing $SFILE"
fi

echo -e "\n=== Other systemPrompt usages in AI/chat code paths ==="
rg -n -S "systemPrompt" -t ts -g 'lib/**' -g 'components/**' -g 'app/**' | head -n 200

Repository: QueueLab/QCX

Length of output: 669


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== lib/actions/chat.ts: saveSystemPrompt & getSystemPrompt ==="
sed -n '130,210p' lib/actions/chat.ts

echo -e "\n=== app/actions.tsx: currentSystemPrompt handling ==="
sed -n '430,540p' app/actions.tsx

echo -e "\n=== components/settings/components/settings.tsx: schema + default ==="
sed -n '1,160p' components/settings/components/settings.tsx

echo -e "\n=== Find where systemPrompt/currentSystemPrompt is passed to the model ==="
rg -n -S "currentSystemPrompt|systemPrompt\s*[:)]|system:\s*.*(systemPrompt|currentSystemPrompt)" -t ts app lib components

echo -e "\n=== Find any normalization/fallback for empty systemPrompt ==="
rg -n -S "systemPrompt.*\|\|\s*''|systemPrompt.*??|if\s*\(.*systemPrompt|trim\(\).*systemPrompt" -t ts app lib components

Repository: QueueLab/QCX

Length of output: 12654


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== lib/agents/researcher.tsx: how systemPromptToUse is derived ==="
sed -n '1,220p' lib/agents/researcher.tsx

echo -e "\n=== lib/agents/resolution-search.tsx: system prompt handling ==="
sed -n '1,220p' lib/agents/resolution-search.tsx

echo -e "\n=== components/settings/components/system-prompt-form.tsx (UI constraints) ==="
sed -n '1,120p' components/settings/components/system-prompt-form.tsx

Repository: QueueLab/QCX

Length of output: 14997


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,120p' lib/db/schema.ts
sed -n '120,200p' lib/db/schema.ts

Repository: QueueLab/QCX

Length of output: 6945


Fix empty system prompt persistence mismatch

The form schema allows an empty systemPrompt (.min(0) with default ""), but lib/actions/chat.ts’s saveSystemPrompt rejects it via if (!prompt) return { error: 'Prompt is required' }, so clearing/saving an empty prompt fails.

Chat/AI runtime is likely safe because app/actions.tsx converts null to '', and lib/agents/researcher.tsx falls back to getDefaultSystemPrompt(...) when dynamicSystemPrompt.trim() is empty.

Update saveSystemPrompt to accept empty strings (or convert empty string to null for storage) so UI behavior matches backend behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/settings/components/settings.tsx` around lines 28 - 34, The
frontend allows an empty systemPrompt but saveSystemPrompt (in
lib/actions/chat.ts) rejects falsy prompts; update saveSystemPrompt to accept
empty string values by either treating an empty string as valid or converting
empty strings to null before validation/storage so clearing the prompt succeeds;
specifically, modify the saveSystemPrompt function's input check (the if
(!prompt) early return) to allow "" (or normalize "" -> null) and proceed to
persist that normalized value, ensuring callers like app/actions.tsx and
lib/agents/researcher.tsx still get the expected fallback behavior.

Expand All @@ -52,8 +50,7 @@ export type SettingsFormValues = z.infer<typeof settingsFormSchema>

// Default values
const defaultValues: Partial<SettingsFormValues> = {
systemPrompt:
"You are a planetary copilot, an AI assistant designed to help users with information about planets, space exploration, and astronomy. Provide accurate, educational, and engaging responses about our solar system and beyond.",
systemPrompt: "",
selectedModel: "Gemini 3.1 Pro",
users: [],
}
Expand Down Expand Up @@ -180,7 +177,7 @@ export function Settings({ initialTab = "system-prompt" }: SettingsProps) {
<Card>
<CardHeader>
<CardTitle>System Prompt</CardTitle>
<CardDescription>Customize the behavior and persona of your planetary copilot</CardDescription>
<CardDescription>Customize the behavior and persona of your planet computer</CardDescription>
</CardHeader>
<CardContent>
<SystemPromptForm form={form} />
Expand All @@ -192,7 +189,7 @@ export function Settings({ initialTab = "system-prompt" }: SettingsProps) {
<Card>
<CardHeader>
<CardTitle>Model Selection</CardTitle>
<CardDescription>Choose the AI model that powers your planetary copilot</CardDescription>
<CardDescription>Choose the AI model that powers your planet computer</CardDescription>
</CardHeader>
<CardContent>
<ModelSelectionForm form={form} />
Expand Down
4 changes: 2 additions & 2 deletions components/settings/components/system-prompt-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ export function SystemPromptForm({ form }: SystemPromptFormProps) {
<FormLabel>System Prompt</FormLabel>
<FormControl>
<Textarea
placeholder="Enter the system prompt for your planetary copilot..."
placeholder="Enter your planet computer system prompt"
className="min-h-[200px] resize-y"
{...field}
/>
</FormControl>
<FormDescription className="flex justify-between">
<span>Define how your copilot should behave and respond to user queries.</span>
<span>Define how your planet computer should behave and respond to user queries.</span>
<span className={characterCount > 1800 ? "text-amber-500" : ""}>{characterCount}/2000</span>
</FormDescription>
<FormMessage />
Expand Down
2 changes: 1 addition & 1 deletion components/settings/settings-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function SettingsView() {
<div className="flex justify-between items-center mb-8">
<div>
<h1 className="text-3xl font-bold tracking-tight">Settings</h1>
<p className="text-muted-foreground">Manage your planetary copilot preferences and user access</p>
<p className="text-muted-foreground">Manage your planet computer preferences and user access</p>
</div>
<Button variant="ghost" size="icon" onClick={handleClose}>
<Minus className="h-6 w-6" />
Expand Down
9 changes: 5 additions & 4 deletions lib/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@ export async function getModel(requireVision: boolean = false) {

if (selectedModel) {
switch (selectedModel) {
case 'QCX-Terra':
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');
return xai(requireVision ? "grok-vision-beta" : "grok-4-fast-non-reasoning");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify where getModel() is used with structured-output APIs.
# Expected: identify call sites where getModel(...) feeds streamObject/generateObject flows.

set -euo pipefail

echo "== getModel call sites =="
rg -n -C2 '\bgetModel\s*\(' --type ts --type tsx

echo
echo "== structured output API usage (streamObject/generateObject) =="
rg -n -C3 '\b(streamObject|generateObject)\s*\(' --type ts --type tsx

echo
echo "== resolution-search and related agents around model selection =="
rg -n -C4 'model:\s*await\s+getModel|model:\s*\(await getModel' lib --type ts --type tsx

Repository: QueueLab/QCX

Length of output: 128


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== getModel call sites =="
rg -n -C2 '\bgetModel\s*\(' lib -g'*.ts' -g'*.tsx'

echo
echo "== structured output call sites =="
rg -n -C3 '\b(streamObject|generateObject)\s*\(' lib -g'*.ts' -g'*.tsx'

echo
echo "== look for model: (await) getModel near structured output usage =="
rg -n -C4 'model:\s*\(?\s*(await\s+)?getModel' lib -g'*.ts' -g'*.tsx'

Repository: QueueLab/QCX

Length of output: 10620


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== lib/utils/index.ts around getModel =="
sed -n '1,140p' lib/utils/index.ts | nl -ba | sed -n '1,140p'

echo
echo "== lib/utils/index.ts: lines ~35-110 (for exact grok routing) =="
sed -n '35,110p' lib/utils/index.ts | nl -ba

echo
echo "== lib/agents/task-manager.tsx: generateObject block =="
sed -n '1,120p' lib/agents/task-manager.tsx | nl -ba | sed -n '1,120p'

echo
echo "== lib/agents/query-suggestor.tsx: streamObject block end (schema) =="
sed -n '1,120p' lib/agents/query-suggestor.tsx | nl -ba | sed -n '40,120p'

echo
echo "== lib/agents/inquire.tsx: streamObject block end (schema) =="
sed -n '1,120p' lib/agents/inquire.tsx | nl -ba | sed -n '1,120p'

Repository: QueueLab/QCX

Length of output: 158


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== lib/utils/index.ts (first 140 lines) =="
sed -n '1,140p' lib/utils/index.ts | cat -n

echo
echo "== lib/agents/task-manager.tsx (first 140 lines) =="
sed -n '1,140p' lib/agents/task-manager.tsx | cat -n

echo
echo "== lib/agents/query-suggestor.tsx (first 120 lines) =="
sed -n '1,120p' lib/agents/query-suggestor.tsx | cat -n

echo
echo "== lib/agents/inquire.tsx (first 120 lines) =="
sed -n '1,120p' lib/agents/inquire.tsx | cat -n

Repository: QueueLab/QCX

Length of output: 15644


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== lib/utils/index.ts: grok-4-fast-non-reasoning routing =="
rg -n "grok-4-fast-non-reasoning" lib/utils/index.ts

echo
echo "== structured output call sites using getModel() ==" 
rg -n -C2 "model:\s*\(await\s+getModel\(\)\)" lib -g'*.ts' -g'*.tsx' | rg -n -C2 "(streamObject|generateObject)"

echo
echo "== structured output call sites (streamObject/generateObject) that pass schema =="
rg -n -C2 "(streamObject|generateObject)\s*\(" lib -g'*.ts' -g'*.tsx' | rg -n -C2 "schema:"

Repository: QueueLab/QCX

Length of output: 2028


Route structured-output calls to a JSON-schema-compatible model (xAI grok-4-fast-non-reasoning is unsafe)

lib/utils/index.ts selects xAI grok-4-fast-non-reasoning whenever requireVision is false (lines 45 and 90), but multiple structured-output call sites use generateObject/streamObject with a schema while calling getModel() with the default requireVision=false, e.g.:

  • lib/agents/task-manager.tsx: generateObject({ model: (await getModel()), schema: nextActionSchema })
  • lib/agents/query-suggestor.tsx: streamObject({ model: (await getModel()), schema: relatedSchema })
  • lib/agents/inquire.tsx: streamObject({ model: (await getModel()), schema: inquirySchema })
  • (Also) lib/agents/resolution-search.tsx / lib/actions/suggest.ts: streamObject(..., schema: ...) with getModel(hasImage) (so non-image paths can still hit grok-4-fast-non-reasoning)

Per the guidance, structured-output workers must not use grok-4-fast-non-reasoning; model selection needs a dedicated “structured-output/JSON-schema capable” signal (not just requireVision) and must route those calls to a compatible model (e.g., OpenAI gpt-4o).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/utils/index.ts` at line 45, getModel currently only switches on
requireVision and returns xai("grok-4-fast-non-reasoning") for non-vision use,
which is unsafe for structured-output; add a new boolean parameter (e.g.,
requireStructuredOutput or supportsJsonSchema) to the getModel signature in
lib/utils/index.ts and when true return a JSON-schema-capable model (e.g.,
openai("gpt-4o") or another approved model) instead of
grok-4-fast-non-reasoning; update all call sites that use
generateObject/streamObject with a schema (e.g., generateObject({ model: (await
getModel()), schema: nextActionSchema }) in lib/agents/task-manager.tsx and
streamObject calls in lib/agents/query-suggestor.tsx, lib/agents/inquire.tsx,
lib/agents/resolution-search.tsx and lib/actions/suggest.ts) to pass the new
flag so structured-output flows select the JSON-schema-capable model while
leaving vision and other paths unchanged.

} catch (error) {
console.error('Selected model "Grok 4.2" is configured but failed to initialize.', error);
throw new Error('Failed to initialize selected model.');
Expand All @@ -57,7 +58,7 @@ export async function getModel(requireVision: boolean = false) {
apiKey: gemini3ProApiKey,
});
try {
return google('gemini-3.1-pro-preview');
return google(requireVision ? "gemini-1.5-pro" : "gemini-3.1-pro-preview");
} catch (error) {
console.error('Selected model "Gemini 3.1 Pro" is configured but failed to initialize.', error);
throw new Error('Failed to initialize selected model.');
Expand Down Expand Up @@ -86,7 +87,7 @@ export async function getModel(requireVision: boolean = false) {
baseURL: 'https://api.x.ai/v1',
});
try {
return xai('grok-4-fast-non-reasoning');
return xai(requireVision ? "grok-vision-beta" : "grok-4-fast-non-reasoning");
} catch (error) {
console.warn('xAI API unavailable, falling back to next provider:');
}
Expand All @@ -97,7 +98,7 @@ export async function getModel(requireVision: boolean = false) {
apiKey: gemini3ProApiKey,
});
try {
return google('gemini-3.1-pro-preview');
return google(requireVision ? "gemini-1.5-pro" : "gemini-3.1-pro-preview");
} catch (error) {
console.warn('Gemini 3.1 Pro API unavailable, falling back to next provider:', error);
}
Expand Down