From f6258c970bded0c03c08b5ce826e873d92b726f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=85=83=E5=9D=A4?= Date: Thu, 10 Jul 2025 09:35:04 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- service/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/service/index.ts b/service/index.ts index 19e4542..942dba6 100644 --- a/service/index.ts +++ b/service/index.ts @@ -44,9 +44,9 @@ export const fetchConversations = async (limit = 100, last_id = null) => { return get('conversations', { params: { limit, last_id } }) } -export const fetchChatList = async (conversationId: string, limit = 20, last_id = null) => { +export const fetchChatList = async (conversationId: string, limit = 20) => { return get('messages', { - params: { conversation_id: conversationId, limit, last_id } + params: { conversation_id: conversationId, limit} }) } From 8094d5e1288a6a17ccb670f52208b5b12eedf1d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=85=83=E5=9D=A4?= Date: Thu, 10 Jul 2025 09:36:28 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=8F=8D=E9=A6=88=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/chat-with-history/index.tsx | 287 ++++++++++----------- 1 file changed, 141 insertions(+), 146 deletions(-) diff --git a/app/components/chat-with-history/index.tsx b/app/components/chat-with-history/index.tsx index 493cb8c..f873b38 100644 --- a/app/components/chat-with-history/index.tsx +++ b/app/components/chat-with-history/index.tsx @@ -1,33 +1,26 @@ -import type { FC } from 'react' -import { - useEffect, - useState, -} from 'react' -import { useAsyncEffect } from 'ahooks' -import { useThemeContext } from '@/app/components/chat/embedded-chatbot/theme/theme-context' -import { - ChatWithHistoryContext, - useChatWithHistoryContext, -} from './context' -import { useChatWithHistory } from './hooks' -import Sidebar from './sidebar' -import HeaderInMobile from './header-in-mobile' -import ConfigPanel from './config-panel' -import ChatWrapper from './chat-wrapper' -import type { InstalledApp } from '@/models/explore' -import Loading from '@/app/components/base/loading' -import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' -import { useRouter } from 'next/navigation' -import AppUnavailable from '@/app/components/base/app-unavailable' +import type { FC } from "react"; +import "antd/dist/reset.css"; // 引入 antd 的样式 +import { useEffect, useState } from "react"; +import { useAsyncEffect } from "ahooks"; +import { useThemeContext } from "@/app/components/chat/embedded-chatbot/theme/theme-context"; +import { ChatWithHistoryContext, useChatWithHistoryContext } from "./context"; +import { useChatWithHistory } from "./hooks"; +import Sidebar from "./sidebar"; +import HeaderInMobile from "./header-in-mobile"; +import ConfigPanel from "./config-panel"; +import ChatWrapper from "./chat-wrapper"; +import type { InstalledApp } from "@/models/explore"; +import Loading from "@/app/components/base/loading"; +import useBreakpoints, { MediaType } from "@/hooks/use-breakpoints"; +import { useRouter } from "next/navigation"; +import AppUnavailable from "@/app/components/base/app-unavailable"; type ChatWithHistoryProps = { - className?: string - userStr?: string -} -const ChatWithHistory: FC = ({ - className, - userStr, -}) => { + className?: string; + userStr?: string; +}; + +const ChatWithHistory: FC = ({ className, userStr }) => { const { appInfoError, appData, @@ -38,82 +31,78 @@ const ChatWithHistory: FC = ({ chatShouldReloadKey, isMobile, themeBuilder, - } = useChatWithHistoryContext() + } = useChatWithHistoryContext(); - const chatReady = (!showConfigPanelBeforeChat || !!appPrevChatTree.length) - const customConfig = appData?.custom_config - const site = appData?.site + const chatReady = !showConfigPanelBeforeChat || !!appPrevChatTree.length; + const customConfig = appData?.custom_config; + const site = appData?.site; useEffect(() => { - themeBuilder?.buildTheme(site?.chat_color_theme, site?.chat_color_theme_inverted) + themeBuilder?.buildTheme( + site?.chat_color_theme, + site?.chat_color_theme_inverted + ); if (site) { - if (customConfig) - document.title = `${site.title}` - else - document.title = `${site.title} - Powered by Dify` + if (customConfig) document.title = `${site.title}`; + else document.title = `${site.title} - Powered by Dify`; } - }, [site, customConfig, themeBuilder]) + }, [site, customConfig, themeBuilder]); if (appInfoLoading) { - return ( - - ) + return ; } if (appInfoError) { - return ( - - ) + return ; } return ( -
- { - !isMobile && ( - - ) - } - { - isMobile && ( - - ) - } -
- { - showConfigPanelBeforeChat && !appChatListDataLoading && !appPrevChatTree.length && ( -
+
+ {!isMobile && } + {isMobile && } +
+ {showConfigPanelBeforeChat && + !appChatListDataLoading && + !appPrevChatTree.length && ( +
- ) - } - { - appChatListDataLoading && chatReady && ( - - ) - } - { - chatReady && !appChatListDataLoading && ( - - ) - } + )} + {appChatListDataLoading && chatReady && } + {chatReady && !appChatListDataLoading && ( + + )}
- ) -} + ); +}; export type ChatWithHistoryWrapProps = { - installedAppInfo?: InstalledApp - className?: string - userStr?: string -} + installedAppInfo?: InstalledApp; + className?: string; + userStr?: string; +}; + const ChatWithHistoryWrap: FC = ({ installedAppInfo, className, userStr, }) => { - const media = useBreakpoints() - const isMobile = media === MediaType.mobile - const themeBuilder = useThemeContext() + const media = useBreakpoints(); + const isMobile = media === MediaType.mobile; + const themeBuilder = useThemeContext(); const { appInfoError, @@ -146,93 +135,99 @@ const ChatWithHistoryWrap: FC = ({ appId, handleFeedback, currentChatInstanceRef, - } = useChatWithHistory(installedAppInfo) + feedbackModal, + } = useChatWithHistory(installedAppInfo); return ( - + + {feedbackModal} {/* 确保 feedbackModal 正确渲染 */} - ) -} + ); +}; const ChatWithHistoryWrapWithCheckToken: FC = ({ installedAppInfo, className, }) => { - const [initialized, setInitialized] = useState(false) - const [appUnavailable, setAppUnavailable] = useState(false) - const [resCode, setResCode] = useState(200) - const router = useRouter() - const [userName, setUserName] = useState('') - const [nickName, setNickName] = useState('') + const [initialized, setInitialized] = useState(false); + const [appUnavailable, setAppUnavailable] = useState(false); + const [resCode, setResCode] = useState(200); + const router = useRouter(); + const [userName, setUserName] = useState(""); + const [nickName, setNickName] = useState(""); + useAsyncEffect(async () => { if (!initialized) { if (!installedAppInfo) { - const accessToken = localStorage.getItem('token') + const accessToken = localStorage.getItem("token"); if (!accessToken) { - router.replace('/login') + router.replace("/login"); } else { fetch(`${process.env.NEXT_PUBLIC_BASE_API_URL}/getInfo`, { - method: 'GET', + method: "GET", headers: { Authorization: `Bearer ${accessToken}`, }, - }).then(res => res.json()).then(data => { - if (data.code !== 200) { - localStorage.removeItem('token') - router.replace('/login') - } else { - setUserName(data.user.userName) - setNickName(data.user.nickName) - } - }).catch(() => { - setResCode(500) - setAppUnavailable(true) }) + .then((res) => res.json()) + .then((data) => { + if (data.code !== 200) { + localStorage.removeItem("token"); + router.replace("/login"); + } else { + setUserName(data.user.userName); + setNickName(data.user.nickName); + } + }) + .catch(() => { + setResCode(500); + setAppUnavailable(true); + }); } } - setInitialized(true) + setInitialized(true); } - }, []) + }, []); - if (!initialized) - return null + if (!initialized) return null; - if (appUnavailable) - return + if (appUnavailable) return ; return ( = ({ className={className} userStr={`${nickName}[${userName}]`} /> - ) -} + ); +}; -export default ChatWithHistoryWrapWithCheckToken +export default ChatWithHistoryWrapWithCheckToken; From 374f5171cf0366ae302ec083d8914d0542919737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=85=83=E5=9D=A4?= Date: Thu, 10 Jul 2025 09:37:12 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=8F=8D=E9=A6=88=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/chat-with-history/hooks.tsx | 784 +++++++++++++-------- 1 file changed, 489 insertions(+), 295 deletions(-) diff --git a/app/components/chat-with-history/hooks.tsx b/app/components/chat-with-history/hooks.tsx index 2d18f0f..d2df323 100644 --- a/app/components/chat-with-history/hooks.tsx +++ b/app/components/chat-with-history/hooks.tsx @@ -1,25 +1,15 @@ -import { - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react' -import { useTranslation } from 'react-i18next' -import useSWR from 'swr' -import { useLocalStorageState } from 'ahooks' -import { produce } from 'immer' -import type { - Callback, - ChatConfig, - ChatItem, - Feedback, -} from '../types' -import { CONVERSATION_ID_INFO } from '../constants' -import { buildChatItemTree } from '../utils' -import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils' -import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' -// TODO mars +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import useSWR from "swr"; +import { useLocalStorageState } from "ahooks"; +import { produce } from "immer"; +import { message } from "antd"; +import { client, getInfo } from "@/app/api/utils/common"; +import type { Callback, ChatConfig, ChatItem, Feedback } from "../types"; +import { CONVERSATION_ID_INFO } from "../constants"; +import { buildChatItemTree } from "../utils"; +import { addFileInfos, sortAgentSorts } from "@/app/components/tools/utils"; +import { getProcessedFilesFromResponse } from "@/app/components/base/file-uploader/utils"; import { delConversation, fetchAppInfo, @@ -32,50 +22,66 @@ import { renameConversation, unpinConversation, updateFeedback, -} from '@/service/index' -import type { InstalledApp } from '@/models/explore' -import type { - AppData, - ConversationItem, -} from '@/models/share' -import { useToastContext } from '@/app/components/base/toast' -import { changeLanguage } from '@/i18n/i18next-config' -import { useAppFavicon } from '@/hooks/use-app-favicon' -import { InputVarType } from '@/app/components/workflow/types' -import { TransferMethod } from '@/types/app' +} from "@/service/index"; +import type { InstalledApp } from "@/models/explore"; +import type { AppData, ConversationItem } from "@/models/share"; +import { useToastContext } from "@/app/components/base/toast"; +import { changeLanguage } from "@/i18n/i18next-config"; +import { useAppFavicon } from "@/hooks/use-app-favicon"; +import { InputVarType } from "@/app/components/workflow/types"; +import { TransferMethod } from "@/types/app"; +import FeedbackModal from "@/app/components/chat-with-history/FeedbackModal"; // 引入 FeedbackModal 组件 function getFormattedChatList(messages: any[]) { - const newChatList: ChatItem[] = [] + const newChatList: ChatItem[] = []; messages.forEach((item) => { - const questionFiles = item.message_files?.filter((file: any) => file.belongs_to === 'user') || [] + const questionFiles = + item.message_files?.filter((file: any) => file.belongs_to === "user") || + []; newChatList.push({ id: `question-${item.id}`, content: item.query, isAnswer: false, - message_files: getProcessedFilesFromResponse(questionFiles.map((item: any) => ({ ...item, related_id: item.id }))), + message_files: getProcessedFilesFromResponse( + questionFiles.map((item: any) => ({ ...item, related_id: item.id })) + ), parentMessageId: item.parent_message_id || undefined, - }) - const answerFiles = item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [] + }); + const answerFiles = + item.message_files?.filter( + (file: any) => file.belongs_to === "assistant" + ) || []; if (answerFiles.length > 0 && questionFiles.length > 0) // 获取本地文件名 - answerFiles[0].filename = questionFiles[0]?.filename + answerFiles[0].filename = questionFiles[0]?.filename; newChatList.push({ id: item.id, content: item.answer, - agent_thoughts: addFileInfos(item.agent_thoughts ? sortAgentSorts(item.agent_thoughts) : item.agent_thoughts, item.message_files), + agent_thoughts: addFileInfos( + item.agent_thoughts + ? sortAgentSorts(item.agent_thoughts) + : item.agent_thoughts, + item.message_files + ), feedback: item.feedback, isAnswer: true, citation: item.retriever_resources, - message_files: getProcessedFilesFromResponse(answerFiles.map((item: any) => ({ ...item, related_id: item.id }))), + message_files: getProcessedFilesFromResponse( + answerFiles.map((item: any) => ({ ...item, related_id: item.id })) + ), parentMessageId: `question-${item.id}`, - }) - }) - return newChatList + }); + }); + return newChatList; } export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { - const isInstalledApp = useMemo(() => !!installedAppInfo, [installedAppInfo]) - const { data: appInfo, isLoading: appInfoLoading, error: appInfoError } = useSWR(installedAppInfo ? null : 'appInfo', fetchAppInfo) + const isInstalledApp = useMemo(() => !!installedAppInfo, [installedAppInfo]); + const { + data: appInfo, + isLoading: appInfoLoading, + error: appInfoError, + } = useSWR(installedAppInfo ? null : "appInfo", fetchAppInfo); useAppFavicon({ enable: !installedAppInfo, @@ -83,11 +89,11 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { icon: appInfo?.site?.icon, icon_background: appInfo?.site?.icon_background, icon_url: appInfo?.site?.icon_url, - }) + }); const appData = useMemo(() => { if (isInstalledApp) { - const { id, app } = installedAppInfo! + const { id, app } = installedAppInfo!; return { app_id: id, site: { @@ -97,326 +103,507 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { icon_background: app.icon_background, icon_url: app.icon_url, prompt_public: false, - copyright: '', + copyright: "", show_workflow_steps: true, use_icon_as_answer_icon: app.use_icon_as_answer_icon, }, - plan: 'basic', - } as AppData + plan: "basic", + } as AppData; } - return appInfo - }, [isInstalledApp, installedAppInfo, appInfo]) - const appId = useMemo(() => appData?.app_id, [appData]) + return appInfo; + }, [isInstalledApp, installedAppInfo, appInfo]); + const appId = useMemo(() => appData?.app_id, [appData]); useEffect(() => { if (appData?.site?.default_language) - changeLanguage(appData.site?.default_language) - }, [appData]) + changeLanguage(appData.site?.default_language); + }, [appData]); - const [conversationIdInfo, setConversationIdInfo] = useLocalStorageState>(CONVERSATION_ID_INFO, { + const [conversationIdInfo, setConversationIdInfo] = useLocalStorageState< + Record + >(CONVERSATION_ID_INFO, { defaultValue: {}, - }) - const currentConversationId = useMemo(() => conversationIdInfo?.[appId || ''] || '', [appId, conversationIdInfo]) - const handleConversationIdInfoChange = useCallback((changeConversationId: string) => { - if (appId) { - setConversationIdInfo({ - ...conversationIdInfo, - [appId || '']: changeConversationId, - }) - } - }, [appId, conversationIdInfo, setConversationIdInfo]) - const [showConfigPanelBeforeChat, setShowConfigPanelBeforeChat] = useState(true) - const [newConversationId, setNewConversationId] = useState('') + }); + const currentConversationId = useMemo( + () => conversationIdInfo?.[appId || ""] || "", + [appId, conversationIdInfo] + ); + const handleConversationIdInfoChange = useCallback( + (changeConversationId: string) => { + if (appId) { + setConversationIdInfo({ + ...conversationIdInfo, + [appId || ""]: changeConversationId, + }); + } + }, + [appId, conversationIdInfo, setConversationIdInfo] + ); + const [showConfigPanelBeforeChat, setShowConfigPanelBeforeChat] = + useState(true); + const [newConversationId, setNewConversationId] = useState(""); const chatShouldReloadKey = useMemo(() => { - if (currentConversationId === newConversationId) - return '' + if (currentConversationId === newConversationId) return ""; - return currentConversationId - }, [currentConversationId, newConversationId]) - const { data: appParams } = useSWR(['appParams', isInstalledApp, appId], () => fetchAppParams()) - const { data: appMeta } = useSWR(['appMeta', isInstalledApp, appId], () => fetchAppMeta()) - const { data: appPinnedConversationData, mutate: mutateAppPinnedConversationData } = useSWR(['appConversationData', isInstalledApp, appId, true], () => fetchConversations()) - const { data: appConversationData, isLoading: appConversationDataLoading, mutate: mutateAppConversationData } = useSWR(['appConversationData', isInstalledApp, appId, false], () => fetchConversations()) - const { data: appChatListData, isLoading: appChatListDataLoading } = useSWR(chatShouldReloadKey ? ['appChatList', chatShouldReloadKey, isInstalledApp, appId] : null, () => fetchChatList(chatShouldReloadKey)) + return currentConversationId; + }, [currentConversationId, newConversationId]); + const { data: appParams } = useSWR(["appParams", isInstalledApp, appId], () => + fetchAppParams() + ); + const { data: appMeta } = useSWR(["appMeta", isInstalledApp, appId], () => + fetchAppMeta() + ); + const { + data: appPinnedConversationData, + mutate: mutateAppPinnedConversationData, + } = useSWR(["appConversationData", isInstalledApp, appId, true], () => + fetchConversations() + ); + const { + data: appConversationData, + isLoading: appConversationDataLoading, + mutate: mutateAppConversationData, + } = useSWR(["appConversationData", isInstalledApp, appId, false], () => + fetchConversations() + ); + const { data: appChatListData, isLoading: appChatListDataLoading } = useSWR( + chatShouldReloadKey + ? ["appChatList", chatShouldReloadKey, isInstalledApp, appId] + : null, + () => fetchChatList(chatShouldReloadKey) + ); const appPrevChatTree = useMemo( - () => (currentConversationId && appChatListData?.data.length) - ? buildChatItemTree(getFormattedChatList(appChatListData.data)) - : [], - [appChatListData, currentConversationId], - ) + () => + currentConversationId && appChatListData?.data.length + ? buildChatItemTree(getFormattedChatList(appChatListData.data)) + : [], + [appChatListData, currentConversationId] + ); - const [showNewConversationItemInList, setShowNewConversationItemInList] = useState(false) + const [showNewConversationItemInList, setShowNewConversationItemInList] = + useState(false); const pinnedConversationList = useMemo(() => { - return appPinnedConversationData?.data || [] - }, [appPinnedConversationData]) - const { t } = useTranslation() - const newConversationInputsRef = useRef>({}) - const [newConversationInputs, setNewConversationInputs] = useState>({}) - const handleNewConversationInputsChange = useCallback((newInputs: Record) => { - newConversationInputsRef.current = newInputs - setNewConversationInputs(newInputs) - }, []) + return appPinnedConversationData?.data || []; + }, [appPinnedConversationData]); + const { t } = useTranslation(); + const newConversationInputsRef = useRef>({}); + const [newConversationInputs, setNewConversationInputs] = useState< + Record + >({}); + const handleNewConversationInputsChange = useCallback( + (newInputs: Record) => { + newConversationInputsRef.current = newInputs; + setNewConversationInputs(newInputs); + }, + [] + ); const inputsForms = useMemo(() => { - return (appParams?.user_input_form || []).filter((item: any) => !item.external_data_tool).map((item: any) => { - if (item.paragraph) { - return { - ...item.paragraph, - type: 'paragraph', + return (appParams?.user_input_form || []) + .filter((item: any) => !item.external_data_tool) + .map((item: any) => { + if (item.paragraph) { + return { + ...item.paragraph, + type: "paragraph", + }; } - } - if (item.number) { - return { - ...item.number, - type: 'number', + if (item.number) { + return { + ...item.number, + type: "number", + }; } - } - if (item.select) { - return { - ...item.select, - type: 'select', + if (item.select) { + return { + ...item.select, + type: "select", + }; } - } - if (item['file-list']) { - return { - ...item['file-list'], - type: 'file-list', + if (item["file-list"]) { + return { + ...item["file-list"], + type: "file-list", + }; } - } - if (item.file) { - return { - ...item.file, - type: 'file', + if (item.file) { + return { + ...item.file, + type: "file", + }; } - } - return { - ...item['text-input'], - type: 'text-input', - } - }) - }, [appParams]) + return { + ...item["text-input"], + type: "text-input", + }; + }); + }, [appParams]); useEffect(() => { - const conversationInputs: Record = {} + const conversationInputs: Record = {}; inputsForms.forEach((item: any) => { - conversationInputs[item.variable] = item.default || null - }) - handleNewConversationInputsChange(conversationInputs) - }, [handleNewConversationInputsChange, inputsForms]) + conversationInputs[item.variable] = item.default || null; + }); + handleNewConversationInputsChange(conversationInputs); + }, [handleNewConversationInputsChange, inputsForms]); - const { data: newConversation } = useSWR(newConversationId ? [isInstalledApp, appId, newConversationId] : null, () => generationConversationName(newConversationId)) - const [originConversationList, setOriginConversationList] = useState([]) + const { data: newConversation } = useSWR( + newConversationId ? [isInstalledApp, appId, newConversationId] : null, + () => generationConversationName(newConversationId) + ); + const [originConversationList, setOriginConversationList] = useState< + ConversationItem[] + >([]); useEffect(() => { if (appConversationData?.data && !appConversationDataLoading) - setOriginConversationList(appConversationData?.data) - }, [appConversationData, appConversationDataLoading]) + setOriginConversationList(appConversationData?.data); + }, [appConversationData, appConversationDataLoading]); const conversationList = useMemo(() => { - const data = originConversationList.slice() + const data = originConversationList.slice(); - if (showNewConversationItemInList && data[0]?.id !== '') { + if (showNewConversationItemInList && data[0]?.id !== "") { data.unshift({ - id: '', - name: t('share.chat.newChatDefaultName'), + id: "", + name: t("share.chat.newChatDefaultName"), inputs: {}, - introduction: '', - }) + introduction: "", + }); } - return data - }, [originConversationList, showNewConversationItemInList, t]) + return data; + }, [originConversationList, showNewConversationItemInList, t]); useEffect(() => { if (newConversation) { - setOriginConversationList(produce((draft) => { - const index = draft.findIndex(item => item.id === newConversation.id) + setOriginConversationList( + produce((draft) => { + const index = draft.findIndex( + (item) => item.id === newConversation.id + ); - if (index > -1) - draft[index] = newConversation - else - draft.unshift(newConversation) - })) + if (index > -1) draft[index] = newConversation; + else draft.unshift(newConversation); + }) + ); } - }, [newConversation]) + }, [newConversation]); const currentConversationItem = useMemo(() => { - let conversationItem = conversationList.find(item => item.id === currentConversationId) + let conversationItem = conversationList.find( + (item) => item.id === currentConversationId + ); if (!conversationItem && pinnedConversationList.length) - conversationItem = pinnedConversationList.find(item => item.id === currentConversationId) + conversationItem = pinnedConversationList.find( + (item) => item.id === currentConversationId + ); - return conversationItem - }, [conversationList, currentConversationId, pinnedConversationList]) + return conversationItem; + }, [conversationList, currentConversationId, pinnedConversationList]); - const { notify } = useToastContext() - const checkInputsRequired = useCallback((silent?: boolean) => { - let hasEmptyInput = '' - let fileIsUploading = false - const requiredVars = inputsForms.filter(({ required }) => required) - if (requiredVars.length) { - requiredVars.forEach(({ variable, label, type }) => { - if (hasEmptyInput) - return + const { notify } = useToastContext(); + const checkInputsRequired = useCallback( + (silent?: boolean) => { + let hasEmptyInput = ""; + let fileIsUploading = false; + const requiredVars = inputsForms.filter(({ required }) => required); + if (requiredVars.length) { + requiredVars.forEach(({ variable, label, type }) => { + if (hasEmptyInput) return; - if (fileIsUploading) - return + if (fileIsUploading) return; - if (!newConversationInputsRef.current[variable] && !silent) - hasEmptyInput = label as string + if (!newConversationInputsRef.current[variable] && !silent) + hasEmptyInput = label as string; - if ((type === InputVarType.singleFile || type === InputVarType.multiFiles) && newConversationInputsRef.current[variable] && !silent) { - const files = newConversationInputsRef.current[variable] - if (Array.isArray(files)) - fileIsUploading = files.find(item => item.transferMethod === TransferMethod.local_file && !item.uploadedId) - else - fileIsUploading = files.transferMethod === TransferMethod.local_file && !files.uploadedId - } - }) - } + if ( + (type === InputVarType.singleFile || + type === InputVarType.multiFiles) && + newConversationInputsRef.current[variable] && + !silent + ) { + const files = newConversationInputsRef.current[variable]; + if (Array.isArray(files)) + fileIsUploading = files.find( + (item) => + item.transferMethod === TransferMethod.local_file && + !item.uploadedId + ); + else + fileIsUploading = + files.transferMethod === TransferMethod.local_file && + !files.uploadedId; + } + }); + } - if (hasEmptyInput) { - notify({ type: 'error', message: t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }) }) - return false - } + if (hasEmptyInput) { + notify({ + type: "error", + message: t("appDebug.errorMessage.valueOfVarRequired", { + key: hasEmptyInput, + }), + }); + return false; + } - if (fileIsUploading) { - notify({ type: 'info', message: t('appDebug.errorMessage.waitForFileUpload') }) - return - } + if (fileIsUploading) { + notify({ + type: "info", + message: t("appDebug.errorMessage.waitForFileUpload"), + }); + return; + } - return true - }, [inputsForms, notify, t]) + return true; + }, + [inputsForms, notify, t] + ); const handleStartChat = useCallback(() => { if (checkInputsRequired()) { - setShowConfigPanelBeforeChat(false) - setShowNewConversationItemInList(true) + setShowConfigPanelBeforeChat(false); + setShowNewConversationItemInList(true); } - }, [setShowConfigPanelBeforeChat, setShowNewConversationItemInList, checkInputsRequired]) - const currentChatInstanceRef = useRef<{ handleStop: () => void }>({ handleStop: () => { } }) - const handleChangeConversation = useCallback((conversationId: string) => { - currentChatInstanceRef.current.handleStop() - setNewConversationId('') - handleConversationIdInfoChange(conversationId) + }, [ + setShowConfigPanelBeforeChat, + setShowNewConversationItemInList, + checkInputsRequired, + ]); + const currentChatInstanceRef = useRef<{ handleStop: () => void }>({ + handleStop: () => {}, + }); + const handleChangeConversation = useCallback( + (conversationId: string) => { + currentChatInstanceRef.current.handleStop(); + setNewConversationId(""); + handleConversationIdInfoChange(conversationId); - if (conversationId === '' && !checkInputsRequired(true)) - setShowConfigPanelBeforeChat(true) - else - setShowConfigPanelBeforeChat(false) - }, [handleConversationIdInfoChange, setShowConfigPanelBeforeChat, checkInputsRequired]) + if (conversationId === "" && !checkInputsRequired(true)) + setShowConfigPanelBeforeChat(true); + else setShowConfigPanelBeforeChat(false); + }, + [ + handleConversationIdInfoChange, + setShowConfigPanelBeforeChat, + checkInputsRequired, + ] + ); const handleNewConversation = useCallback(() => { - currentChatInstanceRef.current.handleStop() - setNewConversationId('') + currentChatInstanceRef.current.handleStop(); + setNewConversationId(""); if (showNewConversationItemInList) { - handleChangeConversation('') + handleChangeConversation(""); + } else if (currentConversationId) { + handleConversationIdInfoChange(""); + setShowConfigPanelBeforeChat(true); + setShowNewConversationItemInList(true); + handleNewConversationInputsChange({}); } - else if (currentConversationId) { - handleConversationIdInfoChange('') - setShowConfigPanelBeforeChat(true) - setShowNewConversationItemInList(true) - handleNewConversationInputsChange({}) - } - }, [handleChangeConversation, currentConversationId, handleConversationIdInfoChange, setShowConfigPanelBeforeChat, setShowNewConversationItemInList, showNewConversationItemInList, handleNewConversationInputsChange]) + }, [ + handleChangeConversation, + currentConversationId, + handleConversationIdInfoChange, + setShowConfigPanelBeforeChat, + setShowNewConversationItemInList, + showNewConversationItemInList, + handleNewConversationInputsChange, + ]); const handleUpdateConversationList = useCallback(() => { - mutateAppConversationData() - mutateAppPinnedConversationData() - }, [mutateAppConversationData, mutateAppPinnedConversationData]) + mutateAppConversationData(); + mutateAppPinnedConversationData(); + }, [mutateAppConversationData, mutateAppPinnedConversationData]); - const handlePinConversation = useCallback(async (conversationId: string) => { - await pinConversation(isInstalledApp, appId, conversationId) - notify({ type: 'success', message: t('common.api.success') }) - handleUpdateConversationList() - }, [isInstalledApp, appId, notify, t, handleUpdateConversationList]) + const handlePinConversation = useCallback( + async (conversationId: string) => { + await pinConversation(isInstalledApp, appId, conversationId); + notify({ type: "success", message: t("common.api.success") }); + handleUpdateConversationList(); + }, + [isInstalledApp, appId, notify, t, handleUpdateConversationList] + ); - const handleUnpinConversation = useCallback(async (conversationId: string) => { - await unpinConversation(isInstalledApp, appId, conversationId) - notify({ type: 'success', message: t('common.api.success') }) - handleUpdateConversationList() - }, [isInstalledApp, appId, notify, t, handleUpdateConversationList]) + const handleUnpinConversation = useCallback( + async (conversationId: string) => { + await unpinConversation(isInstalledApp, appId, conversationId); + notify({ type: "success", message: t("common.api.success") }); + handleUpdateConversationList(); + }, + [isInstalledApp, appId, notify, t, handleUpdateConversationList] + ); - const [conversationDeleting, setConversationDeleting] = useState(false) - const handleDeleteConversation = useCallback(async ( - conversationId: string, - { - onSuccess, - }: Callback, - ) => { - if (conversationDeleting) - return + const [conversationDeleting, setConversationDeleting] = useState(false); + const handleDeleteConversation = useCallback( + async (conversationId: string, { onSuccess }: Callback) => { + if (conversationDeleting) return; - try { - setConversationDeleting(true) - await delConversation(conversationId) - notify({ type: 'success', message: t('common.api.success') }) - onSuccess() - } - finally { - setConversationDeleting(false) - } + try { + setConversationDeleting(true); + await delConversation(conversationId); + notify({ type: "success", message: t("common.api.success") }); + onSuccess(); + } finally { + setConversationDeleting(false); + } - if (conversationId === currentConversationId) - handleNewConversation() + if (conversationId === currentConversationId) handleNewConversation(); - handleUpdateConversationList() - }, [isInstalledApp, appId, notify, t, handleUpdateConversationList, handleNewConversation, currentConversationId, conversationDeleting]) + handleUpdateConversationList(); + }, + [ + isInstalledApp, + appId, + notify, + t, + handleUpdateConversationList, + handleNewConversation, + currentConversationId, + conversationDeleting, + ] + ); - const [conversationRenaming, setConversationRenaming] = useState(false) - const handleRenameConversation = useCallback(async ( - conversationId: string, - newName: string, - { - onSuccess, - }: Callback, - ) => { - if (conversationRenaming) - return + const [conversationRenaming, setConversationRenaming] = useState(false); + const handleRenameConversation = useCallback( + async ( + conversationId: string, + newName: string, + { onSuccess }: Callback + ) => { + if (conversationRenaming) return; - if (!newName.trim()) { - notify({ - type: 'error', - message: t('common.chat.conversationNameCanNotEmpty'), - }) - return - } + if (!newName.trim()) { + notify({ + type: "error", + message: t("common.chat.conversationNameCanNotEmpty"), + }); + return; + } - setConversationRenaming(true) - try { - await renameConversation(conversationId, newName) + setConversationRenaming(true); + try { + await renameConversation(conversationId, newName); - notify({ - type: 'success', - message: t('common.actionMsg.modifiedSuccessfully'), - }) - setOriginConversationList(produce((draft) => { - const index = originConversationList.findIndex(item => item.id === conversationId) - const item = draft[index] + notify({ + type: "success", + message: t("common.actionMsg.modifiedSuccessfully"), + }); + setOriginConversationList( + produce((draft) => { + const index = originConversationList.findIndex( + (item) => item.id === conversationId + ); + const item = draft[index]; - draft[index] = { - ...item, - name: newName, - } - })) - onSuccess() - } - finally { - setConversationRenaming(false) - } - }, [isInstalledApp, appId, notify, t, conversationRenaming, originConversationList]) + draft[index] = { + ...item, + name: newName, + }; + }) + ); + onSuccess(); + } finally { + setConversationRenaming(false); + } + }, + [ + isInstalledApp, + appId, + notify, + t, + conversationRenaming, + originConversationList, + ] + ); - const handleNewConversationCompleted = useCallback((newConversationId: string) => { - setNewConversationId(newConversationId) - handleConversationIdInfoChange(newConversationId) - setShowNewConversationItemInList(false) - mutateAppConversationData() - }, [mutateAppConversationData, handleConversationIdInfoChange]) + const handleNewConversationCompleted = useCallback( + (newConversationId: string) => { + setNewConversationId(newConversationId); + handleConversationIdInfoChange(newConversationId); + setShowNewConversationItemInList(false); + mutateAppConversationData(); + }, + [mutateAppConversationData, handleConversationIdInfoChange] + ); - const handleFeedback = useCallback(async (messageId: string, feedback: Feedback) => { - await updateFeedback({ url: `/messages/${messageId}/feedbacks`, body: { rating: feedback.rating } }, isInstalledApp, appId) - notify({ type: 'success', message: t('common.api.success') }) - }, [isInstalledApp, appId, t, notify]) + const [isFeedbackModalVisible, setIsFeedbackModalVisible] = useState(false); + const [currentMessageId, setCurrentMessageId] = useState(null); + const [currentFeedback, setCurrentFeedback] = useState(null); + const [ + currentConversationIdForFeedback, + setCurrentConversationIdForFeedback, + ] = useState(null); + + const handleFeedback = useCallback( + (messageId: string, feedback: Feedback, conversationId: string) => { + setCurrentMessageId(messageId); + setCurrentFeedback(feedback); + setCurrentConversationIdForFeedback(conversationId); + setIsFeedbackModalVisible(true); + }, + [] + ); + + const handleFeedbackSubmit = useCallback( + async (selectedOption: number | null, feedbackText: string) => { + console.log(currentMessageId); + console.log(currentFeedback); + + try { + // 构造请求参数 + const requestBody = { + operationType: selectedOption, // 0 或 1 + feedbackText, // 用户反馈建议 + conversationId: '89fba8a7-e4e2-4167-8b7c-b3c103797053', // 会话 ID + messageId: currentMessageId, // 消息 ID + username: "user_admin", // 用户名 + }; + + // 调用 Java 接口 + const javaResponse = await fetch( + `${process.env.NEXT_PUBLIC_BASE_API_URL}/api/conversation/feedback`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(requestBody), + } + ); + + // 调用原有的 updateFeedback 函数 + await updateFeedback( + { + url: `/messages/${currentMessageId}/feedbacks`, + body: { rating: currentFeedback.rating }, + }, + isInstalledApp, + appId + ); + + // 显示成功通知 + notify({ type: "success", message: t("common.api.success") }); + + // 关闭对话框 + setIsFeedbackModalVisible(false); + } catch (error) { + console.error("Error:", error); + notify({ type: "error", message: t("common.api.failed") }); + } + }, + [ + currentMessageId, + currentFeedback, + currentConversationIdForFeedback, + isInstalledApp, + appId, + notify, + t, + ] + ); return { appInfoError, @@ -427,7 +614,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { currentConversationItem, handleConversationIdInfoChange, appData, - appParams: appParams || {} as ChatConfig, + appParams: appParams || ({} as ChatConfig), appMeta, appPinnedConversationData, appConversationData, @@ -458,5 +645,12 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { chatShouldReloadKey, handleFeedback, currentChatInstanceRef, - } -} + feedbackModal: ( + setIsFeedbackModalVisible(false)} + /> + ), + }; +}; From 54ae9e9961fcb2b274dfdd45220d0b55185a3598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=85=83=E5=9D=A4?= Date: Thu, 10 Jul 2025 09:38:32 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=B0=E6=96=87?= =?UTF-8?q?=E4=BB=B6=EF=BC=9A=E7=94=A8=E6=88=B7=E5=8F=8D=E9=A6=88=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat-with-history/FeedbackModal.tsx | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 app/components/chat-with-history/FeedbackModal.tsx diff --git a/app/components/chat-with-history/FeedbackModal.tsx b/app/components/chat-with-history/FeedbackModal.tsx new file mode 100644 index 0000000..979bb2f --- /dev/null +++ b/app/components/chat-with-history/FeedbackModal.tsx @@ -0,0 +1,55 @@ +import React, { useState } from 'react'; +import { Modal, Checkbox, Input, message } from 'antd'; + +const { TextArea } = Input; + +interface FeedbackModalProps { + open: boolean; + onOk: (selectedOption: number | null, feedbackText: string) => void; + onCancel: () => void; +} + +const FeedbackModal: React.FC = ({ open, onOk, onCancel }) => { + const [selectedOption, setSelectedOption] = useState(null); + const [feedbackText, setFeedbackText] = useState(''); + + const handleOk = () => { + if (selectedOption === null || !feedbackText) { + message.warning('请选择操作类型并填写反馈建议'); + return; + } + onOk(selectedOption, feedbackText); + }; + + return ( + +
+ setSelectedOption(0)} + > + 新增 + + setSelectedOption(1)} + > + 修改 + +
+