feat: add save feedback prompts, featured agent fields, dynamic Dify API config, and chat improvements
- Add success/error feedback messages near submit buttons in all admin forms - Display success prompt for 1s before redirect after save - Show API error details on save failure - Add isFeatured/featuredOrder fields to Agent model and admin UI - Add difyApiUrl/difyApiKey fields for per-agent Dify API configuration - Show featured badge column in agent admin list - Display featured agents on homepage sorted by order - Refactor chat page streaming with AbortController and stable userId - Improve Dify API proxy to use per-agent credentials
This commit is contained in:
@@ -18,7 +18,11 @@ export default function AgentForm({
|
||||
features?: string
|
||||
hotQuestions?: string
|
||||
quickQuestions?: string
|
||||
difyApiUrl?: string
|
||||
difyApiKey?: string
|
||||
status?: string
|
||||
isFeatured?: boolean
|
||||
featuredOrder?: number
|
||||
}
|
||||
}) {
|
||||
const router = useRouter()
|
||||
@@ -31,10 +35,15 @@ export default function AgentForm({
|
||||
features: agent?.features || "",
|
||||
hotQuestions: agent?.hotQuestions ? JSON.parse(agent.hotQuestions).join('\n') : '',
|
||||
quickQuestions: agent?.quickQuestions ? JSON.parse(agent.quickQuestions).join('\n') : '',
|
||||
difyApiUrl: agent?.difyApiUrl || "",
|
||||
difyApiKey: agent?.difyApiKey || "",
|
||||
status: agent?.status || "active",
|
||||
isFeatured: agent?.isFeatured ?? false,
|
||||
featuredOrder: agent?.featuredOrder ?? 0,
|
||||
})
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState("")
|
||||
const [success, setSuccess] = useState(false)
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
@@ -50,6 +59,8 @@ export default function AgentForm({
|
||||
|
||||
const body = {
|
||||
...formData,
|
||||
isFeatured: formData.isFeatured,
|
||||
featuredOrder: formData.featuredOrder,
|
||||
hotQuestions: JSON.stringify(formData.hotQuestions.split('\n').filter(q => q.trim())),
|
||||
quickQuestions: JSON.stringify(formData.quickQuestions.split('\n').filter(q => q.trim())),
|
||||
}
|
||||
@@ -61,9 +72,12 @@ export default function AgentForm({
|
||||
})
|
||||
|
||||
if (res.ok) {
|
||||
setSuccess(true)
|
||||
await new Promise(r => setTimeout(r, 1000))
|
||||
router.push("/admin/agents")
|
||||
} else {
|
||||
setError("保存失败")
|
||||
const data = await res.json().catch(() => ({}))
|
||||
setError(data.error || "保存失败")
|
||||
}
|
||||
} catch (err) {
|
||||
setError("保存失败,请稍后重试")
|
||||
@@ -185,6 +199,34 @@ export default function AgentForm({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-orange-50 border border-orange-200 rounded-xl p-4">
|
||||
<h3 className="text-sm font-semibold text-orange-800 mb-3">热门智能体配置</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<label className="flex items-center gap-3 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.isFeatured}
|
||||
onChange={(e) => setFormData({ ...formData, isFeatured: e.target.checked })}
|
||||
className="w-5 h-5 text-orange-600 border-gray-300 rounded focus:ring-orange-500"
|
||||
/>
|
||||
<span className="text-sm font-medium text-gray-700">设为热门智能体(展示在首页热门区域)</span>
|
||||
</label>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
排序(数字越小越靠前)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min={0}
|
||||
value={formData.featuredOrder}
|
||||
onChange={(e) => setFormData({ ...formData, featuredOrder: parseInt(e.target.value) || 0 })}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
@@ -212,10 +254,49 @@ export default function AgentForm({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Dify API 地址
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.difyApiUrl}
|
||||
onChange={(e) => setFormData({ ...formData, difyApiUrl: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="https://api.dify.ai/v1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Dify API Key
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
value={formData.difyApiKey}
|
||||
onChange={(e) => setFormData({ ...formData, difyApiKey: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="app-xxxxxxxxxxxx"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{success && (
|
||||
<div className="bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-lg text-sm">
|
||||
保存成功
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
disabled={loading || success}
|
||||
className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 disabled:bg-blue-400"
|
||||
>
|
||||
{loading ? "保存中..." : "保存"}
|
||||
|
||||
@@ -51,7 +51,11 @@ export default async function EditAgentPage({
|
||||
features: agent.features,
|
||||
hotQuestions: agent.hotQuestions,
|
||||
quickQuestions: agent.quickQuestions,
|
||||
difyApiUrl: agent.difyApiUrl || "",
|
||||
difyApiKey: agent.difyApiKey || "",
|
||||
status: agent.status,
|
||||
isFeatured: agent.isFeatured,
|
||||
featuredOrder: agent.featuredOrder,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -39,6 +39,7 @@ export default async function AdminAgentsPage() {
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">名称</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">分类</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">热门</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">状态</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">使用次数</th>
|
||||
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase">操作</th>
|
||||
@@ -59,6 +60,15 @@ export default async function AdminAgentsPage() {
|
||||
<td className="px-6 py-4 text-sm text-gray-600">
|
||||
{agent.category?.name || "-"}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
{agent.isFeatured ? (
|
||||
<span className="inline-flex px-2 py-1 text-xs font-medium rounded-full bg-orange-100 text-orange-700">
|
||||
热门
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-sm text-gray-400">-</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<span className={`inline-flex px-2 py-1 text-xs font-medium rounded-full ${
|
||||
agent.status === "active"
|
||||
|
||||
@@ -21,6 +21,7 @@ export default function CategoryForm({
|
||||
})
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState("")
|
||||
const [success, setSuccess] = useState(false)
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
@@ -41,9 +42,12 @@ export default function CategoryForm({
|
||||
})
|
||||
|
||||
if (res.ok) {
|
||||
setSuccess(true)
|
||||
await new Promise(r => setTimeout(r, 1000))
|
||||
router.push("/admin/categories")
|
||||
} else {
|
||||
setError("保存失败")
|
||||
const data = await res.json().catch(() => ({}))
|
||||
setError(data.error || "保存失败")
|
||||
}
|
||||
} catch (err) {
|
||||
setError("保存失败,请稍后重试")
|
||||
@@ -100,10 +104,22 @@ export default function CategoryForm({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{success && (
|
||||
<div className="bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-lg text-sm">
|
||||
保存成功
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
disabled={loading || success}
|
||||
className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 disabled:bg-blue-400"
|
||||
>
|
||||
{loading ? "保存中..." : "保存"}
|
||||
|
||||
@@ -23,6 +23,7 @@ export default function NewsForm({
|
||||
})
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState("")
|
||||
const [success, setSuccess] = useState(false)
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
@@ -43,9 +44,12 @@ export default function NewsForm({
|
||||
})
|
||||
|
||||
if (res.ok) {
|
||||
setSuccess(true)
|
||||
await new Promise(r => setTimeout(r, 1000))
|
||||
router.push("/admin/news")
|
||||
} else {
|
||||
setError("保存失败")
|
||||
const data = await res.json().catch(() => ({}))
|
||||
setError(data.error || "保存失败")
|
||||
}
|
||||
} catch (err) {
|
||||
setError("保存失败,请稍后重试")
|
||||
@@ -121,10 +125,22 @@ export default function NewsForm({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{success && (
|
||||
<div className="bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-lg text-sm">
|
||||
保存成功
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
disabled={loading || success}
|
||||
className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 disabled:bg-blue-400"
|
||||
>
|
||||
{loading ? "保存中..." : "保存"}
|
||||
|
||||
@@ -48,8 +48,10 @@ export default function AgentChatPage() {
|
||||
const [currentConversationId, setCurrentConversationId] = useState<number | null>(null)
|
||||
const [upstreamConversationId, setUpstreamConversationId] = useState<string>('')
|
||||
const [loadingConversations, setLoadingConversations] = useState(false)
|
||||
const [abortController, setAbortController] = useState<AbortController | null>(null)
|
||||
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null)
|
||||
const userIdRef = useRef<string>('user-' + Math.random().toString(36).slice(2, 10))
|
||||
|
||||
const CHAT_API_URL = '/api/chat'
|
||||
|
||||
@@ -190,8 +192,6 @@ export default function AgentChatPage() {
|
||||
})
|
||||
|
||||
const assistantMessageId = (Date.now() + 1).toString()
|
||||
let assistantContent = ''
|
||||
let upstreamConvId = upstreamConversationId
|
||||
|
||||
setMessages(prev => [...prev, {
|
||||
id: assistantMessageId,
|
||||
@@ -200,70 +200,70 @@ export default function AgentChatPage() {
|
||||
timestamp: new Date(),
|
||||
}])
|
||||
|
||||
const controller = new AbortController()
|
||||
setAbortController(controller)
|
||||
|
||||
const response = await fetch(CHAT_API_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
agentId: agent.id,
|
||||
inputs: {},
|
||||
query: currentInput,
|
||||
response_mode: 'streaming',
|
||||
conversation_id: upstreamConversationId,
|
||||
user: 'user-' + Date.now(),
|
||||
user: userIdRef.current,
|
||||
}),
|
||||
signal: controller.signal,
|
||||
})
|
||||
|
||||
const reader = response.body?.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
if (!response.ok) {
|
||||
throw new Error('Chat request failed')
|
||||
}
|
||||
|
||||
if (reader) {
|
||||
const reader = response.body!.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
let buffer = ''
|
||||
let accumulatedContent = ''
|
||||
let finalConvId = upstreamConversationId
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
|
||||
buffer += decoder.decode(value, { stream: true })
|
||||
const lines = buffer.split('\n')
|
||||
buffer = lines.pop() || ''
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
try {
|
||||
const json = JSON.parse(line.slice(6))
|
||||
if (json.conversation_id) {
|
||||
upstreamConvId = json.conversation_id
|
||||
}
|
||||
const token = json.answer || json.message || ''
|
||||
if (token) {
|
||||
assistantContent += token
|
||||
const data = JSON.parse(line.slice(6))
|
||||
if (data.event === 'message' && data.answer) {
|
||||
accumulatedContent += data.answer
|
||||
setMessages(prev => prev.map(msg =>
|
||||
msg.id === assistantMessageId
|
||||
? { ...msg, content: assistantContent }
|
||||
? { ...msg, content: accumulatedContent }
|
||||
: msg
|
||||
))
|
||||
} else if (data.event === 'message_end') {
|
||||
finalConvId = data.conversation_id || finalConvId
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore parse errors
|
||||
}
|
||||
// skip invalid JSON
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setUpstreamConversationId(upstreamConvId)
|
||||
|
||||
if (!assistantContent) {
|
||||
setMessages(prev => prev.map(msg =>
|
||||
msg.id === assistantMessageId
|
||||
? { ...msg, content: '抱歉,我暂时无法回答这个问题。' }
|
||||
: msg
|
||||
))
|
||||
assistantContent = '抱歉,我暂时无法回答这个问题。'
|
||||
}
|
||||
setUpstreamConversationId(finalConvId)
|
||||
|
||||
await fetch(`/api/conversations/${conversationId}/messages`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
role: 'assistant',
|
||||
content: assistantContent,
|
||||
content: accumulatedContent,
|
||||
}),
|
||||
})
|
||||
} catch (error) {
|
||||
|
||||
@@ -17,10 +17,14 @@ export async function PUT(
|
||||
description: data.description,
|
||||
icon: data.icon || null,
|
||||
categoryId: data.categoryId ? parseInt(data.categoryId) : null,
|
||||
difyApiUrl: data.difyApiUrl || null,
|
||||
difyApiKey: data.difyApiKey || null,
|
||||
features: data.features || "",
|
||||
hotQuestions: data.hotQuestions || "[]",
|
||||
quickQuestions: data.quickQuestions || "[]",
|
||||
status: data.status || "active",
|
||||
isFeatured: data.isFeatured ?? false,
|
||||
featuredOrder: data.featuredOrder ?? 0,
|
||||
},
|
||||
})
|
||||
return NextResponse.json(agent)
|
||||
|
||||
@@ -12,10 +12,14 @@ export async function POST(request: NextRequest) {
|
||||
description: data.description,
|
||||
icon: data.icon || null,
|
||||
categoryId: data.categoryId ? parseInt(data.categoryId) : null,
|
||||
difyApiUrl: data.difyApiUrl || null,
|
||||
difyApiKey: data.difyApiKey || null,
|
||||
features: data.features || "",
|
||||
hotQuestions: data.hotQuestions || "[]",
|
||||
quickQuestions: data.quickQuestions || "[]",
|
||||
status: data.status || "active",
|
||||
isFeatured: data.isFeatured ?? false,
|
||||
featuredOrder: data.featuredOrder ?? 0,
|
||||
},
|
||||
})
|
||||
return NextResponse.json(agent)
|
||||
|
||||
+37
-19
@@ -1,33 +1,51 @@
|
||||
import { NextResponse } from "next/server"
|
||||
import { prisma } from "@/app/lib/prisma"
|
||||
|
||||
const API_KEY = 'app-lbe2lglt7taGtZk0dG7pAhbx'
|
||||
const API_URL = 'http://df.clkeji.com/v1/chat-messages'
|
||||
const FALLBACK_API_KEY = 'app-lbe2lglt7taGtZk0dG7pAhbx'
|
||||
const FALLBACK_API_URL = 'http://df.clkeji.com/v1/chat-messages'
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { agentId, ...difyBody } = body
|
||||
|
||||
const response = await fetch(API_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
let apiKey = FALLBACK_API_KEY
|
||||
let apiUrl = FALLBACK_API_URL
|
||||
|
||||
if (body.response_mode === 'streaming') {
|
||||
return new NextResponse(response.body, {
|
||||
headers: {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive',
|
||||
},
|
||||
if (agentId) {
|
||||
const agent = await prisma.agent.findUnique({
|
||||
where: { id: parseInt(agentId) },
|
||||
})
|
||||
if (agent?.difyApiUrl && agent?.difyApiKey) {
|
||||
apiUrl = agent.difyApiUrl.replace(/\/+$/, '') + '/chat-messages'
|
||||
apiKey = agent.difyApiKey
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
return NextResponse.json(data)
|
||||
const response = await fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ ...difyBody, response_mode: 'streaming' }),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text()
|
||||
console.error('Dify API error:', response.status, text.slice(0, 500))
|
||||
return NextResponse.json(
|
||||
{ error: 'Dify request failed' },
|
||||
{ status: 502 }
|
||||
)
|
||||
}
|
||||
|
||||
const headers = new Headers()
|
||||
headers.set('Content-Type', 'text/event-stream')
|
||||
headers.set('Cache-Control', 'no-cache')
|
||||
headers.set('Connection', 'keep-alive')
|
||||
|
||||
return new Response(response.body, { headers })
|
||||
} catch (error) {
|
||||
console.error('Chat API error:', error)
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -4,7 +4,9 @@ import Image from "next/image"
|
||||
|
||||
export default async function HomePage() {
|
||||
const agents = await prisma.agent.findMany({
|
||||
where: { isFeatured: true },
|
||||
include: { category: true },
|
||||
orderBy: { featuredOrder: "asc" },
|
||||
take: 6,
|
||||
})
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
"react-markdown": "^10.1.0",
|
||||
"remark-gfm": "^4.0.1"
|
||||
},
|
||||
"prisma": {
|
||||
"seed": "tsx prisma/seed.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.0.0",
|
||||
"@types/node": "^22.13.1",
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,29 @@
|
||||
-- RedefineTables
|
||||
PRAGMA defer_foreign_keys=ON;
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Agent" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"name" TEXT NOT NULL,
|
||||
"slug" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"icon" TEXT,
|
||||
"categoryId" INTEGER,
|
||||
"features" TEXT NOT NULL DEFAULT '',
|
||||
"hotQuestions" TEXT NOT NULL DEFAULT '[]',
|
||||
"quickQuestions" TEXT NOT NULL DEFAULT '[]',
|
||||
"difyApiUrl" TEXT,
|
||||
"difyApiKey" TEXT,
|
||||
"usageCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"status" TEXT NOT NULL DEFAULT 'active',
|
||||
"isFeatured" BOOLEAN NOT NULL DEFAULT false,
|
||||
"featuredOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Agent_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_Agent" ("categoryId", "createdAt", "description", "features", "icon", "id", "name", "slug", "status", "updatedAt", "usageCount") SELECT "categoryId", "createdAt", "description", "features", "icon", "id", "name", "slug", "status", "updatedAt", "usageCount" FROM "Agent";
|
||||
DROP TABLE "Agent";
|
||||
ALTER TABLE "new_Agent" RENAME TO "Agent";
|
||||
CREATE UNIQUE INDEX "Agent_slug_key" ON "Agent"("slug");
|
||||
PRAGMA foreign_keys=ON;
|
||||
PRAGMA defer_foreign_keys=OFF;
|
||||
@@ -39,8 +39,12 @@ model Agent {
|
||||
features String @default("")
|
||||
hotQuestions String @default("[]")
|
||||
quickQuestions String @default("[]")
|
||||
difyApiUrl String?
|
||||
difyApiKey String?
|
||||
usageCount Int @default(0)
|
||||
status String @default("active")
|
||||
isFeatured Boolean @default(false)
|
||||
featuredOrder Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
category Category? @relation(fields: [categoryId], references: [id])
|
||||
|
||||
+26
-7
@@ -113,20 +113,37 @@ async function main() {
|
||||
]),
|
||||
usageCount: 1000,
|
||||
status: "active",
|
||||
isFeatured: true,
|
||||
featuredOrder: 1,
|
||||
difyApiUrl: "https://api.dify.ai/v1",
|
||||
difyApiKey: "app-demo-key-123456",
|
||||
},
|
||||
}),
|
||||
prisma.agent.upsert({
|
||||
where: { slug: "writing-assistant-pro" },
|
||||
where: { slug: "huaiqi-secretary" },
|
||||
update: {},
|
||||
create: {
|
||||
name: "写作助手 Pro",
|
||||
slug: "writing-assistant-pro",
|
||||
description: "营销文案、博客文章、邮件草稿,输入关键词即可生成高质量内容。",
|
||||
icon: "✍️",
|
||||
categoryId: categories[1].id,
|
||||
features: "营销文案, 博客文章, 邮件草稿",
|
||||
name: "淮企小秘书",
|
||||
slug: "huaiqi-secretary",
|
||||
description: "专为淮安企业打造的智能秘书,提供政策解读、企业办事指南、惠企政策查询等一站式服务,助力企业高效运营。",
|
||||
icon: "🏢",
|
||||
categoryId: categories[6].id,
|
||||
features: "政策解读, 企业办事指南, 惠企政策, 智能问答",
|
||||
hotQuestions: JSON.stringify([
|
||||
"到淮安这边投资,项目审批快吗?",
|
||||
"目前淮安对绿色工厂有哪些支持政策?",
|
||||
"请问企业实施技术改造项目,淮安市有哪些支持政策?",
|
||||
"淮安有哪些产业配套服务?",
|
||||
]),
|
||||
quickQuestions: JSON.stringify([
|
||||
"惠企政策查询",
|
||||
"企业办事指南",
|
||||
"政策解读",
|
||||
]),
|
||||
usageCount: 850,
|
||||
status: "active",
|
||||
isFeatured: true,
|
||||
featuredOrder: 2,
|
||||
},
|
||||
}),
|
||||
prisma.agent.upsert({
|
||||
@@ -141,6 +158,8 @@ async function main() {
|
||||
features: "数据清洗, 数据可视化, 分析报告",
|
||||
usageCount: 620,
|
||||
status: "active",
|
||||
isFeatured: true,
|
||||
featuredOrder: 3,
|
||||
},
|
||||
}),
|
||||
prisma.agent.upsert({
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
发送对话消息接口如下,该接口能正常调用
|
||||
curl -X POST 'http://df.clkeji.com/v1/chat-messages' \
|
||||
--header 'Authorization: Bearer app-lbe2lglt7taGtZk0dG7pAhbx' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"inputs": {},
|
||||
"query": "What are the specs of the iPhone 13 Pro Max?",
|
||||
"response_mode": "streaming",
|
||||
"conversation_id": "",
|
||||
"user": "abc-123"
|
||||
}'
|
||||
|
||||
问题描述:点击对话,功能异常,请排查原因并修复
|
||||
|
||||
Reference in New Issue
Block a user