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:
root
2026-05-08 20:15:54 +08:00
parent 362c37fb42
commit f2d7037ca2
16 changed files with 298 additions and 102 deletions
+83 -2
View File
@@ -18,7 +18,11 @@ export default function AgentForm({
features?: string features?: string
hotQuestions?: string hotQuestions?: string
quickQuestions?: string quickQuestions?: string
difyApiUrl?: string
difyApiKey?: string
status?: string status?: string
isFeatured?: boolean
featuredOrder?: number
} }
}) { }) {
const router = useRouter() const router = useRouter()
@@ -31,10 +35,15 @@ export default function AgentForm({
features: agent?.features || "", features: agent?.features || "",
hotQuestions: agent?.hotQuestions ? JSON.parse(agent.hotQuestions).join('\n') : '', hotQuestions: agent?.hotQuestions ? JSON.parse(agent.hotQuestions).join('\n') : '',
quickQuestions: agent?.quickQuestions ? JSON.parse(agent.quickQuestions).join('\n') : '', quickQuestions: agent?.quickQuestions ? JSON.parse(agent.quickQuestions).join('\n') : '',
difyApiUrl: agent?.difyApiUrl || "",
difyApiKey: agent?.difyApiKey || "",
status: agent?.status || "active", status: agent?.status || "active",
isFeatured: agent?.isFeatured ?? false,
featuredOrder: agent?.featuredOrder ?? 0,
}) })
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [error, setError] = useState("") const [error, setError] = useState("")
const [success, setSuccess] = useState(false)
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault() e.preventDefault()
@@ -50,6 +59,8 @@ export default function AgentForm({
const body = { const body = {
...formData, ...formData,
isFeatured: formData.isFeatured,
featuredOrder: formData.featuredOrder,
hotQuestions: JSON.stringify(formData.hotQuestions.split('\n').filter(q => q.trim())), hotQuestions: JSON.stringify(formData.hotQuestions.split('\n').filter(q => q.trim())),
quickQuestions: JSON.stringify(formData.quickQuestions.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) { if (res.ok) {
setSuccess(true)
await new Promise(r => setTimeout(r, 1000))
router.push("/admin/agents") router.push("/admin/agents")
} else { } else {
setError("保存失败") const data = await res.json().catch(() => ({}))
setError(data.error || "保存失败")
} }
} catch (err) { } catch (err) {
setError("保存失败,请稍后重试") setError("保存失败,请稍后重试")
@@ -185,6 +199,34 @@ export default function AgentForm({
/> />
</div> </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 className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">
@@ -212,10 +254,49 @@ export default function AgentForm({
</div> </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">
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"> <div className="flex gap-4">
<button <button
type="submit" 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" className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 disabled:bg-blue-400"
> >
{loading ? "保存中..." : "保存"} {loading ? "保存中..." : "保存"}
+4
View File
@@ -51,7 +51,11 @@ export default async function EditAgentPage({
features: agent.features, features: agent.features,
hotQuestions: agent.hotQuestions, hotQuestions: agent.hotQuestions,
quickQuestions: agent.quickQuestions, quickQuestions: agent.quickQuestions,
difyApiUrl: agent.difyApiUrl || "",
difyApiKey: agent.difyApiKey || "",
status: agent.status, status: agent.status,
isFeatured: agent.isFeatured,
featuredOrder: agent.featuredOrder,
}} }}
/> />
</div> </div>
+10
View File
@@ -39,6 +39,7 @@ export default async function AdminAgentsPage() {
<tr> <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-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> <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"> <td className="px-6 py-4 text-sm text-gray-600">
{agent.category?.name || "-"} {agent.category?.name || "-"}
</td> </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"> <td className="px-6 py-4">
<span className={`inline-flex px-2 py-1 text-xs font-medium rounded-full ${ <span className={`inline-flex px-2 py-1 text-xs font-medium rounded-full ${
agent.status === "active" agent.status === "active"
+18 -2
View File
@@ -21,6 +21,7 @@ export default function CategoryForm({
}) })
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [error, setError] = useState("") const [error, setError] = useState("")
const [success, setSuccess] = useState(false)
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault() e.preventDefault()
@@ -41,9 +42,12 @@ export default function CategoryForm({
}) })
if (res.ok) { if (res.ok) {
setSuccess(true)
await new Promise(r => setTimeout(r, 1000))
router.push("/admin/categories") router.push("/admin/categories")
} else { } else {
setError("保存失败") const data = await res.json().catch(() => ({}))
setError(data.error || "保存失败")
} }
} catch (err) { } catch (err) {
setError("保存失败,请稍后重试") setError("保存失败,请稍后重试")
@@ -100,10 +104,22 @@ export default function CategoryForm({
/> />
</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"> <div className="flex gap-4">
<button <button
type="submit" 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" className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 disabled:bg-blue-400"
> >
{loading ? "保存中..." : "保存"} {loading ? "保存中..." : "保存"}
+18 -2
View File
@@ -23,6 +23,7 @@ export default function NewsForm({
}) })
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [error, setError] = useState("") const [error, setError] = useState("")
const [success, setSuccess] = useState(false)
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault() e.preventDefault()
@@ -43,9 +44,12 @@ export default function NewsForm({
}) })
if (res.ok) { if (res.ok) {
setSuccess(true)
await new Promise(r => setTimeout(r, 1000))
router.push("/admin/news") router.push("/admin/news")
} else { } else {
setError("保存失败") const data = await res.json().catch(() => ({}))
setError(data.error || "保存失败")
} }
} catch (err) { } catch (err) {
setError("保存失败,请稍后重试") setError("保存失败,请稍后重试")
@@ -121,10 +125,22 @@ export default function NewsForm({
</div> </div>
</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"> <div className="flex gap-4">
<button <button
type="submit" 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" className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 disabled:bg-blue-400"
> >
{loading ? "保存中..." : "保存"} {loading ? "保存中..." : "保存"}
+27 -27
View File
@@ -48,8 +48,10 @@ export default function AgentChatPage() {
const [currentConversationId, setCurrentConversationId] = useState<number | null>(null) const [currentConversationId, setCurrentConversationId] = useState<number | null>(null)
const [upstreamConversationId, setUpstreamConversationId] = useState<string>('') const [upstreamConversationId, setUpstreamConversationId] = useState<string>('')
const [loadingConversations, setLoadingConversations] = useState(false) const [loadingConversations, setLoadingConversations] = useState(false)
const [abortController, setAbortController] = useState<AbortController | null>(null)
const messagesEndRef = useRef<HTMLDivElement>(null) const messagesEndRef = useRef<HTMLDivElement>(null)
const userIdRef = useRef<string>('user-' + Math.random().toString(36).slice(2, 10))
const CHAT_API_URL = '/api/chat' const CHAT_API_URL = '/api/chat'
@@ -190,8 +192,6 @@ export default function AgentChatPage() {
}) })
const assistantMessageId = (Date.now() + 1).toString() const assistantMessageId = (Date.now() + 1).toString()
let assistantContent = ''
let upstreamConvId = upstreamConversationId
setMessages(prev => [...prev, { setMessages(prev => [...prev, {
id: assistantMessageId, id: assistantMessageId,
@@ -200,70 +200,70 @@ export default function AgentChatPage() {
timestamp: new Date(), timestamp: new Date(),
}]) }])
const controller = new AbortController()
setAbortController(controller)
const response = await fetch(CHAT_API_URL, { const response = await fetch(CHAT_API_URL, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
agentId: agent.id,
inputs: {}, inputs: {},
query: currentInput, query: currentInput,
response_mode: 'streaming', response_mode: 'streaming',
conversation_id: upstreamConversationId, conversation_id: upstreamConversationId,
user: 'user-' + Date.now(), user: userIdRef.current,
}), }),
signal: controller.signal,
}) })
const reader = response.body?.getReader() if (!response.ok) {
const decoder = new TextDecoder() throw new Error('Chat request failed')
}
if (reader) { const reader = response.body!.getReader()
const decoder = new TextDecoder()
let buffer = '' let buffer = ''
let accumulatedContent = ''
let finalConvId = upstreamConversationId
while (true) { while (true) {
const { done, value } = await reader.read() const { done, value } = await reader.read()
if (done) break if (done) break
buffer += decoder.decode(value, { stream: true }) buffer += decoder.decode(value, { stream: true })
const lines = buffer.split('\n') const lines = buffer.split('\n')
buffer = lines.pop() || '' buffer = lines.pop() || ''
for (const line of lines) { for (const line of lines) {
if (line.startsWith('data: ')) { if (line.startsWith('data: ')) {
try { try {
const json = JSON.parse(line.slice(6)) const data = JSON.parse(line.slice(6))
if (json.conversation_id) { if (data.event === 'message' && data.answer) {
upstreamConvId = json.conversation_id accumulatedContent += data.answer
}
const token = json.answer || json.message || ''
if (token) {
assistantContent += token
setMessages(prev => prev.map(msg => setMessages(prev => prev.map(msg =>
msg.id === assistantMessageId msg.id === assistantMessageId
? { ...msg, content: assistantContent } ? { ...msg, content: accumulatedContent }
: msg : msg
)) ))
} else if (data.event === 'message_end') {
finalConvId = data.conversation_id || finalConvId
} }
} catch (e) { } catch (e) {
// ignore parse errors // skip invalid JSON
}
} }
} }
} }
} }
setUpstreamConversationId(upstreamConvId) setUpstreamConversationId(finalConvId)
if (!assistantContent) {
setMessages(prev => prev.map(msg =>
msg.id === assistantMessageId
? { ...msg, content: '抱歉,我暂时无法回答这个问题。' }
: msg
))
assistantContent = '抱歉,我暂时无法回答这个问题。'
}
await fetch(`/api/conversations/${conversationId}/messages`, { await fetch(`/api/conversations/${conversationId}/messages`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
role: 'assistant', role: 'assistant',
content: assistantContent, content: accumulatedContent,
}), }),
}) })
} catch (error) { } catch (error) {
+4
View File
@@ -17,10 +17,14 @@ export async function PUT(
description: data.description, description: data.description,
icon: data.icon || null, icon: data.icon || null,
categoryId: data.categoryId ? parseInt(data.categoryId) : null, categoryId: data.categoryId ? parseInt(data.categoryId) : null,
difyApiUrl: data.difyApiUrl || null,
difyApiKey: data.difyApiKey || null,
features: data.features || "", features: data.features || "",
hotQuestions: data.hotQuestions || "[]", hotQuestions: data.hotQuestions || "[]",
quickQuestions: data.quickQuestions || "[]", quickQuestions: data.quickQuestions || "[]",
status: data.status || "active", status: data.status || "active",
isFeatured: data.isFeatured ?? false,
featuredOrder: data.featuredOrder ?? 0,
}, },
}) })
return NextResponse.json(agent) return NextResponse.json(agent)
+4
View File
@@ -12,10 +12,14 @@ export async function POST(request: NextRequest) {
description: data.description, description: data.description,
icon: data.icon || null, icon: data.icon || null,
categoryId: data.categoryId ? parseInt(data.categoryId) : null, categoryId: data.categoryId ? parseInt(data.categoryId) : null,
difyApiUrl: data.difyApiUrl || null,
difyApiKey: data.difyApiKey || null,
features: data.features || "", features: data.features || "",
hotQuestions: data.hotQuestions || "[]", hotQuestions: data.hotQuestions || "[]",
quickQuestions: data.quickQuestions || "[]", quickQuestions: data.quickQuestions || "[]",
status: data.status || "active", status: data.status || "active",
isFeatured: data.isFeatured ?? false,
featuredOrder: data.featuredOrder ?? 0,
}, },
}) })
return NextResponse.json(agent) return NextResponse.json(agent)
+37 -19
View File
@@ -1,33 +1,51 @@
import { NextResponse } from "next/server" import { NextResponse } from "next/server"
import { prisma } from "@/app/lib/prisma"
const API_KEY = 'app-lbe2lglt7taGtZk0dG7pAhbx' const FALLBACK_API_KEY = 'app-lbe2lglt7taGtZk0dG7pAhbx'
const API_URL = 'http://df.clkeji.com/v1/chat-messages' const FALLBACK_API_URL = 'http://df.clkeji.com/v1/chat-messages'
export async function POST(request: Request) { export async function POST(request: Request) {
try { try {
const body = await request.json() const body = await request.json()
const { agentId, ...difyBody } = body
const response = await fetch(API_URL, { let apiKey = FALLBACK_API_KEY
method: 'POST', let apiUrl = FALLBACK_API_URL
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
})
if (body.response_mode === 'streaming') { if (agentId) {
return new NextResponse(response.body, { const agent = await prisma.agent.findUnique({
headers: { where: { id: parseInt(agentId) },
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
}) })
if (agent?.difyApiUrl && agent?.difyApiKey) {
apiUrl = agent.difyApiUrl.replace(/\/+$/, '') + '/chat-messages'
apiKey = agent.difyApiKey
}
} }
const data = await response.json() const response = await fetch(apiUrl, {
return NextResponse.json(data) 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) { } catch (error) {
console.error('Chat API error:', error) console.error('Chat API error:', error)
return NextResponse.json( return NextResponse.json(
+2
View File
@@ -4,7 +4,9 @@ import Image from "next/image"
export default async function HomePage() { export default async function HomePage() {
const agents = await prisma.agent.findMany({ const agents = await prisma.agent.findMany({
where: { isFeatured: true },
include: { category: true }, include: { category: true },
orderBy: { featuredOrder: "asc" },
take: 6, take: 6,
}) })
+3
View File
@@ -21,6 +21,9 @@
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"remark-gfm": "^4.0.1" "remark-gfm": "^4.0.1"
}, },
"prisma": {
"seed": "tsx prisma/seed.ts"
},
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4.0.0", "@tailwindcss/postcss": "^4.0.0",
"@types/node": "^22.13.1", "@types/node": "^22.13.1",
BIN
View File
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;
+4
View File
@@ -39,8 +39,12 @@ model Agent {
features String @default("") features String @default("")
hotQuestions String @default("[]") hotQuestions String @default("[]")
quickQuestions String @default("[]") quickQuestions String @default("[]")
difyApiUrl String?
difyApiKey String?
usageCount Int @default(0) usageCount Int @default(0)
status String @default("active") status String @default("active")
isFeatured Boolean @default(false)
featuredOrder Int @default(0)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
category Category? @relation(fields: [categoryId], references: [id]) category Category? @relation(fields: [categoryId], references: [id])
+26 -7
View File
@@ -113,20 +113,37 @@ async function main() {
]), ]),
usageCount: 1000, usageCount: 1000,
status: "active", status: "active",
isFeatured: true,
featuredOrder: 1,
difyApiUrl: "https://api.dify.ai/v1",
difyApiKey: "app-demo-key-123456",
}, },
}), }),
prisma.agent.upsert({ prisma.agent.upsert({
where: { slug: "writing-assistant-pro" }, where: { slug: "huaiqi-secretary" },
update: {}, update: {},
create: { create: {
name: "写作助手 Pro", name: "淮企小秘书",
slug: "writing-assistant-pro", slug: "huaiqi-secretary",
description: "营销文案、博客文章、邮件草稿,输入关键词即可生成高质量内容。", description: "专为淮安企业打造的智能秘书,提供政策解读、企业办事指南、惠企政策查询等一站式服务,助力企业高效运营。",
icon: "✍️", icon: "🏢",
categoryId: categories[1].id, categoryId: categories[6].id,
features: "营销文案, 博客文章, 邮件草稿", features: "政策解读, 企业办事指南, 惠企政策, 智能问答",
hotQuestions: JSON.stringify([
"到淮安这边投资,项目审批快吗?",
"目前淮安对绿色工厂有哪些支持政策?",
"请问企业实施技术改造项目,淮安市有哪些支持政策?",
"淮安有哪些产业配套服务?",
]),
quickQuestions: JSON.stringify([
"惠企政策查询",
"企业办事指南",
"政策解读",
]),
usageCount: 850, usageCount: 850,
status: "active", status: "active",
isFeatured: true,
featuredOrder: 2,
}, },
}), }),
prisma.agent.upsert({ prisma.agent.upsert({
@@ -141,6 +158,8 @@ async function main() {
features: "数据清洗, 数据可视化, 分析报告", features: "数据清洗, 数据可视化, 分析报告",
usageCount: 620, usageCount: 620,
status: "active", status: "active",
isFeatured: true,
featuredOrder: 3,
}, },
}), }),
prisma.agent.upsert({ prisma.agent.upsert({
-14
View File
@@ -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"
}'
问题描述:点击对话,功能异常,请排查原因并修复