diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown.tsx index 5f02871ce14..3602fb585b9 100644 --- a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown.tsx +++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown.tsx @@ -44,18 +44,20 @@ const FeedbackDropdown = ({ className }: { className?: string }) => {
What would you like to share?
- -
diff --git a/apps/studio/pages/api/ai/feedback/classify.ts b/apps/studio/pages/api/ai/feedback/classify.ts index 3a064fe7de1..c27a76c3d4d 100644 --- a/apps/studio/pages/api/ai/feedback/classify.ts +++ b/apps/studio/pages/api/ai/feedback/classify.ts @@ -1,17 +1,10 @@ -import { ContextLengthError } from 'ai-commands' -import { classifyFeedback } from 'ai-commands/edge' -import apiWrapper from 'lib/api/apiWrapper' +import { generateObject } from 'ai' import { NextApiRequest, NextApiResponse } from 'next' - -const openAiKey = process.env.OPENAI_API_KEY +import { z } from 'zod' +import { getModel } from 'lib/ai/model' +import apiWrapper from 'lib/api/apiWrapper' async function handler(req: NextApiRequest, res: NextApiResponse) { - if (!openAiKey) { - return res.status(500).json({ - error: 'No OPENAI_API_KEY set. Create this environment variable to use AI features.', - }) - } - const { method } = req switch (method) { @@ -28,18 +21,83 @@ export async function handlePost(req: NextApiRequest, res: NextApiResponse) { body: { prompt }, } = req + if (!prompt) { + return res.status(400).json({ + error: 'Prompt is required', + }) + } + try { - const result = await classifyFeedback(prompt) - res.status(200).json({ feedback_category: result }) - return + const { model, error: modelError } = await getModel() + + if (modelError) { + return res.status(500).json({ error: modelError.message }) + } + + const { object } = await generateObject({ + model, + schema: z.object({ + feedback_category: z.enum(['support', 'feedback', 'unknown']), + }), + temperature: 0, + prompt: ` + Classify the following feedback as ONE of: support, feedback, unknown. + - support: bug reports, help requests, or issues + - feedback: feature requests or suggestions + - unknown: unclear or unrelated + + If you can't determine support or feedback, always output "unknown". + + Only output a JSON object in this format: { "feedback_category": "support|feedback|unknown" } + + Examples: + Feedback: "Whenever I try to invite a team member, the invite email doesn't get sent." + Response: { "feedback_category": "support" } + + Feedback: "I have reached the storage limit for my project and my plan. I cannot understand how I can expand the storage space in my project." + Response: { "feedback_category": "support" } + + Feedback: "Please delete the project x in my account" + Response: { "feedback_category": "support" } + + Feedback: "My billing page is broken" + Response: { "feedback_category": "support" } + + Feedback: "I accidentally deleted my database—can it be recovered?" + Response: { "feedback_category": "support" } + + Feedback: "My login tokens are expiring too quickly, even though I didn't change any settings." + Response: { "feedback_category": "support" } + + Feedback: "Can you add more integrations?" + Response: { "feedback_category": "feedback" } + + Feedback: "I'm getting charged for a project I thought I deleted. Can you help me stop billing?" + Response: { "feedback_category": "support" } + + Feedback: "Could you support OAuth login for more providers like Apple or LinkedIn?" + Response: { "feedback_category": "feedback" } + + Feedback: "It's unclear in the docs how to set up row-level security with multiple roles." + Response: { "feedback_category": "feedback" } + + Feedback: "I am trying to pause my Pro project" + Response: { "feedback_category": "feedback" } + + Feedback: "${prompt}" + Response: + `, + }) + + return res.json({ feedback_category: object.feedback_category }) } catch (error) { if (error instanceof Error) { - console.error(`AI feedback classification failed: ${error.message}`) + console.error(`Classifying this feedback failed`) - if (error instanceof ContextLengthError) { + // Check for context length error + if (error.message.includes('context_length') || error.message.includes('too long')) { return res.status(400).json({ - error: - 'Your feedback prompt is too large for Supabase AI to ingest. Try splitting it into smaller prompts.', + error: 'This prompt is too large to ingest', }) } } else { @@ -47,7 +105,7 @@ export async function handlePost(req: NextApiRequest, res: NextApiResponse) { } return res.status(500).json({ - error: 'There was an unknown error classifying the feedback. Please try again.', + error: 'There was an unknown error generating the feedback category.', }) } } diff --git a/packages/ai-commands/edge.ts b/packages/ai-commands/edge.ts index 95023e6bb1d..729d55b5128 100644 --- a/packages/ai-commands/edge.ts +++ b/packages/ai-commands/edge.ts @@ -1,5 +1,4 @@ export * from './src/errors' export * from './src/docs' -export * from './src/sql/classify-feedback' export * from './src/sql/cron' diff --git a/packages/ai-commands/src/sql/classify-feedback.ts b/packages/ai-commands/src/sql/classify-feedback.ts deleted file mode 100644 index 68ecd255533..00000000000 --- a/packages/ai-commands/src/sql/classify-feedback.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { generateObject } from 'ai' -import { openai as openaiProvider } from '@ai-sdk/openai' -import { z } from 'zod' -import { ContextLengthError } from '../errors' - -// Given a prompt from the feedback widget, classify it as support, feedback, or unknown. -export async function classifyFeedback(prompt: string) { - try { - const { object } = await generateObject({ - model: openaiProvider('gpt-4o'), - temperature: 0, - - schema: z.object({ - feedback_category: z.enum(['support', 'feedback', 'unknown']), - }), - prompt: ` - Classify the following feedback as ONE of: support, feedback, unknown. - - support: bug reports, help requests, or issues - - feedback: feature requests or suggestions - - unknown: unclear or unrelated - - If you can't determine support or feedback, always output "unknown". - - Only output a JSON object in this format: { "feedback_category": "support|feedback|unknown" } - - Examples: - Feedback: "Whenever I try to invite a team member, the invite email doesn't get sent." - Response: { "feedback_category": "support" } - - Feedback: "I have reached the storage limit for my project and my plan. I cannot understand how I can expand the storage space in my project." - Response: { "feedback_category": "support" } - - Feedback: "Please delete the project x in my account" - Response: { "feedback_category": "support" } - - Feedback: "My billing page is broken" - Response: { "feedback_category": "support" } - - Feedback: "I accidentally deleted my database—can it be recovered?" - Response: { "feedback_category": "support" } - - Feedback: "My login tokens are expiring too quickly, even though I didn't change any settings." - Response: { "feedback_category": "support" } - - Feedback: "Can you add more integrations?" - Response: { "feedback_category": "feedback" } - - Feedback: "I'm getting charged for a project I thought I deleted. Can you help me stop billing?" - Response: { "feedback_category": "support" } - - Feedback: "Could you support OAuth login for more providers like Apple or LinkedIn?" - Response: { "feedback_category": "feedback" } - - Feedback: "It's unclear in the docs how to set up row-level security with multiple roles." - Response: { "feedback_category": "feedback" } - - Feedback: "I am trying to pause my Pro project" - Response: { "feedback_category": "feedback" } - - Feedback: "${prompt}" - Response: - `, - }) - - return object.feedback_category - } catch (error) { - if (error instanceof Error && 'code' in error && error.code === 'context_length_exceeded') { - throw new ContextLengthError() - } - throw error - } -}