fix: export log dateend;feat: file selector render (#6072)

* fix: export log dateend

* feat: file selector render
This commit is contained in:
Archer 2025-12-10 13:28:04 +08:00 committed by archer
parent 3c20d9475f
commit 9f09fbffbb
No known key found for this signature in database
GPG Key ID: 4446499B846D4A9E
26 changed files with 225 additions and 67 deletions

View File

@ -32,6 +32,7 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4144' \
6. 支持配置对话文件白名单。
7. S3 支持 pathStyle 配置。
8. 支持通过 Sealos 来进行多租户自定义域名配置。
9. 工作流中引用工具时,文件输入支持手动填写(原本只支持变量引用)。
## ⚙️ 优化
@ -57,6 +58,7 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4144' \
10. 发布渠道文档链接定位错误。
11. Checkbox 在禁用状态时hover 样式错误。
12. 模型头像缺失情况下,默认 huggingface.svg 图标显示错误。
13. 日志导出时,结束时间会多出一天。
## 插件

View File

@ -119,7 +119,7 @@
"document/content/docs/upgrading/4-14/4141.mdx": "2025-11-19T10:15:27+08:00",
"document/content/docs/upgrading/4-14/4142.mdx": "2025-11-18T19:27:14+08:00",
"document/content/docs/upgrading/4-14/4143.mdx": "2025-11-26T20:52:05+08:00",
"document/content/docs/upgrading/4-14/4144.mdx": "2025-12-10T11:41:15+08:00",
"document/content/docs/upgrading/4-14/4144.mdx": "2025-12-10T11:23:18+08:00",
"document/content/docs/upgrading/4-8/40.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/upgrading/4-8/41.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/upgrading/4-8/42.mdx": "2025-08-02T19:38:37+08:00",
@ -201,4 +201,4 @@
"document/content/docs/use-cases/external-integration/openapi.mdx": "2025-09-29T11:34:11+08:00",
"document/content/docs/use-cases/external-integration/wecom.mdx": "2025-12-09T23:33:32+08:00",
"document/content/docs/use-cases/index.mdx": "2025-07-24T14:23:04+08:00"
}
}

View File

@ -16,3 +16,10 @@ export const parseI18nString = (str: I18nStringType | string = '', lang = 'en')
// 最后回退到英文
return str['en'] || '';
};
export const formatI18nLocationToZhEn = (locale: localeType = 'zh-CN'): 'zh' | 'en' => {
if (locale.toLocaleLowerCase().startsWith('zh')) {
return 'zh';
}
return 'en';
};

View File

@ -209,14 +209,15 @@ export type DispatchNodeResponseType = {
headers?: Record<string, any>;
httpResult?: Record<string, any>;
// plugin output
// Tool
toolInput?: Record<string, any>;
pluginOutput?: Record<string, any>;
pluginDetail?: ChatHistoryItemResType[];
// if-else
ifElseResult?: string;
// tool
// tool call
toolCallInputTokens?: number;
toolCallOutputTokens?: number;
toolDetail?: ChatHistoryItemResType[];
@ -225,9 +226,6 @@ export type DispatchNodeResponseType = {
// code
codeLog?: string;
// plugin
pluginOutput?: Record<string, any>;
// read files
readFilesResult?: string;
readFiles?: ReadFileNodeResponse;

View File

@ -2,11 +2,13 @@ import fs from 'node:fs';
import type { ReaderModel } from '@maxmind/geoip2-node';
import { Reader } from '@maxmind/geoip2-node';
import { cleanupIntervalMs, dbPath, privateOrOtherLocationName } from './constants';
import type { I18nName, LocationName } from './type';
import type { LocationName } from './type';
import { extractLocationData } from './utils';
import type { NextApiRequest } from 'next';
import { getClientIp } from 'request-ip';
import { addLog } from '../system/log';
import type { localeType } from '@fastgpt/global/common/i18n/type';
import { formatI18nLocationToZhEn } from '@fastgpt/global/common/i18n/utils';
let reader: ReaderModel | null = null;
@ -25,21 +27,23 @@ export function getGeoReader() {
return reader;
}
export function getLocationFromIp(ip?: string, locale: keyof I18nName = 'zh') {
export function getLocationFromIp(ip?: string, locale: localeType = 'zh-CN') {
const formatedLocale = formatI18nLocationToZhEn(locale);
if (!ip) {
return privateOrOtherLocationName.country?.[locale];
return privateOrOtherLocationName.country?.[formatedLocale];
}
const reader = getGeoReader();
let locationName = locationIpMap.get(ip);
if (locationName) {
return [
locationName.country?.[locale],
locationName.province?.[locale],
locationName.city?.[locale]
locationName.country?.[formatedLocale],
locationName.province?.[formatedLocale],
locationName.city?.[formatedLocale]
]
.filter(Boolean)
.join(locale === 'zh' ? '' : ',');
.join(formatedLocale === 'zh' ? '' : ',');
}
try {
@ -62,15 +66,15 @@ export function getLocationFromIp(ip?: string, locale: keyof I18nName = 'zh') {
locationIpMap.set(ip, locationName);
return [
locationName.country?.[locale],
locationName.province?.[locale],
locationName.city?.[locale]
locationName.country?.[formatedLocale],
locationName.province?.[formatedLocale],
locationName.city?.[formatedLocale]
]
.filter(Boolean)
.join(locale === 'zh' ? '' : ', ');
.join(formatedLocale === 'zh' ? '' : ', ');
} catch (error) {
locationIpMap.set(ip, privateOrOtherLocationName);
return privateOrOtherLocationName.country?.[locale];
return privateOrOtherLocationName.country?.[formatedLocale];
}
}

View File

@ -54,6 +54,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
} = props;
const systemToolId = toolConfig?.systemTool?.toolId;
let toolInput: Record<string, any> = {};
try {
// run system tool
@ -78,10 +79,11 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
return dbPlugin?.inputListVal || {};
}
})();
toolInput = Object.fromEntries(
Object.entries(params).filter(([key]) => key !== NodeInputKeyEnum.systemInputConfig)
);
const inputs = {
...Object.fromEntries(
Object.entries(params).filter(([key]) => key !== NodeInputKeyEnum.systemInputConfig)
),
...toolInput,
...inputConfigParams
};
@ -132,6 +134,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
return {
data: res.error,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
toolInput,
toolRes: res.error,
moduleLogo: avatar
},
@ -148,6 +151,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
return {
error: res.error,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
toolInput,
error: res.error,
moduleLogo: avatar
},
@ -179,6 +183,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
data: result,
[DispatchNodeResponseKeyEnum.answerText]: answerText,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
toolInput,
toolRes: result,
moduleLogo: avatar,
totalPoints: usagePoints
@ -213,10 +218,12 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
});
props.mcpClientMemory[url] = mcpClient;
toolInput = params;
const result = await mcpClient.toolCall({ toolName, params, closeConnection: false });
return {
data: { [NodeOutputKeyEnum.rawResponse]: result },
[DispatchNodeResponseKeyEnum.nodeResponse]: {
toolInput,
toolRes: result,
moduleLogo: avatar
},
@ -241,6 +248,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
throw new Error(`HTTP tool ${toolName} not found`);
}
toolInput = params;
const { data, errorMsg } = await runHTTPTool({
baseUrl: baseUrl || '',
toolPath: httpTool.path,
@ -262,6 +270,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
return {
error: { [NodeOutputKeyEnum.errorText]: errorMsg },
[DispatchNodeResponseKeyEnum.nodeResponse]: {
toolInput,
toolRes: errorMsg,
moduleLogo: avatar
},
@ -274,6 +283,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
return {
data: { [NodeOutputKeyEnum.rawResponse]: data, ...(typeof data === 'object' ? data : {}) },
[DispatchNodeResponseKeyEnum.nodeResponse]: {
toolInput,
toolRes: data,
moduleLogo: avatar
},
@ -290,6 +300,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
storeSecret: headerSecret
})
});
toolInput = restParams;
const result = await mcpClient.toolCall({ toolName, params: restParams });
return {
@ -297,6 +308,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
[NodeOutputKeyEnum.rawResponse]: result
},
[DispatchNodeResponseKeyEnum.nodeResponse]: {
toolInput,
toolRes: result,
moduleLogo: avatar
},
@ -318,6 +330,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
return getNodeErrResponse({
error,
customNodeResponse: {
toolInput,
moduleLogo: avatar
}
});

View File

@ -105,6 +105,12 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
let val = data[input.key] ?? input.value;
if (input.renderTypeList.includes(FlowNodeInputTypeEnum.password)) {
val = anyValueDecrypt(val);
} else if (
input.renderTypeList.includes(FlowNodeInputTypeEnum.fileSelect) &&
Array.isArray(val) &&
data[input.key]
) {
data[input.key] = val.map((item) => item.url);
}
return {
@ -172,6 +178,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
[DispatchNodeResponseKeyEnum.nodeResponse]: {
moduleLogo: plugin.avatar,
totalPoints: usagePoints,
toolInput: data,
pluginOutput: output?.pluginOutput,
pluginDetail: pluginData?.permission?.hasWritePer // Not system plugin
? flowResponses.filter((item) => {

View File

@ -56,6 +56,8 @@ export const dispatchPluginInput = async (
return {
data: {
...params,
// 旧版本适配
[NodeOutputKeyEnum.userFiles]: files
.map((item) => {
return item?.url ?? '';

View File

@ -51,61 +51,56 @@ const NodeInputSelect = ({
{
type: FlowNodeInputTypeEnum.textarea,
icon: FlowNodeInputMap[FlowNodeInputTypeEnum.textarea].icon,
title: t('common:core.workflow.inputType.Manual input')
},
{
type: FlowNodeInputTypeEnum.JSONEditor,
icon: FlowNodeInputMap[FlowNodeInputTypeEnum.JSONEditor].icon,
title: t('common:core.workflow.inputType.Manual input')
},
{
type: FlowNodeInputTypeEnum.addInputParam,
icon: FlowNodeInputMap[FlowNodeInputTypeEnum.addInputParam].icon,
title: t('common:core.workflow.inputType.dynamicTargetInput')
},
{
type: FlowNodeInputTypeEnum.selectLLMModel,
icon: FlowNodeInputMap[FlowNodeInputTypeEnum.selectLLMModel].icon,
title: t('common:core.workflow.inputType.Manual select')
},
{
type: FlowNodeInputTypeEnum.settingLLMModel,
icon: FlowNodeInputMap[FlowNodeInputTypeEnum.settingLLMModel].icon,
title: t('common:core.workflow.inputType.Manual select')
},
{
type: FlowNodeInputTypeEnum.selectDataset,
icon: FlowNodeInputMap[FlowNodeInputTypeEnum.selectDataset].icon,
title: t('common:core.workflow.inputType.Manual select')
},
{
type: FlowNodeInputTypeEnum.selectDatasetParamsModal,
icon: FlowNodeInputMap[FlowNodeInputTypeEnum.selectDatasetParamsModal].icon,
title: t('common:core.workflow.inputType.Manual select')
},
{
type: FlowNodeInputTypeEnum.settingDatasetQuotePrompt,
icon: FlowNodeInputMap[FlowNodeInputTypeEnum.settingDatasetQuotePrompt].icon,
title: t('common:core.workflow.inputType.Manual input')
},
{
type: FlowNodeInputTypeEnum.hidden,
icon: FlowNodeInputMap[FlowNodeInputTypeEnum.hidden].icon,
title: t('common:core.workflow.inputType.Manual input')
},
{
type: FlowNodeInputTypeEnum.custom,
icon: FlowNodeInputMap[FlowNodeInputTypeEnum.custom].icon,
title: t('common:core.workflow.inputType.Manual input')
},
{
type: FlowNodeInputTypeEnum.fileSelect,
icon: FlowNodeInputMap[FlowNodeInputTypeEnum.fileSelect].icon,
title: t('common:core.workflow.inputType.Manual input')
}
]);
@ -122,7 +117,7 @@ const NodeInputSelect = ({
onChange(input.type);
}
})),
[renderType]
[onChange, renderType]
);
const filterMenuList = useMemo(

View File

@ -145,6 +145,7 @@
"expand_tool_create": "Expand MCP/Http create",
"export_config_successful": "Configuration copied, some sensitive information automatically filtered. Please check for any remaining sensitive data.",
"export_configs": "Export",
"export_log_filename": "{{name}} chat logs.csv",
"fastgpt_marketplace": "FastGPT plug-in market",
"feedback_count": "User Feedback",
"file_quote_link": "Files",

View File

@ -14,7 +14,7 @@
"citations": "{{num}} References",
"clear_input_value": "Clear input",
"click_contextual_preview": "Click to see contextual preview",
"click_to_add_url": "Click to add link",
"click_to_add_url": "Enter file link",
"completion_finish_close": "Disconnection",
"completion_finish_content_filter": "Trigger safe wind control",
"completion_finish_function_call": "Function Calls",
@ -51,6 +51,7 @@
"home.no_available_tools": "No tools available",
"home.select_tools": "Select Tool",
"home.tools": "Tool: {{num}}",
"images_collection_not_supported": "Image collection is not supported open the original file",
"in_progress": "In Progress",
"input_guide": "Input Guide",
"input_guide_lexicon": "Lexicon",
@ -75,7 +76,6 @@
"query_extension_result": "Problem optimization results",
"question_tip": "From top to bottom, the response order of each module",
"read_raw_source": "Open the original text",
"images_collection_not_supported": "Image collection is not supported open the original file",
"reasoning_text": "Thinking process",
"release_cancel": "Release Cancel",
"release_send": "Release send, slide up to cancel",
@ -167,6 +167,8 @@
"start_chat": "Start",
"stream_output": "Stream Output",
"task_has_continued": "Task has continued running",
"tool_input": "tool input",
"tool_output": "Tool output",
"unsupported_file_type": "Unsupported file types",
"upload": "Upload",
"variable_invisable_in_share": "External variables are not visible in login-free links",

View File

@ -427,7 +427,6 @@
"core.chat.response.module query": "Question/Search Term",
"core.chat.response.module similarity": "Similarity",
"core.chat.response.module temperature": "Temperature",
"core.chat.response.plugin output": "Plugin Output Value",
"core.chat.response.search using reRank": "Result Re-Rank",
"core.chat.response.text output": "Text Output",
"core.chat.response.update_var_result": "Variable Update Result (Displays Multiple Variable Update Results in Order)",

View File

@ -149,6 +149,7 @@
"expand_tool_create": "展开MCP、Http创建",
"export_config_successful": "已复制配置,自动过滤部分敏感信息,请注意检查是否仍有敏感数据",
"export_configs": "导出配置",
"export_log_filename": "{{name}} 对话日志.csv",
"fastgpt_marketplace": "FastGPT 插件市场",
"feedback_count": "用户反馈",
"file_quote_link": "文件链接",

View File

@ -14,7 +14,7 @@
"citations": "{{num}}条引用",
"clear_input_value": "清空输入",
"click_contextual_preview": "点击查看上下文预览",
"click_to_add_url": "点击添加链接",
"click_to_add_url": "输入文件链接",
"completion_finish_close": "连接断开",
"completion_finish_content_filter": "触发安全风控",
"completion_finish_function_call": "函数调用",
@ -51,6 +51,7 @@
"home.no_available_tools": "暂无可用工具",
"home.select_tools": "选择工具",
"home.tools": "工具:{{num}}",
"images_collection_not_supported": "图片数据集不支持打开原文",
"in_progress": "进行中",
"input_guide": "输入引导",
"input_guide_lexicon": "词库",
@ -75,7 +76,6 @@
"query_extension_result": "问题优化结果",
"question_tip": "从上到下,为各个模块的响应顺序",
"read_raw_source": "打开原文",
"images_collection_not_supported": "图片数据集不支持打开原文",
"reasoning_text": "思考过程",
"release_cancel": "松开取消",
"release_send": "松开发送,上滑取消",
@ -170,6 +170,8 @@
"start_chat": "开始对话",
"stream_output": "流输出",
"task_has_continued": "任务已继续运行",
"tool_input": "工具输入",
"tool_output": "工具输出",
"unsupported_file_type": "不支持的文件类型",
"upload": "上传",
"variable_invisable_in_share": "外部变量在免登录链接中不可见",

View File

@ -430,7 +430,6 @@
"core.chat.response.module query": "问题/检索词",
"core.chat.response.module similarity": "相似度",
"core.chat.response.module temperature": "温度",
"core.chat.response.plugin output": "插件输出值",
"core.chat.response.search using reRank": "结果重排",
"core.chat.response.text output": "文本输出",
"core.chat.response.update_var_result": "变量更新结果(按顺序展示多个变量更新结果)",

View File

@ -144,6 +144,7 @@
"expand_tool_create": "展開 MCP、Http 創建",
"export_config_successful": "已複製設定,自動過濾部分敏感資訊,請注意檢查是否仍有敏感資料",
"export_configs": "匯出設定",
"export_log_filename": "{{name}} 對話日誌.csv",
"fastgpt_marketplace": "FastGPT 插件市場",
"feedback_count": "使用者回饋",
"file_quote_link": "檔案連結",

View File

@ -14,7 +14,7 @@
"citations": "{{num}} 筆引用",
"clear_input_value": "清空輸入",
"click_contextual_preview": "點選檢視上下文預覽",
"click_to_add_url": "點擊添加鏈接",
"click_to_add_url": "輸入文件鏈接",
"completion_finish_close": "連接斷開",
"completion_finish_content_filter": "觸發安全風控",
"completion_finish_function_call": "函式呼叫",
@ -51,6 +51,7 @@
"home.no_available_tools": "暫無可用工具",
"home.select_tools": "選擇工具",
"home.tools": "工具:{{num}}",
"images_collection_not_supported": "圖片資料集不支持開啟原文",
"in_progress": "進行中",
"input_guide": "輸入導引",
"input_guide_lexicon": "詞彙庫",
@ -75,7 +76,6 @@
"query_extension_result": "問題優化結果",
"question_tip": "由上至下,各個模組的回應順序",
"read_raw_source": "開啟原文",
"images_collection_not_supported": "圖片資料集不支持開啟原文",
"reasoning_text": "思考過程",
"release_cancel": "鬆開取消",
"release_send": "鬆開傳送,上滑取消",
@ -167,6 +167,8 @@
"start_chat": "開始對話",
"stream_output": "串流輸出",
"task_has_continued": "任務已繼續運行",
"tool_input": "工具輸入",
"tool_output": "工具輸出",
"unsupported_file_type": "不支援的檔案類型",
"upload": "上傳",
"variable_invisable_in_share": "外部變量在免登錄鏈接中不可見",

View File

@ -427,7 +427,6 @@
"core.chat.response.module query": "問題/搜尋詞",
"core.chat.response.module similarity": "相似度",
"core.chat.response.module temperature": "溫度",
"core.chat.response.plugin output": "外掛程式輸出值",
"core.chat.response.search using reRank": "結果重新排名",
"core.chat.response.text output": "文字輸出",
"core.chat.response.update_var_result": "變數更新結果(依序顯示多個變數更新結果)",

View File

@ -30,6 +30,7 @@ import { POST } from '@/web/common/api/request';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { formatFileSize } from '@fastgpt/global/common/file/tools';
import { WorkflowRuntimeContext } from '@/components/core/chat/ChatContainer/context/workflowRuntimeContext';
import { useSafeTranslation } from '@fastgpt/web/hooks/useSafeTranslation';
const FileSelector = ({
value,
@ -53,7 +54,7 @@ const FileSelector = ({
}) => {
const { feConfigs } = useSystemStore();
const { toast } = useToast();
const { t } = useTranslation();
const { t } = useSafeTranslation();
const appId = useContextSelector(WorkflowRuntimeContext, (v) => v.appId);
const chatId = useContextSelector(WorkflowRuntimeContext, (v) => v.chatId);
@ -491,7 +492,7 @@ const FileSelector = ({
</HStack>
{file?.error && (
<Box mt={1} fontSize={'xs'} color={'red.600'}>
{file?.error}
{t(file.error)}
</Box>
)}
</Box>

View File

@ -350,10 +350,8 @@ export const WholeResponseContent = ({
</>
{/* plugin */}
<>
<Row
label={t('common:core.chat.response.plugin output')}
value={activeModule?.pluginOutput}
/>
<Row label={t('chat:tool_input')} value={activeModule?.toolInput} />
<Row label={t('chat:tool_output')} value={activeModule?.pluginOutput} />
</>
{/* text output */}
<Row label={t('common:core.chat.response.text output')} value={activeModule?.textOutput} />

View File

@ -10,7 +10,6 @@ export type GetAppChatLogsProps = {
sources?: ChatSourceEnum[];
tmbIds?: string[];
chatSearch?: string;
locale?: keyof I18nName;
};
export type GetAppChatLogsParams = PaginationProps<GetAppChatLogsProps>;

View File

@ -20,7 +20,6 @@ import MultipleSelect, {
import React, { useMemo, useState } from 'react';
import { useTranslation } from 'next-i18next';
import DateRangePicker from '@fastgpt/web/components/common/DateRangePicker';
import { addDays } from 'date-fns';
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
import { getTeamMembers } from '@/web/support/user/team/api';
import Avatar from '@fastgpt/web/components/common/Avatar';
@ -50,7 +49,8 @@ import dynamic from 'next/dynamic';
import type { HeaderControlProps } from './LogChart';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MyBox from '@fastgpt/web/components/common/MyBox';
import type { I18nName } from '@fastgpt/service/common/geo/type';
import { useContextSelector } from 'use-context-selector';
import { AppContext } from '../context';
const DetailLogsModal = dynamic(() => import('./DetailLogsModal'));
@ -65,10 +65,11 @@ const LogTable = ({
showSourceSelector = true,
px = [4, 8]
}: HeaderControlProps) => {
const { t, i18n } = useTranslation();
const { t } = useTranslation();
const { feConfigs } = useSystemStore();
const [detailLogsId, setDetailLogsId] = useState<string>();
const appName = useContextSelector(AppContext, (v) => v.appDetail.name);
// source
const sourceList = useMemo(
@ -147,15 +148,14 @@ const LogTable = ({
const headerTitle = enabledKeys.map((k) => t(AppLogKeysEnumMap[k])).join(',');
await downloadFetch({
url: '/api/core/app/exportChatLogs',
filename: 'chat_logs.csv',
filename: t('app:export_log_filename', { name: appName }),
body: {
appId,
dateStart: dayjs(dateRange.from || new Date()).format(),
dateEnd: dayjs(addDays(dateRange.to || new Date(), 1)).format(),
dateEnd: dayjs(dateRange.to || new Date()).format(),
sources: isSelectAllSource ? undefined : chatSources,
tmbIds: isSelectAllTmb ? undefined : selectTmbIds,
chatSearch,
locale: i18n.language === 'zh-CN' ? 'zh' : 'en',
title: `${headerTitle},${t('app:logs_keys_chatDetails')}`,
logKeys: enabledKeys,
sourcesMap: Object.fromEntries(
@ -180,8 +180,7 @@ const LogTable = ({
dateEnd: dateRange.to!,
sources: isSelectAllSource ? undefined : chatSources,
tmbIds: isSelectAllTmb ? undefined : selectTmbIds,
chatSearch,
locale: (i18n.language === 'zh-CN' ? 'zh' : 'en') as keyof I18nName
chatSearch
}),
[
appId,
@ -191,8 +190,7 @@ const LogTable = ({
isSelectAllSource,
selectTmbIds,
isSelectAllTmb,
chatSearch,
i18n.language
chatSearch
]
);

View File

@ -22,7 +22,7 @@ const RenderList: Record<
Component: dynamic(() => import('./templates/Reference'))
},
[FlowNodeInputTypeEnum.fileSelect]: {
Component: dynamic(() => import('./templates/Reference'))
Component: dynamic(() => import('./templates/FileSelect'))
},
[FlowNodeInputTypeEnum.selectApp]: {
Component: dynamic(() => import('./templates/SelectApp'))
@ -135,6 +135,8 @@ const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props)
if (!RenderItem) return null;
console.log(renderType, input);
return {
Component: (
<RenderItem.Component inputs={filterProInputs} item={input} nodeId={nodeId} />

View File

@ -0,0 +1,127 @@
import React, { useCallback, useMemo, useState } from 'react';
import type { RenderInputProps } from '../type';
import { Box, Button, HStack, Input, InputGroup, useDisclosure, VStack } from '@chakra-ui/react';
import type { SelectAppItemType } from '@fastgpt/global/core/workflow/template/system/abandoned/runApp/type';
import Avatar from '@fastgpt/web/components/common/Avatar';
import SelectAppModal from '../../../../SelectAppModal';
import { useTranslation } from 'next-i18next';
import { useContextSelector } from 'use-context-selector';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { getAppDetailById } from '@/web/core/app/api';
import { WorkflowActionsContext } from '@/pageComponents/app/detail/WorkflowComponents/context/workflowActionsContext';
import { AppContext } from '@/pageComponents/app/detail/context';
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyDivider from '@fastgpt/web/components/common/MyDivider';
import { getFileIcon } from '@fastgpt/global/common/file/icon';
import MyAvatar from '@fastgpt/web/components/common/Avatar';
import IconButton from '@/pageComponents/account/team/OrgManage/IconButton';
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
const FileSelectRender = ({ item, nodeId }: RenderInputProps) => {
const { t } = useTranslation();
const onChangeNode = useContextSelector(WorkflowActionsContext, (v) => v.onChangeNode);
const [urlInput, setUrlInput] = useState('');
const values = useMemo(() => {
if (Array.isArray(item.value)) {
return item.value;
}
return [];
}, [item.value]);
const maxSelectFiles = item.maxFiles || 10;
const isMaxSelected = values.length >= maxSelectFiles;
const handleAddUrl = useCallback(
(value: string) => {
if (!value.trim()) return;
onChangeNode({
nodeId,
type: 'updateInput',
key: item.key,
value: {
...item,
value: [value.trim(), ...values]
}
});
setUrlInput('');
},
[item, nodeId, onChangeNode, values]
);
const handleDeleteUrl = useCallback(
(index: number) => {
onChangeNode({
nodeId,
type: 'updateInput',
key: item.key,
value: {
...item,
value: values.filter((_, i) => i !== index)
}
});
},
[item, nodeId, onChangeNode, values]
);
return (
<Box w={'500px'}>
<Box w={'100%'}>
<InputGroup display={'flex'} alignItems={'center'}>
<MyIcon
position={'absolute'}
left={2.5}
name="common/addLight"
w={'1.2rem'}
color={'primary.600'}
zIndex={10}
/>
<Input
isDisabled={isMaxSelected}
value={urlInput}
onChange={(e) => setUrlInput(e.target.value)}
onBlur={(e) => handleAddUrl(e.target.value)}
border={'1.5px dashed'}
borderColor={'myGray.250'}
borderRadius={'md'}
pl={8}
py={1.5}
placeholder={
isMaxSelected ? t('file:reached_max_file_count') : t('chat:click_to_add_url')
}
/>
</InputGroup>
</Box>
{/* Render */}
{values.length > 0 && (
<>
<MyDivider />
<VStack>
{values.map((url, index) => {
const fileIcon = getFileIcon(url, 'common/link');
return (
<Box key={index} w={'full'}>
<HStack py={2} px={3} bg={'white'} borderRadius={'md'} border={'sm'}>
<MyAvatar src={fileIcon} w={'1.2rem'} />
<Box fontSize={'sm'} flex={'1 0 0'} title={url} className="textEllipsis">
{url}
</Box>
{/* Status icon */}
<MyIconButton
icon={'close'}
onClick={() => handleDeleteUrl(index)}
hoverColor="red.600"
hoverBg="red.50"
/>
</HStack>
</Box>
);
})}
</VStack>
</>
)}
</Box>
);
};
export default React.memo(FileSelectRender);

View File

@ -26,7 +26,7 @@ import { getAppLatestVersion } from '@fastgpt/service/core/app/version/controlle
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
import { getTimezoneCodeFromStr } from '@fastgpt/global/common/time/timezone';
import { getLocationFromIp } from '@fastgpt/service/common/geo';
import type { I18nName } from '@fastgpt/service/common/geo/type';
import { getLocale } from '@fastgpt/service/common/middle/i18n';
const formatJsonString = (data: any) => {
if (data == null) return '';
@ -40,7 +40,6 @@ export type ExportChatLogsBody = GetAppChatLogsProps & {
title: string;
sourcesMap: Record<string, { label: string }>;
logKeys: AppLogKeysEnum[];
locale?: keyof I18nName;
};
async function handler(req: ApiRequestProps<ExportChatLogsBody, {}>, res: NextApiResponse) {
@ -51,7 +50,6 @@ async function handler(req: ApiRequestProps<ExportChatLogsBody, {}>, res: NextAp
sources,
tmbIds,
chatSearch,
locale = 'en',
title,
sourcesMap,
logKeys = []
@ -61,6 +59,7 @@ async function handler(req: ApiRequestProps<ExportChatLogsBody, {}>, res: NextAp
throw new Error('缺少参数');
}
const locale = getLocale(req);
const timezoneCode = getTimezoneCodeFromStr(dateStart);
const { teamId, tmbId, app } = await authApp({

View File

@ -1,8 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import type { NextApiResponse } from 'next';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { type AppLogsListItemType } from '@/types/app';
import { Types } from '@fastgpt/service/common/mongo';
import { addDays } from 'date-fns';
import type { GetAppChatLogsParams } from '@/global/core/api/appReq.d';
import { authApp } from '@fastgpt/service/support/permission/app/auth';
import {
@ -19,12 +18,13 @@ import { getLocationFromIp } from '@fastgpt/service/common/geo';
import { AppReadChatLogPerVal } from '@fastgpt/global/support/permission/app/constant';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import type { ApiRequestProps } from '@fastgpt/service/type/next';
import { getLocale } from '@fastgpt/service/common/middle/i18n';
async function handler(
req: ApiRequestProps<GetAppChatLogsParams>,
_res: NextApiResponse
): Promise<PaginationResponse<AppLogsListItemType>> {
const { appId, dateStart, dateEnd, sources, tmbIds, chatSearch, locale = 'en' } = req.body;
const { appId, dateStart, dateEnd, sources, tmbIds, chatSearch } = req.body;
const { pageSize = 20, offset } = parsePaginationRequest(req);
@ -294,7 +294,7 @@ async function handler(
const listWithRegion = list.map((item) => {
const ip = item.region;
const region = getLocationFromIp(ip, locale);
const region = getLocationFromIp(ip, getLocale(req));
return {
...item,