mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
Nudge feedback widget, move request to bedrock (#38074)
* Nudge feedback widget, move request to bedrock * Fix the response object * Remove an import to deleted file. --------- Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
This commit is contained in:
1 parent
750028b542
commit
f9921569a9
4 files changed
+83
-96
No files matched your search
+6
-4
@@ -44,18 +44,20 @@ const FeedbackDropdown = ({ className }: { className?: string }) => {
|
||||
<div className="flex flex-col gap-4 p-4">
|
||||
<div className="font-medium text-sm">What would you like to share?</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<Button type="default" className="h-24" onClick={() => setIsOpen(false)} asChild>
|
||||
<Button type="default" className="h-32" onClick={() => setIsOpen(false)} asChild>
|
||||
<Link href="/support/new">
|
||||
<span className="grid gap-1 text-center">
|
||||
<TriangleAlert size="28" className="mx-auto text-destructive-600" />
|
||||
<span className="text-sm">Issue</span>
|
||||
<span className="text-base">Issue</span>
|
||||
<span className="text-xm text-foreground-lighter">with my project</span>
|
||||
</span>
|
||||
</Link>
|
||||
</Button>
|
||||
<Button type="default" className="h-24" onClick={() => setStage('widget')}>
|
||||
<Button type="default" className="h-32" onClick={() => setStage('widget')}>
|
||||
<span className="grid gap-1 text-center">
|
||||
<Lightbulb size="28" className="mx-auto text-warning" />
|
||||
<span className="text-sm">Idea</span>
|
||||
<span className="text-base">Idea</span>
|
||||
<span className="text-xm text-foreground-lighter">to improve Supabase</span>
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -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.',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from './src/errors'
|
||||
|
||||
export * from './src/docs'
|
||||
export * from './src/sql/classify-feedback'
|
||||
export * from './src/sql/cron'
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user