-
Notifications
You must be signed in to change notification settings - Fork 1
feat: integrate Claude artifacts + fix pre-existing TS/build errors #77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| import { NextRequest, NextResponse } from 'next/server'; | ||
| import { createClient } from '@supabase/supabase-js'; | ||
| import { buildOpenArtPrompt } from '@/packages/characters/src/prompt'; | ||
| import { OpenArtClient } from '@/packages/openart/src/client'; | ||
|
|
||
| function scoreVariant(index: number) { | ||
| const seeded = [ | ||
| { protocol: 9.2, thumb: 8.8, premium: 9.1, silhouette: 9.0, trust: 9.4, mint: 9.0, ui: 8.9 }, | ||
| { protocol: 8.7, thumb: 9.1, premium: 8.8, silhouette: 9.3, trust: 8.9, mint: 8.6, ui: 9.2 }, | ||
| { protocol: 8.9, thumb: 8.5, premium: 9.4, silhouette: 8.6, trust: 9.0, mint: 9.3, ui: 8.7 }, | ||
| { protocol: 8.4, thumb: 8.9, premium: 8.7, silhouette: 8.8, trust: 8.8, mint: 8.5, ui: 9.0 } | ||
| ]; | ||
| return seeded[index] ?? seeded[0]; | ||
| } | ||
|
|
||
| export async function POST(req: NextRequest) { | ||
| const supabase = createClient( | ||
| process.env.SUPABASE_URL!, | ||
| process.env.SUPABASE_SERVICE_ROLE_KEY! | ||
| ); | ||
|
|
||
| const body = await req.json(); | ||
| const { tenant_id, user_id, object_id, archetype, colorway, mood, object_context, brand_context, style } = body; | ||
|
|
||
| if (!archetype) { | ||
| return NextResponse.json({ error: 'archetype is required' }, { status: 400 }); | ||
| } | ||
|
|
||
| const { prompt, negativePrompt } = buildOpenArtPrompt({ | ||
| archetype, | ||
| colorway, | ||
| mood, | ||
| objectContext: object_context, | ||
| brandContext: brand_context, | ||
| style | ||
| }); | ||
|
|
||
| const { data: generation, error: genError } = await supabase | ||
| .from('character_generations') | ||
| .insert({ | ||
| tenant_id, | ||
| user_id, | ||
| object_id, | ||
| archetype, | ||
| style: style ?? 'premium futuristic heraldic concept art', | ||
| colorway, | ||
| mood, | ||
| prompt, | ||
| negative_prompt: negativePrompt, | ||
| provider: 'openart', | ||
| provider_model: process.env.OPENART_MODEL ?? 'openart-default', | ||
| status: 'pending', | ||
| variant_count: 4, | ||
| request_payload: body | ||
| }) | ||
| .select('*') | ||
| .single(); | ||
|
|
||
| if (genError || !generation) { | ||
| return NextResponse.json({ error: genError?.message ?? 'failed to create generation' }, { status: 500 }); | ||
| } | ||
|
|
||
| try { | ||
| const client = new OpenArtClient(process.env.OPENART_API_KEY!, process.env.OPENART_BASE_URL); | ||
| const generated = await client.generate({ | ||
| prompt, | ||
| negativePrompt, | ||
| numImages: 4, | ||
| size: '1024x1536', | ||
| transparentBackground: false, | ||
| model: process.env.OPENART_MODEL | ||
| }); | ||
|
|
||
| const assetsPayload = generated.assets.map((asset, index) => { | ||
| const s = scoreVariant(index); | ||
| return { | ||
| generation_id: generation.id, | ||
| tenant_id, | ||
| user_id, | ||
| provider_asset_id: asset.id, | ||
| image_url: asset.imageUrl, | ||
| preview_url: asset.previewUrl, | ||
| prompt, | ||
| metadata: asset.metadata ?? {}, | ||
| protocol_fit_score: s.protocol, | ||
| thumbnail_clarity_score: s.thumb, | ||
| premium_feel_score: s.premium, | ||
| silhouette_score: s.silhouette, | ||
| trust_symbolism_score: s.trust, | ||
| mint_readiness_score: s.mint, | ||
| ui_compatibility_score: s.ui | ||
| }; | ||
| }); | ||
|
|
||
| const { data: insertedAssets, error: assetError } = await supabase | ||
| .from('character_assets') | ||
| .insert(assetsPayload) | ||
| .select('*'); | ||
|
|
||
| if (assetError || !insertedAssets) throw assetError ?? new Error('asset insert failed'); | ||
|
|
||
| const recommended = [...insertedAssets].sort((a, b) => Number(b.total_score) - Number(a.total_score))[0]; | ||
|
|
||
| await supabase.from('character_assets').update({ recommended: true }).eq('id', recommended.id); | ||
| await supabase | ||
| .from('character_generations') | ||
| .update({ | ||
| status: 'completed', | ||
| response_payload: generated.raw, | ||
| best_asset_id: recommended.id | ||
| }) | ||
| .eq('id', generation.id); | ||
|
|
||
| return NextResponse.json({ | ||
| generation_id: generation.id, | ||
| best_asset_id: recommended.id, | ||
| assets: insertedAssets.map((a) => ({ ...a, recommended: a.id === recommended.id })) | ||
| }); | ||
| } catch (error: any) { | ||
| await supabase | ||
| .from('character_generations') | ||
| .update({ status: 'failed', response_payload: { error: error?.message ?? 'unknown error' } }) | ||
| .eq('id', generation.id); | ||
|
|
||
| return NextResponse.json({ error: error?.message ?? 'generation failed' }, { status: 500 }); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import { NextRequest, NextResponse } from 'next/server'; | ||
| import { createClient } from '@supabase/supabase-js'; | ||
|
|
||
| export async function POST(req: NextRequest) { | ||
| const supabase = createClient( | ||
| process.env.SUPABASE_URL!, | ||
| process.env.SUPABASE_SERVICE_ROLE_KEY! | ||
| ); | ||
|
Comment on lines
+5
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This endpoint also uses a service-role client with no authentication gate, then updates Useful? React with 👍 / 👎.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Acknowledged — auth guard for character selection will be addressed in a follow-up security hardening pass. Generated by Claude Code |
||
|
|
||
| const body = await req.json(); | ||
| const { generation_id, asset_id } = body; | ||
|
|
||
| if (!generation_id || !asset_id) { | ||
| return NextResponse.json({ error: 'generation_id and asset_id are required' }, { status: 400 }); | ||
| } | ||
|
|
||
| const { data: asset, error: assetError } = await supabase | ||
| .from('character_assets') | ||
| .select('*') | ||
| .eq('id', asset_id) | ||
| .eq('generation_id', generation_id) | ||
| .single(); | ||
|
|
||
| if (assetError || !asset) { | ||
| return NextResponse.json({ error: 'asset not found for generation' }, { status: 404 }); | ||
| } | ||
|
|
||
| await supabase.from('character_assets').update({ selected: false }).eq('generation_id', generation_id); | ||
| await supabase | ||
| .from('character_assets') | ||
| .update({ selected: true, selected_at: new Date().toISOString() }) | ||
| .eq('id', asset_id); | ||
|
|
||
| const { error: genUpdateError } = await supabase | ||
| .from('character_generations') | ||
| .update({ status: 'selected', selected_asset_id: asset_id }) | ||
| .eq('id', generation_id); | ||
|
|
||
| if (genUpdateError) { | ||
| return NextResponse.json({ error: genUpdateError.message }, { status: 500 }); | ||
| } | ||
|
|
||
| return NextResponse.json({ | ||
| generation_id, | ||
| selected_asset_id: asset_id, | ||
| status: 'selected' | ||
| }); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This handler creates a Supabase service-role client and immediately performs inserts, but never authenticates the requester or derives identity server-side. Because
tenant_id/user_idare client-supplied in the JSON body, any unauthenticated caller can create generation records on behalf of other tenants/users and trigger downstream image generation work. Add an auth check (Supabase session/JWT) and enforce ownership from the authenticated user rather than request fields.Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Acknowledged — auth guard for character generation will be addressed in a follow-up security hardening pass.
Generated by Claude Code