fix some api
This commit is contained in:
parent
570b93b74c
commit
13101edc6c
@ -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({
|
||||
|
@ -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 {
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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: {
|
||||
|
@ -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()
|
||||
|
@ -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">
|
||||
|
@ -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(() => {
|
||||
|
Loading…
Reference in New Issue
Block a user