Merge branch 'zhangyuankun-main-patch-48108' into 'zhangyuankun-main-patch-41282'
Zhangyuankun main patch 48108 See merge request line-group/dify-conversation!1
This commit is contained in:
commit
fe1b17c408
@ -6,7 +6,7 @@ import { API_KEY, API_URL } from '@/config'
|
|||||||
export const getInfo = (request: NextRequest) => {
|
export const getInfo = (request: NextRequest) => {
|
||||||
const username = request.cookies.get('username')?.value || 'no-user'
|
const username = request.cookies.get('username')?.value || 'no-user'
|
||||||
const sessionId = request.cookies.get('session_id')?.value || v4()
|
const sessionId = request.cookies.get('session_id')?.value || v4()
|
||||||
const user = `user_${username}:${sessionId}`
|
const user = `${username}`
|
||||||
return {
|
return {
|
||||||
sessionId,
|
sessionId,
|
||||||
user,
|
user,
|
||||||
|
|||||||
55
app/components/chat-with-history/FeedbackModal.tsx
Normal file
55
app/components/chat-with-history/FeedbackModal.tsx
Normal file
@ -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<FeedbackModalProps> = ({ open, onOk, onCancel }) => {
|
||||||
|
const [selectedOption, setSelectedOption] = useState<number | null>(null);
|
||||||
|
const [feedbackText, setFeedbackText] = useState<string>('');
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
if (selectedOption === null || !feedbackText) {
|
||||||
|
message.warning('请选择操作类型并填写反馈建议');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onOk(selectedOption, feedbackText);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title="反馈"
|
||||||
|
open={open}
|
||||||
|
onOk={handleOk}
|
||||||
|
onCancel={onCancel}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedOption === 0}
|
||||||
|
onChange={() => setSelectedOption(0)}
|
||||||
|
>
|
||||||
|
新增
|
||||||
|
</Checkbox>
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedOption === 1}
|
||||||
|
onChange={() => setSelectedOption(1)}
|
||||||
|
>
|
||||||
|
修改
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
<TextArea
|
||||||
|
rows={4}
|
||||||
|
placeholder="请输入您的反馈建议"
|
||||||
|
value={feedbackText}
|
||||||
|
onChange={(e) => setFeedbackText(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FeedbackModal;
|
||||||
@ -1,25 +1,15 @@
|
|||||||
import {
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
useCallback,
|
import { useTranslation } from "react-i18next";
|
||||||
useEffect,
|
import useSWR from "swr";
|
||||||
useMemo,
|
import { useLocalStorageState } from "ahooks";
|
||||||
useRef,
|
import { produce } from "immer";
|
||||||
useState,
|
import { message } from "antd";
|
||||||
} from 'react'
|
import { client, getInfo } from "@/app/api/utils/common";
|
||||||
import { useTranslation } from 'react-i18next'
|
import type { Callback, ChatConfig, ChatItem, Feedback } from "../types";
|
||||||
import useSWR from 'swr'
|
import { CONVERSATION_ID_INFO } from "../constants";
|
||||||
import { useLocalStorageState } from 'ahooks'
|
import { buildChatItemTree } from "../utils";
|
||||||
import { produce } from 'immer'
|
import { addFileInfos, sortAgentSorts } from "@/app/components/tools/utils";
|
||||||
import type {
|
import { getProcessedFilesFromResponse } from "@/app/components/base/file-uploader/utils";
|
||||||
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 {
|
import {
|
||||||
delConversation,
|
delConversation,
|
||||||
fetchAppInfo,
|
fetchAppInfo,
|
||||||
@ -32,50 +22,66 @@ import {
|
|||||||
renameConversation,
|
renameConversation,
|
||||||
unpinConversation,
|
unpinConversation,
|
||||||
updateFeedback,
|
updateFeedback,
|
||||||
} from '@/service/index'
|
} from "@/service/index";
|
||||||
import type { InstalledApp } from '@/models/explore'
|
import type { InstalledApp } from "@/models/explore";
|
||||||
import type {
|
import type { AppData, ConversationItem } from "@/models/share";
|
||||||
AppData,
|
import { useToastContext } from "@/app/components/base/toast";
|
||||||
ConversationItem,
|
import { changeLanguage } from "@/i18n/i18next-config";
|
||||||
} from '@/models/share'
|
import { useAppFavicon } from "@/hooks/use-app-favicon";
|
||||||
import { useToastContext } from '@/app/components/base/toast'
|
import { InputVarType } from "@/app/components/workflow/types";
|
||||||
import { changeLanguage } from '@/i18n/i18next-config'
|
import { TransferMethod } from "@/types/app";
|
||||||
import { useAppFavicon } from '@/hooks/use-app-favicon'
|
import FeedbackModal from "@/app/components/chat-with-history/FeedbackModal"; // 引入 FeedbackModal 组件
|
||||||
import { InputVarType } from '@/app/components/workflow/types'
|
|
||||||
import { TransferMethod } from '@/types/app'
|
|
||||||
|
|
||||||
function getFormattedChatList(messages: any[]) {
|
function getFormattedChatList(messages: any[]) {
|
||||||
const newChatList: ChatItem[] = []
|
const newChatList: ChatItem[] = [];
|
||||||
messages.forEach((item) => {
|
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({
|
newChatList.push({
|
||||||
id: `question-${item.id}`,
|
id: `question-${item.id}`,
|
||||||
content: item.query,
|
content: item.query,
|
||||||
isAnswer: false,
|
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,
|
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)
|
if (answerFiles.length > 0 && questionFiles.length > 0)
|
||||||
// 获取本地文件名
|
// 获取本地文件名
|
||||||
answerFiles[0].filename = questionFiles[0]?.filename
|
answerFiles[0].filename = questionFiles[0]?.filename;
|
||||||
newChatList.push({
|
newChatList.push({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
content: item.answer,
|
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,
|
feedback: item.feedback,
|
||||||
isAnswer: true,
|
isAnswer: true,
|
||||||
citation: item.retriever_resources,
|
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}`,
|
parentMessageId: `question-${item.id}`,
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
return newChatList
|
return newChatList;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
||||||
const isInstalledApp = useMemo(() => !!installedAppInfo, [installedAppInfo])
|
const isInstalledApp = useMemo(() => !!installedAppInfo, [installedAppInfo]);
|
||||||
const { data: appInfo, isLoading: appInfoLoading, error: appInfoError } = useSWR(installedAppInfo ? null : 'appInfo', fetchAppInfo)
|
const {
|
||||||
|
data: appInfo,
|
||||||
|
isLoading: appInfoLoading,
|
||||||
|
error: appInfoError,
|
||||||
|
} = useSWR(installedAppInfo ? null : "appInfo", fetchAppInfo);
|
||||||
|
|
||||||
useAppFavicon({
|
useAppFavicon({
|
||||||
enable: !installedAppInfo,
|
enable: !installedAppInfo,
|
||||||
@ -83,11 +89,11 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
|||||||
icon: appInfo?.site?.icon,
|
icon: appInfo?.site?.icon,
|
||||||
icon_background: appInfo?.site?.icon_background,
|
icon_background: appInfo?.site?.icon_background,
|
||||||
icon_url: appInfo?.site?.icon_url,
|
icon_url: appInfo?.site?.icon_url,
|
||||||
})
|
});
|
||||||
|
|
||||||
const appData = useMemo(() => {
|
const appData = useMemo(() => {
|
||||||
if (isInstalledApp) {
|
if (isInstalledApp) {
|
||||||
const { id, app } = installedAppInfo!
|
const { id, app } = installedAppInfo!;
|
||||||
return {
|
return {
|
||||||
app_id: id,
|
app_id: id,
|
||||||
site: {
|
site: {
|
||||||
@ -97,326 +103,507 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
|||||||
icon_background: app.icon_background,
|
icon_background: app.icon_background,
|
||||||
icon_url: app.icon_url,
|
icon_url: app.icon_url,
|
||||||
prompt_public: false,
|
prompt_public: false,
|
||||||
copyright: '',
|
copyright: "",
|
||||||
show_workflow_steps: true,
|
show_workflow_steps: true,
|
||||||
use_icon_as_answer_icon: app.use_icon_as_answer_icon,
|
use_icon_as_answer_icon: app.use_icon_as_answer_icon,
|
||||||
},
|
},
|
||||||
plan: 'basic',
|
plan: "basic",
|
||||||
} as AppData
|
} as AppData;
|
||||||
}
|
}
|
||||||
|
|
||||||
return appInfo
|
return appInfo;
|
||||||
}, [isInstalledApp, installedAppInfo, appInfo])
|
}, [isInstalledApp, installedAppInfo, appInfo]);
|
||||||
const appId = useMemo(() => appData?.app_id, [appData])
|
const appId = useMemo(() => appData?.app_id, [appData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (appData?.site?.default_language)
|
if (appData?.site?.default_language)
|
||||||
changeLanguage(appData.site?.default_language)
|
changeLanguage(appData.site?.default_language);
|
||||||
}, [appData])
|
}, [appData]);
|
||||||
|
|
||||||
const [conversationIdInfo, setConversationIdInfo] = useLocalStorageState<Record<string, string>>(CONVERSATION_ID_INFO, {
|
const [conversationIdInfo, setConversationIdInfo] = useLocalStorageState<
|
||||||
|
Record<string, string>
|
||||||
|
>(CONVERSATION_ID_INFO, {
|
||||||
defaultValue: {},
|
defaultValue: {},
|
||||||
})
|
});
|
||||||
const currentConversationId = useMemo(() => conversationIdInfo?.[appId || ''] || '', [appId, conversationIdInfo])
|
const currentConversationId = useMemo(
|
||||||
const handleConversationIdInfoChange = useCallback((changeConversationId: string) => {
|
() => conversationIdInfo?.[appId || ""] || "",
|
||||||
if (appId) {
|
[appId, conversationIdInfo]
|
||||||
setConversationIdInfo({
|
);
|
||||||
...conversationIdInfo,
|
const handleConversationIdInfoChange = useCallback(
|
||||||
[appId || '']: changeConversationId,
|
(changeConversationId: string) => {
|
||||||
})
|
if (appId) {
|
||||||
}
|
setConversationIdInfo({
|
||||||
}, [appId, conversationIdInfo, setConversationIdInfo])
|
...conversationIdInfo,
|
||||||
const [showConfigPanelBeforeChat, setShowConfigPanelBeforeChat] = useState(true)
|
[appId || ""]: changeConversationId,
|
||||||
const [newConversationId, setNewConversationId] = useState('')
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[appId, conversationIdInfo, setConversationIdInfo]
|
||||||
|
);
|
||||||
|
const [showConfigPanelBeforeChat, setShowConfigPanelBeforeChat] =
|
||||||
|
useState(true);
|
||||||
|
const [newConversationId, setNewConversationId] = useState("");
|
||||||
const chatShouldReloadKey = useMemo(() => {
|
const chatShouldReloadKey = useMemo(() => {
|
||||||
if (currentConversationId === newConversationId)
|
if (currentConversationId === newConversationId) return "";
|
||||||
return ''
|
|
||||||
|
|
||||||
return currentConversationId
|
return currentConversationId;
|
||||||
}, [currentConversationId, newConversationId])
|
}, [currentConversationId, newConversationId]);
|
||||||
const { data: appParams } = useSWR(['appParams', isInstalledApp, appId], () => fetchAppParams())
|
const { data: appParams } = useSWR(["appParams", isInstalledApp, appId], () =>
|
||||||
const { data: appMeta } = useSWR(['appMeta', isInstalledApp, appId], () => fetchAppMeta())
|
fetchAppParams()
|
||||||
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: appMeta } = useSWR(["appMeta", isInstalledApp, appId], () =>
|
||||||
const { data: appChatListData, isLoading: appChatListDataLoading } = useSWR(chatShouldReloadKey ? ['appChatList', chatShouldReloadKey, isInstalledApp, appId] : null, () => fetchChatList(chatShouldReloadKey))
|
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(
|
const appPrevChatTree = useMemo(
|
||||||
() => (currentConversationId && appChatListData?.data.length)
|
() =>
|
||||||
? buildChatItemTree(getFormattedChatList(appChatListData.data))
|
currentConversationId && appChatListData?.data.length
|
||||||
: [],
|
? buildChatItemTree(getFormattedChatList(appChatListData.data))
|
||||||
[appChatListData, currentConversationId],
|
: [],
|
||||||
)
|
[appChatListData, currentConversationId]
|
||||||
|
);
|
||||||
|
|
||||||
const [showNewConversationItemInList, setShowNewConversationItemInList] = useState(false)
|
const [showNewConversationItemInList, setShowNewConversationItemInList] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
const pinnedConversationList = useMemo(() => {
|
const pinnedConversationList = useMemo(() => {
|
||||||
return appPinnedConversationData?.data || []
|
return appPinnedConversationData?.data || [];
|
||||||
}, [appPinnedConversationData])
|
}, [appPinnedConversationData]);
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation();
|
||||||
const newConversationInputsRef = useRef<Record<string, any>>({})
|
const newConversationInputsRef = useRef<Record<string, any>>({});
|
||||||
const [newConversationInputs, setNewConversationInputs] = useState<Record<string, any>>({})
|
const [newConversationInputs, setNewConversationInputs] = useState<
|
||||||
const handleNewConversationInputsChange = useCallback((newInputs: Record<string, any>) => {
|
Record<string, any>
|
||||||
newConversationInputsRef.current = newInputs
|
>({});
|
||||||
setNewConversationInputs(newInputs)
|
const handleNewConversationInputsChange = useCallback(
|
||||||
}, [])
|
(newInputs: Record<string, any>) => {
|
||||||
|
newConversationInputsRef.current = newInputs;
|
||||||
|
setNewConversationInputs(newInputs);
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
const inputsForms = useMemo(() => {
|
const inputsForms = useMemo(() => {
|
||||||
return (appParams?.user_input_form || []).filter((item: any) => !item.external_data_tool).map((item: any) => {
|
return (appParams?.user_input_form || [])
|
||||||
if (item.paragraph) {
|
.filter((item: any) => !item.external_data_tool)
|
||||||
return {
|
.map((item: any) => {
|
||||||
...item.paragraph,
|
if (item.paragraph) {
|
||||||
type: 'paragraph',
|
return {
|
||||||
|
...item.paragraph,
|
||||||
|
type: "paragraph",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
if (item.number) {
|
||||||
if (item.number) {
|
return {
|
||||||
return {
|
...item.number,
|
||||||
...item.number,
|
type: "number",
|
||||||
type: 'number',
|
};
|
||||||
}
|
}
|
||||||
}
|
if (item.select) {
|
||||||
if (item.select) {
|
return {
|
||||||
return {
|
...item.select,
|
||||||
...item.select,
|
type: "select",
|
||||||
type: 'select',
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (item['file-list']) {
|
if (item["file-list"]) {
|
||||||
return {
|
return {
|
||||||
...item['file-list'],
|
...item["file-list"],
|
||||||
type: 'file-list',
|
type: "file-list",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (item.file) {
|
if (item.file) {
|
||||||
return {
|
return {
|
||||||
...item.file,
|
...item.file,
|
||||||
type: 'file',
|
type: "file",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...item['text-input'],
|
...item["text-input"],
|
||||||
type: 'text-input',
|
type: "text-input",
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
}, [appParams])
|
}, [appParams]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const conversationInputs: Record<string, any> = {}
|
const conversationInputs: Record<string, any> = {};
|
||||||
|
|
||||||
inputsForms.forEach((item: any) => {
|
inputsForms.forEach((item: any) => {
|
||||||
conversationInputs[item.variable] = item.default || null
|
conversationInputs[item.variable] = item.default || null;
|
||||||
})
|
});
|
||||||
handleNewConversationInputsChange(conversationInputs)
|
handleNewConversationInputsChange(conversationInputs);
|
||||||
}, [handleNewConversationInputsChange, inputsForms])
|
}, [handleNewConversationInputsChange, inputsForms]);
|
||||||
|
|
||||||
const { data: newConversation } = useSWR(newConversationId ? [isInstalledApp, appId, newConversationId] : null, () => generationConversationName(newConversationId))
|
const { data: newConversation } = useSWR(
|
||||||
const [originConversationList, setOriginConversationList] = useState<ConversationItem[]>([])
|
newConversationId ? [isInstalledApp, appId, newConversationId] : null,
|
||||||
|
() => generationConversationName(newConversationId)
|
||||||
|
);
|
||||||
|
const [originConversationList, setOriginConversationList] = useState<
|
||||||
|
ConversationItem[]
|
||||||
|
>([]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (appConversationData?.data && !appConversationDataLoading)
|
if (appConversationData?.data && !appConversationDataLoading)
|
||||||
setOriginConversationList(appConversationData?.data)
|
setOriginConversationList(appConversationData?.data);
|
||||||
}, [appConversationData, appConversationDataLoading])
|
}, [appConversationData, appConversationDataLoading]);
|
||||||
const conversationList = useMemo(() => {
|
const conversationList = useMemo(() => {
|
||||||
const data = originConversationList.slice()
|
const data = originConversationList.slice();
|
||||||
|
|
||||||
if (showNewConversationItemInList && data[0]?.id !== '') {
|
if (showNewConversationItemInList && data[0]?.id !== "") {
|
||||||
data.unshift({
|
data.unshift({
|
||||||
id: '',
|
id: "",
|
||||||
name: t('share.chat.newChatDefaultName'),
|
name: t("share.chat.newChatDefaultName"),
|
||||||
inputs: {},
|
inputs: {},
|
||||||
introduction: '',
|
introduction: "",
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
return data
|
return data;
|
||||||
}, [originConversationList, showNewConversationItemInList, t])
|
}, [originConversationList, showNewConversationItemInList, t]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (newConversation) {
|
if (newConversation) {
|
||||||
setOriginConversationList(produce((draft) => {
|
setOriginConversationList(
|
||||||
const index = draft.findIndex(item => item.id === newConversation.id)
|
produce((draft) => {
|
||||||
|
const index = draft.findIndex(
|
||||||
|
(item) => item.id === newConversation.id
|
||||||
|
);
|
||||||
|
|
||||||
if (index > -1)
|
if (index > -1) draft[index] = newConversation;
|
||||||
draft[index] = newConversation
|
else draft.unshift(newConversation);
|
||||||
else
|
})
|
||||||
draft.unshift(newConversation)
|
);
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}, [newConversation])
|
}, [newConversation]);
|
||||||
|
|
||||||
const currentConversationItem = useMemo(() => {
|
const currentConversationItem = useMemo(() => {
|
||||||
let conversationItem = conversationList.find(item => item.id === currentConversationId)
|
let conversationItem = conversationList.find(
|
||||||
|
(item) => item.id === currentConversationId
|
||||||
|
);
|
||||||
|
|
||||||
if (!conversationItem && pinnedConversationList.length)
|
if (!conversationItem && pinnedConversationList.length)
|
||||||
conversationItem = pinnedConversationList.find(item => item.id === currentConversationId)
|
conversationItem = pinnedConversationList.find(
|
||||||
|
(item) => item.id === currentConversationId
|
||||||
|
);
|
||||||
|
|
||||||
return conversationItem
|
return conversationItem;
|
||||||
}, [conversationList, currentConversationId, pinnedConversationList])
|
}, [conversationList, currentConversationId, pinnedConversationList]);
|
||||||
|
|
||||||
const { notify } = useToastContext()
|
const { notify } = useToastContext();
|
||||||
const checkInputsRequired = useCallback((silent?: boolean) => {
|
const checkInputsRequired = useCallback(
|
||||||
let hasEmptyInput = ''
|
(silent?: boolean) => {
|
||||||
let fileIsUploading = false
|
let hasEmptyInput = "";
|
||||||
const requiredVars = inputsForms.filter(({ required }) => required)
|
let fileIsUploading = false;
|
||||||
if (requiredVars.length) {
|
const requiredVars = inputsForms.filter(({ required }) => required);
|
||||||
requiredVars.forEach(({ variable, label, type }) => {
|
if (requiredVars.length) {
|
||||||
if (hasEmptyInput)
|
requiredVars.forEach(({ variable, label, type }) => {
|
||||||
return
|
if (hasEmptyInput) return;
|
||||||
|
|
||||||
if (fileIsUploading)
|
if (fileIsUploading) return;
|
||||||
return
|
|
||||||
|
|
||||||
if (!newConversationInputsRef.current[variable] && !silent)
|
if (!newConversationInputsRef.current[variable] && !silent)
|
||||||
hasEmptyInput = label as string
|
hasEmptyInput = label as string;
|
||||||
|
|
||||||
if ((type === InputVarType.singleFile || type === InputVarType.multiFiles) && newConversationInputsRef.current[variable] && !silent) {
|
if (
|
||||||
const files = newConversationInputsRef.current[variable]
|
(type === InputVarType.singleFile ||
|
||||||
if (Array.isArray(files))
|
type === InputVarType.multiFiles) &&
|
||||||
fileIsUploading = files.find(item => item.transferMethod === TransferMethod.local_file && !item.uploadedId)
|
newConversationInputsRef.current[variable] &&
|
||||||
else
|
!silent
|
||||||
fileIsUploading = files.transferMethod === TransferMethod.local_file && !files.uploadedId
|
) {
|
||||||
}
|
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) {
|
if (hasEmptyInput) {
|
||||||
notify({ type: 'error', message: t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }) })
|
notify({
|
||||||
return false
|
type: "error",
|
||||||
}
|
message: t("appDebug.errorMessage.valueOfVarRequired", {
|
||||||
|
key: hasEmptyInput,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (fileIsUploading) {
|
if (fileIsUploading) {
|
||||||
notify({ type: 'info', message: t('appDebug.errorMessage.waitForFileUpload') })
|
notify({
|
||||||
return
|
type: "info",
|
||||||
}
|
message: t("appDebug.errorMessage.waitForFileUpload"),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
}, [inputsForms, notify, t])
|
},
|
||||||
|
[inputsForms, notify, t]
|
||||||
|
);
|
||||||
const handleStartChat = useCallback(() => {
|
const handleStartChat = useCallback(() => {
|
||||||
if (checkInputsRequired()) {
|
if (checkInputsRequired()) {
|
||||||
setShowConfigPanelBeforeChat(false)
|
setShowConfigPanelBeforeChat(false);
|
||||||
setShowNewConversationItemInList(true)
|
setShowNewConversationItemInList(true);
|
||||||
}
|
}
|
||||||
}, [setShowConfigPanelBeforeChat, setShowNewConversationItemInList, checkInputsRequired])
|
}, [
|
||||||
const currentChatInstanceRef = useRef<{ handleStop: () => void }>({ handleStop: () => { } })
|
setShowConfigPanelBeforeChat,
|
||||||
const handleChangeConversation = useCallback((conversationId: string) => {
|
setShowNewConversationItemInList,
|
||||||
currentChatInstanceRef.current.handleStop()
|
checkInputsRequired,
|
||||||
setNewConversationId('')
|
]);
|
||||||
handleConversationIdInfoChange(conversationId)
|
const currentChatInstanceRef = useRef<{ handleStop: () => void }>({
|
||||||
|
handleStop: () => {},
|
||||||
|
});
|
||||||
|
const handleChangeConversation = useCallback(
|
||||||
|
(conversationId: string) => {
|
||||||
|
currentChatInstanceRef.current.handleStop();
|
||||||
|
setNewConversationId("");
|
||||||
|
handleConversationIdInfoChange(conversationId);
|
||||||
|
|
||||||
if (conversationId === '' && !checkInputsRequired(true))
|
if (conversationId === "" && !checkInputsRequired(true))
|
||||||
setShowConfigPanelBeforeChat(true)
|
setShowConfigPanelBeforeChat(true);
|
||||||
else
|
else setShowConfigPanelBeforeChat(false);
|
||||||
setShowConfigPanelBeforeChat(false)
|
},
|
||||||
}, [handleConversationIdInfoChange, setShowConfigPanelBeforeChat, checkInputsRequired])
|
[
|
||||||
|
handleConversationIdInfoChange,
|
||||||
|
setShowConfigPanelBeforeChat,
|
||||||
|
checkInputsRequired,
|
||||||
|
]
|
||||||
|
);
|
||||||
const handleNewConversation = useCallback(() => {
|
const handleNewConversation = useCallback(() => {
|
||||||
currentChatInstanceRef.current.handleStop()
|
currentChatInstanceRef.current.handleStop();
|
||||||
setNewConversationId('')
|
setNewConversationId("");
|
||||||
|
|
||||||
if (showNewConversationItemInList) {
|
if (showNewConversationItemInList) {
|
||||||
handleChangeConversation('')
|
handleChangeConversation("");
|
||||||
|
} else if (currentConversationId) {
|
||||||
|
handleConversationIdInfoChange("");
|
||||||
|
setShowConfigPanelBeforeChat(true);
|
||||||
|
setShowNewConversationItemInList(true);
|
||||||
|
handleNewConversationInputsChange({});
|
||||||
}
|
}
|
||||||
else if (currentConversationId) {
|
}, [
|
||||||
handleConversationIdInfoChange('')
|
handleChangeConversation,
|
||||||
setShowConfigPanelBeforeChat(true)
|
currentConversationId,
|
||||||
setShowNewConversationItemInList(true)
|
handleConversationIdInfoChange,
|
||||||
handleNewConversationInputsChange({})
|
setShowConfigPanelBeforeChat,
|
||||||
}
|
setShowNewConversationItemInList,
|
||||||
}, [handleChangeConversation, currentConversationId, handleConversationIdInfoChange, setShowConfigPanelBeforeChat, setShowNewConversationItemInList, showNewConversationItemInList, handleNewConversationInputsChange])
|
showNewConversationItemInList,
|
||||||
|
handleNewConversationInputsChange,
|
||||||
|
]);
|
||||||
const handleUpdateConversationList = useCallback(() => {
|
const handleUpdateConversationList = useCallback(() => {
|
||||||
mutateAppConversationData()
|
mutateAppConversationData();
|
||||||
mutateAppPinnedConversationData()
|
mutateAppPinnedConversationData();
|
||||||
}, [mutateAppConversationData, mutateAppPinnedConversationData])
|
}, [mutateAppConversationData, mutateAppPinnedConversationData]);
|
||||||
|
|
||||||
const handlePinConversation = useCallback(async (conversationId: string) => {
|
const handlePinConversation = useCallback(
|
||||||
await pinConversation(isInstalledApp, appId, conversationId)
|
async (conversationId: string) => {
|
||||||
notify({ type: 'success', message: t('common.api.success') })
|
await pinConversation(isInstalledApp, appId, conversationId);
|
||||||
handleUpdateConversationList()
|
notify({ type: "success", message: t("common.api.success") });
|
||||||
}, [isInstalledApp, appId, notify, t, handleUpdateConversationList])
|
handleUpdateConversationList();
|
||||||
|
},
|
||||||
|
[isInstalledApp, appId, notify, t, handleUpdateConversationList]
|
||||||
|
);
|
||||||
|
|
||||||
const handleUnpinConversation = useCallback(async (conversationId: string) => {
|
const handleUnpinConversation = useCallback(
|
||||||
await unpinConversation(isInstalledApp, appId, conversationId)
|
async (conversationId: string) => {
|
||||||
notify({ type: 'success', message: t('common.api.success') })
|
await unpinConversation(isInstalledApp, appId, conversationId);
|
||||||
handleUpdateConversationList()
|
notify({ type: "success", message: t("common.api.success") });
|
||||||
}, [isInstalledApp, appId, notify, t, handleUpdateConversationList])
|
handleUpdateConversationList();
|
||||||
|
},
|
||||||
|
[isInstalledApp, appId, notify, t, handleUpdateConversationList]
|
||||||
|
);
|
||||||
|
|
||||||
const [conversationDeleting, setConversationDeleting] = useState(false)
|
const [conversationDeleting, setConversationDeleting] = useState(false);
|
||||||
const handleDeleteConversation = useCallback(async (
|
const handleDeleteConversation = useCallback(
|
||||||
conversationId: string,
|
async (conversationId: string, { onSuccess }: Callback) => {
|
||||||
{
|
if (conversationDeleting) return;
|
||||||
onSuccess,
|
|
||||||
}: Callback,
|
|
||||||
) => {
|
|
||||||
if (conversationDeleting)
|
|
||||||
return
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setConversationDeleting(true)
|
setConversationDeleting(true);
|
||||||
await delConversation(conversationId)
|
await delConversation(conversationId);
|
||||||
notify({ type: 'success', message: t('common.api.success') })
|
notify({ type: "success", message: t("common.api.success") });
|
||||||
onSuccess()
|
onSuccess();
|
||||||
}
|
} finally {
|
||||||
finally {
|
setConversationDeleting(false);
|
||||||
setConversationDeleting(false)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (conversationId === currentConversationId)
|
if (conversationId === currentConversationId) handleNewConversation();
|
||||||
handleNewConversation()
|
|
||||||
|
|
||||||
handleUpdateConversationList()
|
handleUpdateConversationList();
|
||||||
}, [isInstalledApp, appId, notify, t, handleUpdateConversationList, handleNewConversation, currentConversationId, conversationDeleting])
|
},
|
||||||
|
[
|
||||||
|
isInstalledApp,
|
||||||
|
appId,
|
||||||
|
notify,
|
||||||
|
t,
|
||||||
|
handleUpdateConversationList,
|
||||||
|
handleNewConversation,
|
||||||
|
currentConversationId,
|
||||||
|
conversationDeleting,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
const [conversationRenaming, setConversationRenaming] = useState(false)
|
const [conversationRenaming, setConversationRenaming] = useState(false);
|
||||||
const handleRenameConversation = useCallback(async (
|
const handleRenameConversation = useCallback(
|
||||||
conversationId: string,
|
async (
|
||||||
newName: string,
|
conversationId: string,
|
||||||
{
|
newName: string,
|
||||||
onSuccess,
|
{ onSuccess }: Callback
|
||||||
}: Callback,
|
) => {
|
||||||
) => {
|
if (conversationRenaming) return;
|
||||||
if (conversationRenaming)
|
|
||||||
return
|
|
||||||
|
|
||||||
if (!newName.trim()) {
|
if (!newName.trim()) {
|
||||||
notify({
|
notify({
|
||||||
type: 'error',
|
type: "error",
|
||||||
message: t('common.chat.conversationNameCanNotEmpty'),
|
message: t("common.chat.conversationNameCanNotEmpty"),
|
||||||
})
|
});
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setConversationRenaming(true)
|
setConversationRenaming(true);
|
||||||
try {
|
try {
|
||||||
await renameConversation(conversationId, newName)
|
await renameConversation(conversationId, newName);
|
||||||
|
|
||||||
notify({
|
notify({
|
||||||
type: 'success',
|
type: "success",
|
||||||
message: t('common.actionMsg.modifiedSuccessfully'),
|
message: t("common.actionMsg.modifiedSuccessfully"),
|
||||||
})
|
});
|
||||||
setOriginConversationList(produce((draft) => {
|
setOriginConversationList(
|
||||||
const index = originConversationList.findIndex(item => item.id === conversationId)
|
produce((draft) => {
|
||||||
const item = draft[index]
|
const index = originConversationList.findIndex(
|
||||||
|
(item) => item.id === conversationId
|
||||||
|
);
|
||||||
|
const item = draft[index];
|
||||||
|
|
||||||
draft[index] = {
|
draft[index] = {
|
||||||
...item,
|
...item,
|
||||||
name: newName,
|
name: newName,
|
||||||
}
|
};
|
||||||
}))
|
})
|
||||||
onSuccess()
|
);
|
||||||
}
|
onSuccess();
|
||||||
finally {
|
} finally {
|
||||||
setConversationRenaming(false)
|
setConversationRenaming(false);
|
||||||
}
|
}
|
||||||
}, [isInstalledApp, appId, notify, t, conversationRenaming, originConversationList])
|
},
|
||||||
|
[
|
||||||
|
isInstalledApp,
|
||||||
|
appId,
|
||||||
|
notify,
|
||||||
|
t,
|
||||||
|
conversationRenaming,
|
||||||
|
originConversationList,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
const handleNewConversationCompleted = useCallback((newConversationId: string) => {
|
const handleNewConversationCompleted = useCallback(
|
||||||
setNewConversationId(newConversationId)
|
(newConversationId: string) => {
|
||||||
handleConversationIdInfoChange(newConversationId)
|
setNewConversationId(newConversationId);
|
||||||
setShowNewConversationItemInList(false)
|
handleConversationIdInfoChange(newConversationId);
|
||||||
mutateAppConversationData()
|
setShowNewConversationItemInList(false);
|
||||||
}, [mutateAppConversationData, handleConversationIdInfoChange])
|
mutateAppConversationData();
|
||||||
|
},
|
||||||
|
[mutateAppConversationData, handleConversationIdInfoChange]
|
||||||
|
);
|
||||||
|
|
||||||
const handleFeedback = useCallback(async (messageId: string, feedback: Feedback) => {
|
const [isFeedbackModalVisible, setIsFeedbackModalVisible] = useState(false);
|
||||||
await updateFeedback({ url: `/messages/${messageId}/feedbacks`, body: { rating: feedback.rating } }, isInstalledApp, appId)
|
const [currentMessageId, setCurrentMessageId] = useState<string | null>(null);
|
||||||
notify({ type: 'success', message: t('common.api.success') })
|
const [currentFeedback, setCurrentFeedback] = useState<Feedback | null>(null);
|
||||||
}, [isInstalledApp, appId, t, notify])
|
const [
|
||||||
|
currentConversationIdForFeedback,
|
||||||
|
setCurrentConversationIdForFeedback,
|
||||||
|
] = useState<string | null>(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 {
|
return {
|
||||||
appInfoError,
|
appInfoError,
|
||||||
@ -427,7 +614,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
|||||||
currentConversationItem,
|
currentConversationItem,
|
||||||
handleConversationIdInfoChange,
|
handleConversationIdInfoChange,
|
||||||
appData,
|
appData,
|
||||||
appParams: appParams || {} as ChatConfig,
|
appParams: appParams || ({} as ChatConfig),
|
||||||
appMeta,
|
appMeta,
|
||||||
appPinnedConversationData,
|
appPinnedConversationData,
|
||||||
appConversationData,
|
appConversationData,
|
||||||
@ -458,5 +645,12 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
|||||||
chatShouldReloadKey,
|
chatShouldReloadKey,
|
||||||
handleFeedback,
|
handleFeedback,
|
||||||
currentChatInstanceRef,
|
currentChatInstanceRef,
|
||||||
}
|
feedbackModal: (
|
||||||
}
|
<FeedbackModal
|
||||||
|
open={isFeedbackModalVisible}
|
||||||
|
onOk={handleFeedbackSubmit}
|
||||||
|
onCancel={() => setIsFeedbackModalVisible(false)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@ -1,33 +1,26 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from "react";
|
||||||
import {
|
import "antd/dist/reset.css"; // 引入 antd 的样式
|
||||||
useEffect,
|
import { useEffect, useState } from "react";
|
||||||
useState,
|
import { useAsyncEffect } from "ahooks";
|
||||||
} from 'react'
|
import { useThemeContext } from "@/app/components/chat/embedded-chatbot/theme/theme-context";
|
||||||
import { useAsyncEffect } from 'ahooks'
|
import { ChatWithHistoryContext, useChatWithHistoryContext } from "./context";
|
||||||
import { useThemeContext } from '@/app/components/chat/embedded-chatbot/theme/theme-context'
|
import { useChatWithHistory } from "./hooks";
|
||||||
import {
|
import Sidebar from "./sidebar";
|
||||||
ChatWithHistoryContext,
|
import HeaderInMobile from "./header-in-mobile";
|
||||||
useChatWithHistoryContext,
|
import ConfigPanel from "./config-panel";
|
||||||
} from './context'
|
import ChatWrapper from "./chat-wrapper";
|
||||||
import { useChatWithHistory } from './hooks'
|
import type { InstalledApp } from "@/models/explore";
|
||||||
import Sidebar from './sidebar'
|
import Loading from "@/app/components/base/loading";
|
||||||
import HeaderInMobile from './header-in-mobile'
|
import useBreakpoints, { MediaType } from "@/hooks/use-breakpoints";
|
||||||
import ConfigPanel from './config-panel'
|
import { useRouter } from "next/navigation";
|
||||||
import ChatWrapper from './chat-wrapper'
|
import AppUnavailable from "@/app/components/base/app-unavailable";
|
||||||
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 = {
|
type ChatWithHistoryProps = {
|
||||||
className?: string
|
className?: string;
|
||||||
userStr?: string
|
userStr?: string;
|
||||||
}
|
};
|
||||||
const ChatWithHistory: FC<ChatWithHistoryProps> = ({
|
|
||||||
className,
|
const ChatWithHistory: FC<ChatWithHistoryProps> = ({ className, userStr }) => {
|
||||||
userStr,
|
|
||||||
}) => {
|
|
||||||
const {
|
const {
|
||||||
appInfoError,
|
appInfoError,
|
||||||
appData,
|
appData,
|
||||||
@ -38,82 +31,78 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({
|
|||||||
chatShouldReloadKey,
|
chatShouldReloadKey,
|
||||||
isMobile,
|
isMobile,
|
||||||
themeBuilder,
|
themeBuilder,
|
||||||
} = useChatWithHistoryContext()
|
} = useChatWithHistoryContext();
|
||||||
|
|
||||||
const chatReady = (!showConfigPanelBeforeChat || !!appPrevChatTree.length)
|
const chatReady = !showConfigPanelBeforeChat || !!appPrevChatTree.length;
|
||||||
const customConfig = appData?.custom_config
|
const customConfig = appData?.custom_config;
|
||||||
const site = appData?.site
|
const site = appData?.site;
|
||||||
|
|
||||||
useEffect(() => {
|
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 (site) {
|
||||||
if (customConfig)
|
if (customConfig) document.title = `${site.title}`;
|
||||||
document.title = `${site.title}`
|
else document.title = `${site.title} - Powered by Dify`;
|
||||||
else
|
|
||||||
document.title = `${site.title} - Powered by Dify`
|
|
||||||
}
|
}
|
||||||
}, [site, customConfig, themeBuilder])
|
}, [site, customConfig, themeBuilder]);
|
||||||
|
|
||||||
if (appInfoLoading) {
|
if (appInfoLoading) {
|
||||||
return (
|
return <Loading type="app" />;
|
||||||
<Loading type='app' />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appInfoError) {
|
if (appInfoError) {
|
||||||
return (
|
return <AppUnavailable />;
|
||||||
<AppUnavailable />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`h-full flex bg-white ${className} ${isMobile && 'flex-col'}`}>
|
<div
|
||||||
{
|
className={`h-full flex bg-white ${className} ${isMobile && "flex-col"}`}
|
||||||
!isMobile && (
|
>
|
||||||
<Sidebar userStr={userStr} />
|
{!isMobile && <Sidebar userStr={userStr} />}
|
||||||
)
|
{isMobile && <HeaderInMobile />}
|
||||||
}
|
<div
|
||||||
{
|
className={`grow overflow-hidden ${
|
||||||
isMobile && (
|
showConfigPanelBeforeChat &&
|
||||||
<HeaderInMobile />
|
!appPrevChatTree.length &&
|
||||||
)
|
"flex items-center justify-center"
|
||||||
}
|
}`}
|
||||||
<div className={`grow overflow-hidden ${showConfigPanelBeforeChat && !appPrevChatTree.length && 'flex items-center justify-center'}`}>
|
>
|
||||||
{
|
{showConfigPanelBeforeChat &&
|
||||||
showConfigPanelBeforeChat && !appChatListDataLoading && !appPrevChatTree.length && (
|
!appChatListDataLoading &&
|
||||||
<div className={`flex w-full items-center justify-center h-full ${isMobile && 'px-4'}`}>
|
!appPrevChatTree.length && (
|
||||||
|
<div
|
||||||
|
className={`flex w-full items-center justify-center h-full ${
|
||||||
|
isMobile && "px-4"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<ConfigPanel />
|
<ConfigPanel />
|
||||||
</div>
|
</div>
|
||||||
)
|
)}
|
||||||
}
|
{appChatListDataLoading && chatReady && <Loading type="app" />}
|
||||||
{
|
{chatReady && !appChatListDataLoading && (
|
||||||
appChatListDataLoading && chatReady && (
|
<ChatWrapper key={chatShouldReloadKey} />
|
||||||
<Loading type='app' />
|
)}
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
chatReady && !appChatListDataLoading && (
|
|
||||||
<ChatWrapper key={chatShouldReloadKey} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export type ChatWithHistoryWrapProps = {
|
export type ChatWithHistoryWrapProps = {
|
||||||
installedAppInfo?: InstalledApp
|
installedAppInfo?: InstalledApp;
|
||||||
className?: string
|
className?: string;
|
||||||
userStr?: string
|
userStr?: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
|
const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
|
||||||
installedAppInfo,
|
installedAppInfo,
|
||||||
className,
|
className,
|
||||||
userStr,
|
userStr,
|
||||||
}) => {
|
}) => {
|
||||||
const media = useBreakpoints()
|
const media = useBreakpoints();
|
||||||
const isMobile = media === MediaType.mobile
|
const isMobile = media === MediaType.mobile;
|
||||||
const themeBuilder = useThemeContext()
|
const themeBuilder = useThemeContext();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
appInfoError,
|
appInfoError,
|
||||||
@ -146,93 +135,99 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
|
|||||||
appId,
|
appId,
|
||||||
handleFeedback,
|
handleFeedback,
|
||||||
currentChatInstanceRef,
|
currentChatInstanceRef,
|
||||||
} = useChatWithHistory(installedAppInfo)
|
feedbackModal,
|
||||||
|
} = useChatWithHistory(installedAppInfo);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatWithHistoryContext.Provider value={{
|
<ChatWithHistoryContext.Provider
|
||||||
appInfoError,
|
value={{
|
||||||
appInfoLoading,
|
appInfoError,
|
||||||
appData,
|
appInfoLoading,
|
||||||
appParams,
|
appData,
|
||||||
appMeta,
|
appParams,
|
||||||
appChatListDataLoading,
|
appMeta,
|
||||||
currentConversationId,
|
appChatListDataLoading,
|
||||||
currentConversationItem,
|
currentConversationId,
|
||||||
appPrevChatTree,
|
currentConversationItem,
|
||||||
pinnedConversationList,
|
appPrevChatTree,
|
||||||
conversationList,
|
pinnedConversationList,
|
||||||
showConfigPanelBeforeChat,
|
conversationList,
|
||||||
newConversationInputs,
|
showConfigPanelBeforeChat,
|
||||||
newConversationInputsRef,
|
newConversationInputs,
|
||||||
handleNewConversationInputsChange,
|
newConversationInputsRef,
|
||||||
inputsForms,
|
handleNewConversationInputsChange,
|
||||||
handleNewConversation,
|
inputsForms,
|
||||||
handleStartChat,
|
handleNewConversation,
|
||||||
handleChangeConversation,
|
handleStartChat,
|
||||||
handlePinConversation,
|
handleChangeConversation,
|
||||||
handleUnpinConversation,
|
handlePinConversation,
|
||||||
handleDeleteConversation,
|
handleUnpinConversation,
|
||||||
conversationRenaming,
|
handleDeleteConversation,
|
||||||
handleRenameConversation,
|
conversationRenaming,
|
||||||
handleNewConversationCompleted,
|
handleRenameConversation,
|
||||||
chatShouldReloadKey,
|
handleNewConversationCompleted,
|
||||||
isMobile,
|
chatShouldReloadKey,
|
||||||
isInstalledApp,
|
isMobile,
|
||||||
appId,
|
isInstalledApp,
|
||||||
handleFeedback,
|
appId,
|
||||||
currentChatInstanceRef,
|
handleFeedback,
|
||||||
themeBuilder,
|
currentChatInstanceRef,
|
||||||
}}>
|
themeBuilder,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ChatWithHistory className={className} userStr={userStr} />
|
<ChatWithHistory className={className} userStr={userStr} />
|
||||||
|
{feedbackModal} {/* 确保 feedbackModal 正确渲染 */}
|
||||||
</ChatWithHistoryContext.Provider>
|
</ChatWithHistoryContext.Provider>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const ChatWithHistoryWrapWithCheckToken: FC<ChatWithHistoryWrapProps> = ({
|
const ChatWithHistoryWrapWithCheckToken: FC<ChatWithHistoryWrapProps> = ({
|
||||||
installedAppInfo,
|
installedAppInfo,
|
||||||
className,
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
const [initialized, setInitialized] = useState(false)
|
const [initialized, setInitialized] = useState(false);
|
||||||
const [appUnavailable, setAppUnavailable] = useState<boolean>(false)
|
const [appUnavailable, setAppUnavailable] = useState<boolean>(false);
|
||||||
const [resCode, setResCode] = useState<number>(200)
|
const [resCode, setResCode] = useState<number>(200);
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const [userName, setUserName] = useState('')
|
const [userName, setUserName] = useState("");
|
||||||
const [nickName, setNickName] = useState('')
|
const [nickName, setNickName] = useState("");
|
||||||
|
|
||||||
useAsyncEffect(async () => {
|
useAsyncEffect(async () => {
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
if (!installedAppInfo) {
|
if (!installedAppInfo) {
|
||||||
const accessToken = localStorage.getItem('token')
|
const accessToken = localStorage.getItem("token");
|
||||||
if (!accessToken) {
|
if (!accessToken) {
|
||||||
router.replace('/login')
|
router.replace("/login");
|
||||||
} else {
|
} else {
|
||||||
fetch(`${process.env.NEXT_PUBLIC_BASE_API_URL}/getInfo`, {
|
fetch(`${process.env.NEXT_PUBLIC_BASE_API_URL}/getInfo`, {
|
||||||
method: 'GET',
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${accessToken}`,
|
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)
|
if (!initialized) return null;
|
||||||
return null
|
|
||||||
|
|
||||||
if (appUnavailable)
|
if (appUnavailable) return <AppUnavailable code={resCode} />;
|
||||||
return <AppUnavailable code={resCode} />
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatWithHistoryWrap
|
<ChatWithHistoryWrap
|
||||||
@ -240,7 +235,7 @@ const ChatWithHistoryWrapWithCheckToken: FC<ChatWithHistoryWrapProps> = ({
|
|||||||
className={className}
|
className={className}
|
||||||
userStr={`${nickName}[${userName}]`}
|
userStr={`${nickName}[${userName}]`}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default ChatWithHistoryWrapWithCheckToken
|
export default ChatWithHistoryWrapWithCheckToken;
|
||||||
|
|||||||
@ -1,34 +1,32 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
devIndicators: false, // show dev tools
|
reactStrictMode: true,
|
||||||
productionBrowserSourceMaps: false, // enable browser source map generation during the production build
|
// 压缩优化(Next.js 13+默认启用SWC,无需手动配置)
|
||||||
// Configure pageExtensions to include md and mdx
|
compress: true,
|
||||||
pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
|
|
||||||
experimental: {
|
// 图片优化
|
||||||
// appDir: true,
|
images: {
|
||||||
|
formats: ["image/avif", "image/webp"],
|
||||||
|
domains: ["cdn.yourdomain.com"],
|
||||||
},
|
},
|
||||||
// fix all before production. Now it slow the develop speed.
|
|
||||||
eslint: {
|
// 修正后的开发指示器配置
|
||||||
// Warning: This allows production builds to successfully complete even if
|
devIndicators: {
|
||||||
// your project has ESLint errors.
|
position: "bottom-right", // 新版统一用position
|
||||||
ignoreDuringBuilds: true,
|
|
||||||
},
|
|
||||||
typescript: {
|
|
||||||
// https://nextjs.org/docs/api-reference/next.config.js/ignoring-typescript-errors
|
|
||||||
ignoreBuildErrors: true,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 页面扩展名
|
||||||
|
pageExtensions: ["ts", "tsx", "js", "jsx", "md", "mdx"],
|
||||||
|
|
||||||
|
// 路由重写
|
||||||
async rewrites() {
|
async rewrites() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
source: '/files/:path*',
|
source: "/dev-api/:path*",
|
||||||
destination: `${process.env.NEXT_PUBLIC_FILES_URL}/:path*`,
|
destination: "http://192.168.6.9:8085/:path*",
|
||||||
},
|
},
|
||||||
{
|
];
|
||||||
source: '/dev-api/:path*',
|
|
||||||
destination: 'http://192.168.6.9:8085/:path*',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
module.exports = nextConfig
|
module.exports = nextConfig;
|
||||||
|
|||||||
@ -44,9 +44,9 @@ export const fetchConversations = async (limit = 100, last_id = null) => {
|
|||||||
return get('conversations', { params: { limit, last_id } })
|
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', {
|
return get('messages', {
|
||||||
params: { conversation_id: conversationId, limit, last_id }
|
params: { conversation_id: conversationId, limit}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user