fix some api

This commit is contained in:
songtianlun 2025-08-31 01:31:15 +08:00
parent 570b93b74c
commit 13101edc6c
7 changed files with 243 additions and 41 deletions

View File

@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import { createServerSupabaseClient } from "@/lib/supabase-server";
import { auth } from '@/lib/auth';
import { headers } from 'next/headers';
import { prisma } from "@/lib/prisma";
export async function POST(
@ -8,12 +9,15 @@ export async function POST(
) {
try {
const { id } = await params;
const supabase = await createServerSupabaseClient();
const { data: { user }, error: authError } = await supabase.auth.getUser();
const session = await auth.api.getSession({
headers: await headers()
});
if (authError || !user) {
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const user = session.user;
// 获取原始运行记录
const originalRun = await prisma.simulatorRun.findFirst({

View File

@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import { createServerSupabaseClient } from "@/lib/supabase-server";
import { auth } from '@/lib/auth';
import { headers } from 'next/headers';
import { prisma } from "@/lib/prisma";
export async function GET(
@ -8,12 +9,15 @@ export async function GET(
) {
try {
const { id } = await params;
const supabase = await createServerSupabaseClient();
const { data: { user }, error: authError } = await supabase.auth.getUser();
const session = await auth.api.getSession({
headers: await headers()
});
if (authError || !user) {
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const user = session.user;
const run = await prisma.simulatorRun.findFirst({
where: {
@ -75,12 +79,15 @@ export async function PATCH(
) {
try {
const { id } = await params;
const supabase = await createServerSupabaseClient();
const { data: { user }, error: authError } = await supabase.auth.getUser();
const session = await auth.api.getSession({
headers: await headers()
});
if (authError || !user) {
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const user = session.user;
const body = await request.json();
const { status, output, error, inputTokens, outputTokens, totalCost, duration } = body;
@ -144,12 +151,15 @@ export async function PUT(
) {
try {
const { id } = await params;
const supabase = await createServerSupabaseClient();
const { data: { user }, error: authError } = await supabase.auth.getUser();
const session = await auth.api.getSession({
headers: await headers()
});
if (authError || !user) {
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const user = session.user;
const body = await request.json();
const {

View File

@ -1,17 +1,21 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { createServerSupabaseClient } from '@/lib/supabase-server'
import { auth } from '@/lib/auth'
import { headers } from 'next/headers'
// GET /api/simulator/prompts - 获取用户的提示词列表,包含所有版本信息用于模拟器
export async function GET(request: NextRequest) {
try {
const supabase = await createServerSupabaseClient()
const { data: { user } } = await supabase.auth.getUser()
const session = await auth.api.getSession({
headers: await headers()
})
if (!user) {
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const user = session.user
const { searchParams } = new URL(request.url)
const limit = parseInt(searchParams.get('limit') || '100')

View File

@ -1,15 +1,66 @@
import { NextRequest, NextResponse } from "next/server";
import { createServerSupabaseClient } from "@/lib/supabase-server";
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { prisma } from "@/lib/prisma";
// 模型适配器类型
type ModelAdapter = {
id: string;
name: string;
prepareRequest: (userInput: string, promptContent: string, params: Record<string, unknown>) => Record<string, unknown>;
parseResponse: (response: Record<string, unknown>) => { content: string; outputType?: string };
};
// 图像生成模型适配器
const IMAGE_MODEL_ADAPTERS: Record<string, ModelAdapter> = {
'gpt-image-1': {
id: 'gpt-image-1',
name: 'GPT Image 1',
prepareRequest: (userInput: string, promptContent: string, params: Record<string, unknown>) => ({
model: 'gpt-image-1',
prompt: `${promptContent}\n\nUser input: ${userInput}`,
size: '1024x1024',
quality: 'standard',
...params
}),
parseResponse: (response: Record<string, unknown>) => ({
content: (response as { data?: { url: string }[]; url?: string }).data?.[0]?.url || (response as { url?: string }).url || 'Image generated successfully',
outputType: 'image'
})
},
'google/gemini-2.5-flash-image-preview': {
id: 'google/gemini-2.5-flash-image-preview',
name: 'Gemini 2.5 Flash Image Preview',
prepareRequest: (userInput: string, promptContent: string, params: Record<string, unknown>) => ({
model: 'google/gemini-2.5-flash-image-preview',
contents: [{
parts: [{
text: `${promptContent}\n\nUser input: ${userInput}`
}]
}],
generationConfig: {
temperature: params.temperature || 0.7,
maxOutputTokens: params.maxTokens || 1024
}
}),
parseResponse: (response: Record<string, unknown>) => ({
content: (response as { candidates?: Array<{ content?: { parts?: Array<{ text?: string }> } }>; generated_image_url?: string }).candidates?.[0]?.content?.parts?.[0]?.text || (response as { generated_image_url?: string }).generated_image_url || 'Image generated successfully',
outputType: 'image'
})
}
};
export async function GET(request: NextRequest) {
try {
const supabase = await createServerSupabaseClient();
const { data: { user }, error: authError } = await supabase.auth.getUser();
const session = await auth.api.getSession({
headers: await headers()
});
if (authError || !user) {
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const user = session.user;
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get("page") || "1");
@ -73,12 +124,15 @@ export async function GET(request: NextRequest) {
export async function POST(request: NextRequest) {
try {
const supabase = await createServerSupabaseClient();
const { data: { user }, error: authError } = await supabase.auth.getUser();
const session = await auth.api.getSession({
headers: await headers()
});
if (authError || !user) {
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const user = session.user;
const body = await request.json();
const {
@ -93,6 +147,7 @@ export async function POST(request: NextRequest) {
topP,
frequencyPenalty,
presencePenalty,
generationMode = 'text', // 新增生成模式字段
// 用于创建新prompt的字段
createNewPrompt,
newPromptName,
@ -139,6 +194,24 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: "Model not available" }, { status: 400 });
}
// 验证生成模式与模型的兼容性
if (generationMode === 'text' && model.outputType !== 'text') {
return NextResponse.json({ error: "Selected model is not compatible with text generation mode" }, { status: 400 });
}
if (generationMode === 'image') {
if (model.outputType !== 'image') {
return NextResponse.json({ error: "Selected model is not compatible with image generation mode" }, { status: 400 });
}
// 检查是否有对应的适配器
if (!IMAGE_MODEL_ADAPTERS[model.modelId]) {
return NextResponse.json({
error: `Image model ${model.modelId} is not supported yet. Supported models: ${Object.keys(IMAGE_MODEL_ADAPTERS).join(', ')}`
}, { status: 400 });
}
}
// 创建运行记录
const run = await prisma.simulatorRun.create({
data: {

View File

@ -2,7 +2,7 @@
import { useState, useEffect, useRef, useCallback } from 'react'
import { useTranslations } from 'next-intl'
import { useAuthUser } from '@/hooks/useAuthUser'
import { useBetterAuth } from '@/hooks/useBetterAuth'
import { useRouter } from 'next/navigation'
import { Header } from '@/components/layout/Header'
import { Footer } from '@/components/layout/Footer'
@ -85,7 +85,7 @@ interface Model {
}
export default function SimulatorRunPage({ params }: { params: Promise<{ id: string }> }) {
const { user, loading: authLoading } = useAuthUser()
const { user, loading: authLoading } = useBetterAuth()
const router = useRouter()
const t = useTranslations('simulator')
const locale = useLocale()

View File

@ -2,7 +2,7 @@
import { useState, useEffect, useCallback } from 'react'
import { useTranslations } from 'next-intl'
import { useAuthUser } from '@/hooks/useAuthUser'
import { useBetterAuth } from '@/hooks/useBetterAuth'
import { useRouter } from 'next/navigation'
import { Header } from '@/components/layout/Header'
import { Footer } from '@/components/layout/Footer'
@ -36,11 +36,30 @@ interface Prompt {
}>
}
// 生成模式枚举
type GenerationMode = 'text' | 'image'
// 支持的图像生成模型ID映射
const SUPPORTED_IMAGE_MODELS = {
'gpt-image-1': {
id: 'gpt-image-1',
name: 'GPT Image 1',
adapter: 'gpt-image-1'
},
'google/gemini-2.5-flash-image-preview': {
id: 'google/gemini-2.5-flash-image-preview',
name: 'Gemini 2.5 Flash Image Preview',
adapter: 'gemini-image'
}
} as const
interface Model {
id: string
modelId: string
name: string
provider: string
serviceProvider: string
outputType: string
description?: string
maxTokens?: number
inputCostPer1k?: number
@ -49,7 +68,7 @@ interface Model {
}
export default function NewSimulatorRunPage() {
const { user, loading: authLoading } = useAuthUser()
const { user, loading: authLoading } = useBetterAuth()
const router = useRouter()
const t = useTranslations('simulator')
@ -69,6 +88,10 @@ export default function NewSimulatorRunPage() {
const [simulatorName, setSimulatorName] = useState('')
const [promptInputMode, setPromptInputMode] = useState<'select' | 'create'>('select') // 选择模式:选择现有提示词 或 创建新提示词
// 生成模式相关状态
const [generationMode, setGenerationMode] = useState<GenerationMode>('text')
const [filteredModels, setFilteredModels] = useState<Model[]>([])
// Advanced settings
const [temperature, setTemperature] = useState('0.7')
const [maxTokens, setMaxTokens] = useState('')
@ -76,6 +99,34 @@ export default function NewSimulatorRunPage() {
const [frequencyPenalty, setFrequencyPenalty] = useState('0')
const [presencePenalty, setPresencePenalty] = useState('0')
// 模型过滤逻辑
const filterModelsByMode = useCallback((allModels: Model[], mode: GenerationMode) => {
if (mode === 'text') {
// 文本模式显示所有outputType为text的模型
return allModels.filter(model => model.outputType === 'text')
} else if (mode === 'image') {
// 图像模式:只显示已适配的图像生成模型
return allModels.filter(model =>
model.outputType === 'image' &&
Object.keys(SUPPORTED_IMAGE_MODELS).includes(model.modelId)
)
}
return []
}, [])
// 当生成模式改变时更新模型列表
useEffect(() => {
const filtered = filterModelsByMode(models, generationMode)
setFilteredModels(filtered)
// 重置已选择的模型,选择第一个可用模型
if (filtered.length > 0) {
setSelectedModelId(filtered[0].id)
} else {
setSelectedModelId('')
}
}, [models, generationMode, filterModelsByMode])
const fetchData = useCallback(async () => {
if (!user) return
@ -95,10 +146,7 @@ export default function NewSimulatorRunPage() {
if (modelsResponse.ok) {
const modelsData = await modelsResponse.json()
setModels(modelsData.models || [])
// Auto-select first model
if (modelsData.models?.length > 0) {
setSelectedModelId(modelsData.models[0].id)
}
// 模型选择逻辑现在由useEffect处理
}
} catch (error) {
console.error('Error fetching data:', error)
@ -222,6 +270,7 @@ export default function NewSimulatorRunPage() {
topP: number;
frequencyPenalty: number;
presencePenalty: number;
generationMode: GenerationMode;
createNewPrompt?: boolean;
newPromptName?: string;
newPromptContent?: string;
@ -237,6 +286,7 @@ export default function NewSimulatorRunPage() {
topP: parseFloat(topP),
frequencyPenalty: parseFloat(frequencyPenalty),
presencePenalty: parseFloat(presencePenalty),
generationMode,
}
if (promptInputMode === 'create') {
@ -315,6 +365,52 @@ export default function NewSimulatorRunPage() {
</div>
) : (
<form onSubmit={handleSubmit} className="space-y-6">
{/* Generation Mode Selection */}
<Card className="p-6">
<div className="mb-6">
<h2 className="text-xl font-semibold mb-2 flex items-center">
<Settings className="h-5 w-5 mr-2" />
</h2>
<p className="text-muted-foreground mb-4">
</p>
<div className="flex space-x-1 p-1 bg-muted rounded-lg max-w-md">
<button
type="button"
className={`flex-1 px-4 py-2 text-sm font-medium rounded-md transition-colors ${
generationMode === 'text'
? 'bg-background text-foreground shadow-sm'
: 'text-muted-foreground hover:text-foreground'
}`}
onClick={() => setGenerationMode('text')}
>
📝
</button>
<button
type="button"
className={`flex-1 px-4 py-2 text-sm font-medium rounded-md transition-colors ${
generationMode === 'image'
? 'bg-background text-foreground shadow-sm'
: 'text-muted-foreground hover:text-foreground'
}`}
onClick={() => setGenerationMode('image')}
>
🎨
</button>
</div>
{generationMode === 'image' && filteredModels.length === 0 && (
<div className="mt-4 p-3 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-md">
<p className="text-sm text-yellow-800 dark:text-yellow-200">
{Object.values(SUPPORTED_IMAGE_MODELS).map(m => m.name).join(', ')}
</p>
</div>
)}
</div>
</Card>
{/* Prompt Configuration */}
<Card className="p-6">
<div className="flex items-center justify-between mb-6">
@ -511,13 +607,16 @@ export default function NewSimulatorRunPage() {
<div>
<h2 className="text-xl font-semibold mb-2">{t('selectModel')}</h2>
<p className="text-muted-foreground">
Choose the AI model to run your prompt
{generationMode === 'text'
? '选择用于文本生成的AI模型'
: '选择用于图像生成的AI模型仅显示已适配的模型'
}
</p>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{models.map(model => (
{filteredModels.map(model => (
<div
key={model.id}
className={`border rounded-lg p-4 cursor-pointer transition-all ${
@ -529,9 +628,14 @@ export default function NewSimulatorRunPage() {
>
<div className="flex items-center justify-between mb-2">
<h3 className="font-semibold text-foreground">{model.name}</h3>
<Badge variant="outline" className="text-xs">
{model.provider}
</Badge>
<div className="flex items-center space-x-2">
<Badge variant="outline" className="text-xs">
{model.outputType === 'text' ? '📝' : '🎨'} {model.outputType}
</Badge>
<Badge variant="outline" className="text-xs">
{model.provider}
</Badge>
</div>
</div>
{model.description && (
<p className="text-sm text-muted-foreground mb-3 line-clamp-2">

View File

@ -132,6 +132,8 @@ export function useBetterAuth() {
...globalAuthState,
user,
loading: false,
// 保留现有的 isAdmin 状态,除非用户登出
isAdmin: user ? globalAuthState.isAdmin : false,
}
notifyStateChange(newState)
@ -232,9 +234,14 @@ export function useBetterAuth() {
// 立即更新用户状态(不延迟)
updateUserState(user)
// 异步进行数据同步(防抖延迟)
// 异步进行数据同步
if (user) {
debouncedUserDataSync(user, trigger)
// 初始加载时立即同步,其他情况使用防抖
if (trigger === SyncTrigger.INITIAL_LOAD) {
globalHandleUserDataSync(user, trigger)
} else {
debouncedUserDataSync(user, trigger)
}
}
if (!isGlobalInitialized) {
@ -249,7 +256,7 @@ export function useBetterAuth() {
return () => {
mounted = false
}
}, [session, isPending, updateUserState, debouncedUserDataSync])
}, [session, isPending, updateUserState, debouncedUserDataSync, globalHandleUserDataSync])
// 设置loading状态
useEffect(() => {