Skip to content
Merged
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
2 changes: 1 addition & 1 deletion components/settings/components/settings-skeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export function SettingsSkeleton() {
<Tabs.Root defaultValue="system-prompt" className="w-full">
<Tabs.List className="grid w-full grid-cols-2 md:grid-cols-4 gap-2">
<Tabs.Trigger value="system-prompt">System</Tabs.Trigger>
<Tabs.Trigger value="model">Models</Tabs.Trigger>
<Tabs.Trigger value="tool">Tools</Tabs.Trigger>
<Tabs.Trigger value="user-management">Users</Tabs.Trigger>
<Tabs.Trigger value="map">Map</Tabs.Trigger>
</Tabs.List>
Expand Down
20 changes: 10 additions & 10 deletions components/settings/components/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
import { SystemPromptForm } from './system-prompt-form'
import { ModelSelectionForm } from './model-selection-form'
import { ToolSelectionForm } from './tool-selection-form'
import { UserManagementForm } from './user-management-form';
import { Form } from "@/components/ui/form"
import { useSettingsStore, MapProvider } from "@/lib/store/settings";
Expand All @@ -37,7 +37,7 @@ const settingsFormSchema = z.object({
message: "System prompt cannot exceed 2000 characters.",
}),
selectedModel: z.string().refine(value => value.trim() !== '', {
message: "Please select a model.",
message: "Please select a tool.",
}),
users: z.array(
z.object({
Expand All @@ -57,7 +57,7 @@ export type SettingsFormValues = z.infer<typeof settingsFormSchema>
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.",
selectedModel: "Gemini 3.1 Pro",
selectedModel: "QCX-Terra",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This default only applies when the database has no stored value. fetchData still writes any legacy selectedModel directly into the form, while the schema accepts any non-empty string; after removing the old options, an existing user with e.g. Gemini 3.1 Pro will have no matching Select/Radio item and Save will preserve that disallowed value. Please normalize or migrate legacy values to SkyFi/QCX-Terra and enforce the allowlist in the save action.

users: [],
domain: "",
}
Expand All @@ -70,7 +70,7 @@ export function Settings({ initialTab = "system-prompt" }: SettingsProps) {
const { toast } = useToast()
const router = useRouter()
const [isSaving, setIsSaving] = useState(false)
const [currentTab, setCurrentTab] = useState(initialTab);
const [currentTab, setCurrentTab] = useState(initialTab === "model" ? "tool" : initialTab);
const { mapProvider, setMapProvider } = useSettingsStore();
const { user, loading: authLoading } = useCurrentUser();
const { theme, setTheme } = useTheme()
Expand All @@ -81,7 +81,7 @@ export function Settings({ initialTab = "system-prompt" }: SettingsProps) {
}, [])

useEffect(() => {
setCurrentTab(initialTab);
setCurrentTab(initialTab === "model" ? "tool" : initialTab);
}, [initialTab]);

const userId = user?.id;
Expand Down Expand Up @@ -212,7 +212,7 @@ export function Settings({ initialTab = "system-prompt" }: SettingsProps) {
<Tabs.Root value={currentTab} onValueChange={setCurrentTab} className="w-full">
<Tabs.List className="grid w-full grid-cols-2 md:grid-cols-4 gap-2">
<Tabs.Trigger value="system-prompt" className="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 data-[state=active]:bg-primary/80">System</Tabs.Trigger>
<Tabs.Trigger value="model" className="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 data-[state=active]:bg-primary/80">Models</Tabs.Trigger>
<Tabs.Trigger value="tool" className="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 data-[state=active]:bg-primary/80">Tools</Tabs.Trigger>
<Tabs.Trigger value="user-management" className="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 data-[state=active]:bg-primary/80">Users</Tabs.Trigger>
<Tabs.Trigger value="map" className="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 data-[state=active]:bg-primary/80">Map</Tabs.Trigger>
</Tabs.List>
Expand All @@ -236,14 +236,14 @@ export function Settings({ initialTab = "system-prompt" }: SettingsProps) {
</Card>
</Tabs.Content>

<Tabs.Content value="model" className="mt-6">
<Tabs.Content value="tool" className="mt-6">
<Card>
<CardHeader>
<CardTitle>Models</CardTitle>
<CardDescription>Choose the AI model that powers your planetary copilot</CardDescription>
<CardTitle>Tools</CardTitle>
<CardDescription>Choose the tool that powers your planetary copilot</CardDescription>
</CardHeader>
<CardContent>
<ModelSelectionForm form={form} />
<ToolSelectionForm form={form} />
</CardContent>
</Card>
</Tabs.Content>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ import {
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Sparkles, Zap, Rocket, Cpu, Earth } from "lucide-react";
import { Earth, Orbit } from "lucide-react";

interface ModelSelectionFormProps {
interface ToolSelectionFormProps {
form: UseFormReturn<any>;
}

const models = [
const tools = [
{
id: "QCX-Terra",
name: "QCX-Terra",
Expand All @@ -35,54 +35,38 @@ const models = [
badgeVariant: "default" as const,
},
{
id: "Grok 4.2",
name: "Grok 4.2",
description: "The latest from xAI, pushing the boundaries of reasoning and problem-solving.",
icon: Rocket,
badge: "New",
id: "SkyFi",
name: "SkyFi",
description: "On-demand satellite imagery and Earth intelligence analytics.",
icon: Orbit,
badge: "Satellite",
badgeVariant: "secondary" as const,
},
{
id: "Gemini 3.1 Pro",
name: "Gemini 3.1 Pro",
description: "Google's latest reasoning model, excelling at multimodal understanding and complex agentic tasks.",
icon: Sparkles,
badge: "Advanced",
badgeVariant: "outline" as const,
},
{
id: "GPT-5.1",
name: "GPT-5.1",
description: "The cutting-edge of language models, offering unparalleled performance in creative and analytical tasks.",
icon: Zap,
badge: "Expert",
badgeVariant: "outline" as const,
},
];

export function ModelSelectionForm({ form }: ModelSelectionFormProps) {
export function ToolSelectionForm({ form }: ToolSelectionFormProps) {
return (
<FormField
control={form.control}
name="selectedModel"
render={({ field }) => (
<FormItem className="space-y-4">
<div className="flex items-center justify-between">
<FormLabel>AI Model</FormLabel>
<div className="flex items-center gap-2">
<span className="text-sm text-muted-foreground">Quick Select:</span>
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<FormLabel className="text-base font-semibold">Planetary Tool</FormLabel>
<div className="flex items-center gap-2 flex-wrap sm:flex-nowrap">
<span className="text-sm text-muted-foreground whitespace-nowrap">Quick Select:</span>
<Select onValueChange={field.onChange} value={field.value}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Select model" />
<SelectTrigger className="w-full sm:w-[180px]">
<SelectValue placeholder="Select tool" />
</SelectTrigger>
<SelectContent>
{models.map((model) => {
const Icon = model.icon;
{tools.map((tool) => {
const Icon = tool.icon;
return (
<SelectItem key={model.id} value={model.id}>
<SelectItem key={tool.id} value={tool.id}>
<div className="flex items-center gap-2">
<Icon className="h-4 w-4" />
<span>{model.name}</span>
<span>{tool.name}</span>
</div>
</SelectItem>
);
Expand All @@ -97,32 +81,32 @@ export function ModelSelectionForm({ form }: ModelSelectionFormProps) {
value={field.value}
className="space-y-3"
>
{models.map((model) => {
const Icon = model.icon;
{tools.map((tool) => {
const Icon = tool.icon;
return (
<FormItem key={model.id} className="space-y-0">
<FormItem key={tool.id} className="space-y-0">
<FormControl>
<RadioGroupItem
value={model.id}
id={model.id}
value={tool.id}
id={tool.id}
className="peer sr-only"
/>
</FormControl>
<FormLabel htmlFor={model.id} className="cursor-pointer">
<FormLabel htmlFor={tool.id} className="cursor-pointer">
<Card className="border-2 transition-all peer-data-[state=checked]:border-primary">
<CardContent className="p-4 flex items-start gap-4">
<div className="h-10 w-10 rounded-full bg-primary/10 flex items-center justify-center text-primary">
<Icon className="h-5 w-5" />
</div>
<div className="flex-1 space-y-1">
<div className="flex items-center gap-2">
<h4 className="font-medium">{model.name}</h4>
<Badge variant={model.badgeVariant}>
{model.badge}
<h4 className="font-medium">{tool.name}</h4>
<Badge variant={tool.badgeVariant}>
{tool.badge}
</Badge>
</div>
<p className="text-sm text-muted-foreground">
{model.description}
{tool.description}
</p>
</div>
</CardContent>
Expand All @@ -134,8 +118,8 @@ export function ModelSelectionForm({ form }: ModelSelectionFormProps) {
</RadioGroup>
</FormControl>
<FormDescription>
Select the AI model that will power your planetary copilot.
Different models have different capabilities and performance
Select the tool that will power your planetary copilot.
Different tools have different capabilities and performance
characteristics.
</FormDescription>
<FormMessage />
Expand Down
14 changes: 14 additions & 0 deletions lib/auth/use-current-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@ import { useUser } from '@clerk/nextjs';
export function useCurrentUser() {
const { user, isLoaded } = useUser();

if (process.env.NEXT_PUBLIC_PLAYWRIGHT_TEST === 'true') {
return {
user: {
id: "mock-user-id",
email: "mock_user@example.com",
user_metadata: {
name: "Mock User",
avatar_url: ""
}
},
loading: false
};
}

// Map Clerk user to the structure expected by consumers if necessary
// For now, we'll return a simplified version
return {
Expand Down
8 changes: 7 additions & 1 deletion middleware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { clerkMiddleware } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";

export default clerkMiddleware();
export default function middleware(request: any, event: any) {
if (process.env.NEXT_PUBLIC_PLAYWRIGHT_TEST === "true") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This makes the entire Clerk middleware bypass when NEXT_PUBLIC_PLAYWRIGHT_TEST=true. Because this is a NEXT_PUBLIC_ flag and there is no NODE_ENV guard, a production deployment with the flag set would skip Clerk middleware for every matched route, including API and server-action paths. Please keep the test bypass out of production (for example, use a test-only server-side harness) and preserve Clerk middleware in deployed builds.

return NextResponse.next();
}
return clerkMiddleware()(request, event);
}

export const config = {
matcher: [
Expand Down
2 changes: 1 addition & 1 deletion public/sw.js

Large diffs are not rendered by default.