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
3 changes: 3 additions & 0 deletions .env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ GEMINI_3_PRO_API_KEY=your_gemini_3_pro_api_key_here
# If not set, the `search` tool is skipped and the LLM answers from its own knowledge.
TAVILY_API_KEY=your_tavily_api_key

# Firecrawl API (https://firecrawl.dev)
FIRECRAWL_API_KEY=your_firecrawl_api_key

# Supabase Credentials
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL_HERE
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY_HERE
Expand Down
26 changes: 23 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions components/settings/components/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const settingsFormSchema = z.object({
),
newUserEmail: z.string().email().optional().or(z.literal('')),
newUserRole: z.enum(["admin", "editor", "viewer"]).optional(),
domain: z.string().url().optional().or(z.literal('')),
})

export type SettingsFormValues = z.infer<typeof settingsFormSchema>
Expand All @@ -56,6 +57,7 @@ const defaultValues: Partial<SettingsFormValues> = {
"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",
users: [],
domain: "",
}

interface SettingsProps {
Expand Down
172 changes: 151 additions & 21 deletions components/settings/components/system-prompt-form.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,166 @@
import React, { useState, useEffect } from 'react'
import type { UseFormReturn } from "react-hook-form"
import { FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage } from "@/components/ui/form"
import { Textarea } from "@/components/ui/textarea"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import { Loader2, Sparkles } from "lucide-react"
import { useToast } from "@/components/ui/hooks/use-toast"
import { startSystemPromptGeneration, getSystemPromptGenerationJob } from "@/lib/actions/system-prompt"

interface SystemPromptFormProps {
form: UseFormReturn<any>
}

export function SystemPromptForm({ form }: SystemPromptFormProps) {
const { toast } = useToast()
const systemPrompt = form.watch("systemPrompt")
const domain = form.watch("domain")
const characterCount = systemPrompt?.length || 0
const [jobId, setJobId] = useState<string | null>(null)
const [isGenerating, setIsGenerating] = useState(false)

const handleGenerate = async () => {
if (!domain) {
toast({
title: "Domain required",
description: "Please enter a domain to generate a system prompt.",
variant: "destructive",
})
return
}

setIsGenerating(true)
const result = await startSystemPromptGeneration(domain)

if (result.error) {
toast({
title: "Generation failed",
description: result.error,
variant: "destructive",
})
setIsGenerating(false)
} else if (result.jobId) {
setJobId(result.jobId)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

useEffect(() => {
let interval: NodeJS.Timeout | null = null

if (jobId) {
const pollStartTime = Date.now()
const MAX_POLL_DURATION = 120000 // 2 minutes

interval = setInterval(async () => {
const elapsed = Date.now() - pollStartTime
if (elapsed > MAX_POLL_DURATION) {
if (interval) clearInterval(interval)
setJobId(null)
setIsGenerating(false)
toast({
title: "Generation timeout",
description: "The prompt generation took too long. Please try again.",
variant: "destructive",
})
return
}

const job = await getSystemPromptGenerationJob(jobId)

if (job.error || job.status === 'error') {
if (interval) clearInterval(interval)
setJobId(null)
setIsGenerating(false)
toast({
title: "Generation error",
description: job.errorMessage || job.error || "An error occurred during generation.",
variant: "destructive",
})
} else if (job.status === 'complete') {
if (interval) clearInterval(interval)
setJobId(null)
setIsGenerating(false)
if (job.resultPrompt) {
form.setValue("systemPrompt", job.resultPrompt, { shouldValidate: true, shouldDirty: true })
toast({
title: "Prompt generated",
description: "Your system prompt has been generated based on the domain content.",
})
}
}
}, 3000)
}

return () => {
if (interval) clearInterval(interval)
}
}, [jobId, form, toast])

return (
<FormField
control={form.control}
name="systemPrompt"
render={({ field, fieldState, formState }: { field: import("react-hook-form").ControllerRenderProps<any, "systemPrompt">; fieldState: import("react-hook-form").ControllerFieldState; formState: import("react-hook-form").UseFormStateReturn<any>; }) => (
<FormItem>
<FormLabel>System Prompt</FormLabel>
<FormControl>
<Textarea
placeholder="Enter the system prompt for your planetary copilot..."
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 className={characterCount > 1800 ? "text-amber-500" : ""}>{characterCount}/2000</span>
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<div className="space-y-6">
<FormField
control={form.control}
name="domain"
render={({ field }) => (
<FormItem>
<FormLabel>Business Domain</FormLabel>
<div className="flex gap-2">
<FormControl>
<Input
placeholder="example.com"
{...field}
disabled={isGenerating}
/>
</FormControl>
<Button
type="button"
onClick={handleGenerate}
disabled={isGenerating || !domain}
className="shrink-0"
>
{isGenerating ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Generating...
</>
) : (
<>
<Sparkles className="mr-2 h-4 w-4" />
Generate
</>
)}
</Button>
</div>
<FormDescription>
Enter your business website to automatically generate a tailored system prompt.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="systemPrompt"
render={({ field }) => (
<FormItem>
<FormLabel>System Prompt</FormLabel>
<FormControl>
<Textarea
placeholder="Enter the system prompt for your planetary copilot..."
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 className={characterCount > 1800 ? "text-amber-500" : ""}>{characterCount}/2000</span>
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</div>
)
}
12 changes: 12 additions & 0 deletions drizzle/migrations/0002_lively_black_widow.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
CREATE TABLE "prompt_generation_jobs" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" uuid NOT NULL,
"domain" text NOT NULL,
"status" text DEFAULT 'pending' NOT NULL,
"result_prompt" text,
"error_message" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "prompt_generation_jobs" ADD CONSTRAINT "prompt_generation_jobs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
Loading