feat: 优化追问功能 - LLM智能生成 + 显示位置修复
- 修复 AuthLoader 组件中 router.push 在渲染期间调用的 React 警告
- 追问问题改为只在最新 AI 回复下方显示(修复多处显示问题)
- 新增 LLM 生成追问 API:POST /api/v1/apps/{id}/suggestions
- 前端调用 LLM API 生成智能追问,失败时降级到规则生成
- 登录页填入默认测试账号 kj-admin@govai.gov.cn
This commit is contained in:
@@ -20,8 +20,8 @@ import {
|
|||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("kj-admin@govai.gov.cn");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("admin123");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [errorMsg, setErrorMsg] = useState("");
|
const [errorMsg, setErrorMsg] = useState("");
|
||||||
const [orgs, setOrgs] = useState<Organization[]>([]);
|
const [orgs, setOrgs] = useState<Organization[]>([]);
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ interface AgentUIProps {
|
|||||||
export default function AgentUI({ app }: AgentUIProps) {
|
export default function AgentUI({ app }: AgentUIProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const [conversationId, setConversationId] = useState<string | undefined>();
|
||||||
const [messages, setMessages] = useState<Message[]>(
|
const [messages, setMessages] = useState<Message[]>(
|
||||||
app.welcome_message && !conversationId
|
app.welcome_message && !conversationId
|
||||||
? [{ id: "welcome", role: "assistant", content: app.welcome_message }]
|
? [{ id: "welcome", role: "assistant", content: app.welcome_message }]
|
||||||
@@ -147,7 +148,6 @@ export default function AgentUI({ app }: AgentUIProps) {
|
|||||||
);
|
);
|
||||||
const [input, setInput] = useState("");
|
const [input, setInput] = useState("");
|
||||||
const [isStreaming, setIsStreaming] = useState(false);
|
const [isStreaming, setIsStreaming] = useState(false);
|
||||||
const [conversationId, setConversationId] = useState<string | undefined>();
|
|
||||||
const [selectMode, setSelectMode] = useState(false);
|
const [selectMode, setSelectMode] = useState(false);
|
||||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
||||||
const [deleteTarget, setDeleteTarget] = useState<{
|
const [deleteTarget, setDeleteTarget] = useState<{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useState, useEffect, useCallback, memo, useMemo } from "react";
|
import { useState, useEffect, useCallback, memo, useMemo, useRef } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import type { App, Conversation, Message, Chunk } from "@/lib/types";
|
import type { App, Conversation, Message, Chunk } from "@/lib/types";
|
||||||
@@ -45,10 +45,14 @@ const ChatMessage = memo(function ChatMessage({
|
|||||||
msg,
|
msg,
|
||||||
onCopy,
|
onCopy,
|
||||||
chunks,
|
chunks,
|
||||||
|
suggestions,
|
||||||
|
onSuggestionClick,
|
||||||
}: {
|
}: {
|
||||||
msg: Message;
|
msg: Message;
|
||||||
onCopy: (text: string) => void;
|
onCopy: (text: string) => void;
|
||||||
chunks: Chunk[];
|
chunks: Chunk[];
|
||||||
|
suggestions?: string[];
|
||||||
|
onSuggestionClick?: (text: string) => void;
|
||||||
}) {
|
}) {
|
||||||
if (msg.role === "user") {
|
if (msg.role === "user") {
|
||||||
return (
|
return (
|
||||||
@@ -85,6 +89,20 @@ const ChatMessage = memo(function ChatMessage({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{/* 对话结束后的提示问题 */}
|
||||||
|
{suggestions && suggestions.length > 0 && (
|
||||||
|
<div className="flex flex-wrap gap-2 mt-3">
|
||||||
|
{suggestions.map((suggestion, i) => (
|
||||||
|
<button
|
||||||
|
key={i}
|
||||||
|
onClick={() => onSuggestionClick?.(suggestion)}
|
||||||
|
className="text-xs px-3 py-1.5 rounded-full bg-blue-50 dark:bg-blue-950 text-blue-700 dark:text-blue-300 hover:bg-blue-100 dark:hover:bg-blue-900 border border-blue-200 dark:border-blue-800 transition-colors"
|
||||||
|
>
|
||||||
|
{suggestion}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -98,14 +116,15 @@ export default function ChatbotUI({ app }: ChatbotUIProps) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { user } = useAuthStore();
|
const { user } = useAuthStore();
|
||||||
|
const [conversationId, setConversationId] = useState<string | undefined>();
|
||||||
const [messages, setMessages] = useState<Message[]>(
|
const [messages, setMessages] = useState<Message[]>(
|
||||||
app.welcome_message && !conversationId
|
app.welcome_message && !conversationId
|
||||||
? [{ id: "welcome", role: "assistant", content: app.welcome_message }]
|
? [{ id: "welcome", role: "assistant", content: app.welcome_message }]
|
||||||
: [],
|
: [],
|
||||||
);
|
);
|
||||||
|
const [currentSuggestions, setCurrentSuggestions] = useState<string[]>([]);
|
||||||
const [input, setInput] = useState("");
|
const [input, setInput] = useState("");
|
||||||
const [isStreaming, setIsStreaming] = useState(false);
|
const [isStreaming, setIsStreaming] = useState(false);
|
||||||
const [conversationId, setConversationId] = useState<string | undefined>();
|
|
||||||
const [selectMode, setSelectMode] = useState(false);
|
const [selectMode, setSelectMode] = useState(false);
|
||||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
||||||
const [deleteTarget, setDeleteTarget] = useState<{
|
const [deleteTarget, setDeleteTarget] = useState<{
|
||||||
@@ -263,10 +282,12 @@ export default function ChatbotUI({ app }: ChatbotUIProps) {
|
|||||||
setFileContent(null);
|
setFileContent(null);
|
||||||
setFileName(null);
|
setFileName(null);
|
||||||
setChunks([]);
|
setChunks([]);
|
||||||
|
setCurrentSuggestions([]); // 清空之前的提示问题
|
||||||
setIsStreaming(true);
|
setIsStreaming(true);
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
abortRef.current = controller;
|
abortRef.current = controller;
|
||||||
|
const accumulatedRef = { current: "" };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await streamChat(app.id, fullMessage, conversationId, controller.signal);
|
const res = await streamChat(app.id, fullMessage, conversationId, controller.signal);
|
||||||
@@ -277,6 +298,7 @@ export default function ChatbotUI({ app }: ChatbotUIProps) {
|
|||||||
|
|
||||||
let buffer = "";
|
let buffer = "";
|
||||||
let accumulated = "";
|
let accumulated = "";
|
||||||
|
let conversationIdFromResponse: string | undefined;
|
||||||
while (true) {
|
while (true) {
|
||||||
const { done, value } = await reader.read();
|
const { done, value } = await reader.read();
|
||||||
if (done) break;
|
if (done) break;
|
||||||
@@ -289,7 +311,7 @@ export default function ChatbotUI({ app }: ChatbotUIProps) {
|
|||||||
if (raw === "[DONE]") break;
|
if (raw === "[DONE]") break;
|
||||||
try {
|
try {
|
||||||
const event = JSON.parse(raw);
|
const event = JSON.parse(raw);
|
||||||
if (event.conversation_id) setConversationId(event.conversation_id);
|
if (event.conversation_id) conversationIdFromResponse = event.conversation_id;
|
||||||
// 解析首包的 chunks 映射表
|
// 解析首包的 chunks 映射表
|
||||||
if (event.chunks) {
|
if (event.chunks) {
|
||||||
console.log("[SSE] Received chunks:", event.chunks);
|
console.log("[SSE] Received chunks:", event.chunks);
|
||||||
@@ -297,6 +319,7 @@ export default function ChatbotUI({ app }: ChatbotUIProps) {
|
|||||||
}
|
}
|
||||||
if (event.answer) {
|
if (event.answer) {
|
||||||
accumulated += event.answer;
|
accumulated += event.answer;
|
||||||
|
accumulatedRef.current += event.answer;
|
||||||
const snap = accumulated;
|
const snap = accumulated;
|
||||||
setMessages((prev) =>
|
setMessages((prev) =>
|
||||||
prev.map((m, i) =>
|
prev.map((m, i) =>
|
||||||
@@ -309,6 +332,7 @@ export default function ChatbotUI({ app }: ChatbotUIProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (conversationIdFromResponse) setConversationId(conversationIdFromResponse);
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: ["conversations", app.id],
|
queryKey: ["conversations", app.id],
|
||||||
});
|
});
|
||||||
@@ -328,8 +352,64 @@ export default function ChatbotUI({ app }: ChatbotUIProps) {
|
|||||||
} finally {
|
} finally {
|
||||||
abortRef.current = null;
|
abortRef.current = null;
|
||||||
setIsStreaming(false);
|
setIsStreaming(false);
|
||||||
|
// 生成对话结束后的提示问题
|
||||||
|
generateChatSuggestions(messages);
|
||||||
}
|
}
|
||||||
}, [input, isStreaming, app.id, conversationId, queryClient]);
|
}, [input, isStreaming, app.id, conversationId, queryClient, messages]);
|
||||||
|
|
||||||
|
// 生成对话结束后的提示问题(调用 LLM API)
|
||||||
|
const generateChatSuggestions = async (allMessages: Message[]) => {
|
||||||
|
// 过滤掉 welcome 消息,只保留用户和助手的实际对话
|
||||||
|
const realMessages = allMessages.filter((m) => m.id !== "welcome").slice(-10);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await api.post<{ data: string[] }>(`/api/v1/apps/${app.id}/suggestions`, {
|
||||||
|
conversation_id: conversationId,
|
||||||
|
messages: realMessages.map((m) => ({ role: m.role, content: m.content })),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data && response.data.length > 0) {
|
||||||
|
setCurrentSuggestions(response.data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("LLM 生成追问失败,使用规则生成:", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// API 调用失败时,使用规则生成作为降级方案
|
||||||
|
generateFallbackSuggestions(allMessages);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 规则生成追问(降级方案)
|
||||||
|
const generateFallbackSuggestions = (allMessages: Message[]) => {
|
||||||
|
const lastResponse = allMessages[allMessages.length - 1]?.content || "";
|
||||||
|
const suggestions: string[] = [];
|
||||||
|
const lowerResponse = lastResponse.toLowerCase();
|
||||||
|
|
||||||
|
// 基于回复内容生成针对性追问
|
||||||
|
if (lowerResponse.includes("政策") || lowerResponse.includes("法规") || lowerResponse.includes("法律")) {
|
||||||
|
suggestions.push("还有哪些相关政策?", "政策的适用范围是什么?");
|
||||||
|
}
|
||||||
|
if (lowerResponse.includes("流程") || lowerResponse.includes("步骤") || lowerResponse.includes("程序")) {
|
||||||
|
suggestions.push("具体流程是什么?", "需要准备哪些材料?");
|
||||||
|
}
|
||||||
|
if (lowerResponse.includes("条件") || lowerResponse.includes("要求") || lowerResponse.includes("资格")) {
|
||||||
|
suggestions.push("具体需要什么条件?", "不符合条件怎么办?");
|
||||||
|
}
|
||||||
|
if (lowerResponse.includes("费用") || lowerResponse.includes("收费")) {
|
||||||
|
suggestions.push("收费标准是多少?", "有优惠政策吗?");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suggestions.length < 4) {
|
||||||
|
const generic = ["能详细说明一下吗?", "有什么需要注意的?", "可以举个例子吗?", "还有其他方案吗?"];
|
||||||
|
for (const q of generic) {
|
||||||
|
if (suggestions.length >= 4) break;
|
||||||
|
if (!suggestions.includes(q)) suggestions.push(q);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentSuggestions(suggestions.slice(0, 4));
|
||||||
|
};
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
(e: React.KeyboardEvent) => {
|
(e: React.KeyboardEvent) => {
|
||||||
@@ -346,6 +426,7 @@ export default function ChatbotUI({ app }: ChatbotUIProps) {
|
|||||||
setMessages([]);
|
setMessages([]);
|
||||||
setConversationId(undefined);
|
setConversationId(undefined);
|
||||||
setIsStreaming(false);
|
setIsStreaming(false);
|
||||||
|
setCurrentSuggestions([]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const toggleSelect = useCallback((id: string) => {
|
const toggleSelect = useCallback((id: string) => {
|
||||||
@@ -654,9 +735,22 @@ export default function ChatbotUI({ app }: ChatbotUIProps) {
|
|||||||
|
|
||||||
<div className="flex-1 overflow-y-auto min-h-0 p-4">
|
<div className="flex-1 overflow-y-auto min-h-0 p-4">
|
||||||
<div className="max-w-3xl mx-auto space-y-4">
|
<div className="max-w-3xl mx-auto space-y-4">
|
||||||
{messages.map((msg) => (
|
{messages.map((msg, idx) => {
|
||||||
<ChatMessage key={msg.id} msg={msg} onCopy={copyText} chunks={chunks} />
|
const isLastAssistant = msg.role === "assistant" && idx === messages.length - 1;
|
||||||
))}
|
return (
|
||||||
|
<ChatMessage
|
||||||
|
key={msg.id}
|
||||||
|
msg={msg}
|
||||||
|
onCopy={copyText}
|
||||||
|
chunks={chunks}
|
||||||
|
suggestions={isLastAssistant ? currentSuggestions : undefined}
|
||||||
|
onSuggestionClick={(text) => {
|
||||||
|
setInput(text);
|
||||||
|
textareaRef.current?.focus();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
{messages.length <= 1 && suggestedPrompts.length > 0 && (
|
{messages.length <= 1 && suggestedPrompts.length > 0 && (
|
||||||
<div className="flex flex-wrap gap-2 mt-4">
|
<div className="flex flex-wrap gap-2 mt-4">
|
||||||
|
|||||||
@@ -24,22 +24,20 @@ function AuthLoader({ children }: { children: React.ReactNode }) {
|
|||||||
}
|
}
|
||||||
}, [fetchUser, isPublic]);
|
}, [fetchUser, isPublic]);
|
||||||
|
|
||||||
|
// 鉴权路由:未登录则跳转(useEffect 中执行,避免渲染期间调用 router)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isLoading && !isAuthenticated && !isPublic) {
|
||||||
|
router.push("/login");
|
||||||
|
}
|
||||||
|
}, [isLoading, isAuthenticated, isPublic, router]);
|
||||||
|
|
||||||
// 公开路由:直接渲染,不阻塞
|
// 公开路由:直接渲染,不阻塞
|
||||||
if (isPublic) {
|
if (isPublic) {
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 鉴权路由:未登录则跳转
|
// 加载中或未认证
|
||||||
if (!isLoading && !isAuthenticated) {
|
if (isLoading || !isAuthenticated) {
|
||||||
router.push("/login");
|
|
||||||
return (
|
|
||||||
<div className="flex h-screen items-center justify-center">
|
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen items-center justify-center">
|
<div className="flex h-screen items-center justify-center">
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ func newRouter(cfg *config.Config, pool *pgxpool.Pool, rdb *redis.Client) http.H
|
|||||||
r.Post("/generate-analysis", analysisH.GenerateReport)
|
r.Post("/generate-analysis", analysisH.GenerateReport)
|
||||||
r.Get("/conversations", chatH.Conversations)
|
r.Get("/conversations", chatH.Conversations)
|
||||||
r.Get("/conversations/{convId}/messages", chatH.Messages)
|
r.Get("/conversations/{convId}/messages", chatH.Messages)
|
||||||
|
r.Post("/suggestions", chatH.GetSuggestions)
|
||||||
r.Delete("/conversations/{convId}", chatH.DeleteConversation)
|
r.Delete("/conversations/{convId}", chatH.DeleteConversation)
|
||||||
r.Put("/conversations/{convId}/name", chatH.RenameConversation)
|
r.Put("/conversations/{convId}/name", chatH.RenameConversation)
|
||||||
r.Post("/conversations/batch-delete", chatH.BatchDeleteConversations)
|
r.Post("/conversations/batch-delete", chatH.BatchDeleteConversations)
|
||||||
|
|||||||
@@ -1737,3 +1737,100 @@ func (h *LLMChatHandler) generateConversationName(appID, userID, convID, userMes
|
|||||||
DO UPDATE SET name = EXCLUDED.name, updated_at = now()`,
|
DO UPDATE SET name = EXCLUDED.name, updated_at = now()`,
|
||||||
appID, userID, convID, name)
|
appID, userID, convID, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenerateSuggestions 生成追问建议(由 LLM 智能生成)
|
||||||
|
func (h *LLMChatHandler) GenerateSuggestions(ctx context.Context, conversationID, appID string, messages []map[string]string) ([]string, error) {
|
||||||
|
if len(messages) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建对话历史上下文
|
||||||
|
var historyBuilder strings.Builder
|
||||||
|
for i, msg := range messages {
|
||||||
|
role := "用户"
|
||||||
|
if msg["role"] == "assistant" {
|
||||||
|
role = "助手"
|
||||||
|
}
|
||||||
|
historyBuilder.WriteString(fmt.Sprintf("%d. %s:%s\n", i+1, role, msg["content"]))
|
||||||
|
}
|
||||||
|
|
||||||
|
systemPrompt := `你是一个智能政务问答助手。请根据对话历史,生成4个追问问题,帮助用户深入了解相关内容。
|
||||||
|
|
||||||
|
要求:
|
||||||
|
1. 问题必须与对话内容紧密相关
|
||||||
|
2. 每个问题不超过20个字
|
||||||
|
3. 问题要有深度,引导用户进一步思考
|
||||||
|
4. 避免重复或过于宽泛的问题
|
||||||
|
5. 直接输出问题,用换行分隔,不要编号,不要解释`
|
||||||
|
|
||||||
|
suggestReq := &llm.ChatRequest{
|
||||||
|
Model: "",
|
||||||
|
Messages: []llm.Message{
|
||||||
|
{Role: "system", Content: systemPrompt},
|
||||||
|
{Role: "user", Content: "对话历史:\n" + historyBuilder.String()},
|
||||||
|
},
|
||||||
|
Temperature: 0.7,
|
||||||
|
MaxTokens: 200,
|
||||||
|
Stream: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取激活的 provider
|
||||||
|
provider, defaultModel, err := h.getProviderWithModel(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if defaultModel != "" {
|
||||||
|
suggestReq.Model = defaultModel
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.manager.Chat(ctx, provider, suggestReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析结果:按换行分割
|
||||||
|
suggestions := strings.Split(strings.TrimSpace(result.Content), "\n")
|
||||||
|
var validSuggestions []string
|
||||||
|
for _, s := range suggestions {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
// 过滤空行和过长的行
|
||||||
|
if len(s) > 0 && len([]rune(s)) <= 25 {
|
||||||
|
validSuggestions = append(validSuggestions, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最多返回4个
|
||||||
|
if len(validSuggestions) > 4 {
|
||||||
|
validSuggestions = validSuggestions[:4]
|
||||||
|
}
|
||||||
|
|
||||||
|
return validSuggestions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSuggestions HTTP handler:获取追问建议
|
||||||
|
func (h *LLMChatHandler) GetSuggestions(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
appID := chi.URLParam(r, "id")
|
||||||
|
if appID == "" {
|
||||||
|
response.Error(w, http.StatusBadRequest, 40001, "缺少应用ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
ConversationID string `json:"conversation_id"`
|
||||||
|
Messages []map[string]string `json:"messages"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
response.Error(w, http.StatusBadRequest, 40002, "请求格式错误")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
suggestions, err := h.GenerateSuggestions(ctx, req.ConversationID, appID, req.Messages)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Msg("generate suggestions failed")
|
||||||
|
response.Error(w, http.StatusInternalServerError, 50001, "生成追问失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.JSON(w, http.StatusOK, suggestions)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user