diff --git a/document/content/docs/upgrading/4-14/4144.mdx b/document/content/docs/upgrading/4-14/4144.mdx index 18614247d..4216f34e8 100644 --- a/document/content/docs/upgrading/4-14/4144.mdx +++ b/document/content/docs/upgrading/4-14/4144.mdx @@ -57,6 +57,7 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4144' \ 10. 发布渠道文档链接定位错误。 11. Checkbox 在禁用状态时,hover 样式错误。 12. 模型头像缺失情况下,默认 huggingface.svg 图标显示错误。 +13. 日志导出时,结束时间会多出一天。 ## 插件 diff --git a/document/data/doc-last-modified.json b/document/data/doc-last-modified.json index 6b6a2b050..42b13fc88 100644 --- a/document/data/doc-last-modified.json +++ b/document/data/doc-last-modified.json @@ -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-09T23:33:32+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" -} \ No newline at end of file +} diff --git a/packages/global/common/i18n/utils.ts b/packages/global/common/i18n/utils.ts index 16a65c528..d8b157581 100644 --- a/packages/global/common/i18n/utils.ts +++ b/packages/global/common/i18n/utils.ts @@ -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'; +}; diff --git a/packages/service/common/geo/index.ts b/packages/service/common/geo/index.ts index c7107089b..9c815d854 100644 --- a/packages/service/common/geo/index.ts +++ b/packages/service/common/geo/index.ts @@ -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]; } } diff --git a/packages/web/i18n/en/app.json b/packages/web/i18n/en/app.json index 17b5698db..0de82f169 100644 --- a/packages/web/i18n/en/app.json +++ b/packages/web/i18n/en/app.json @@ -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", diff --git a/packages/web/i18n/zh-CN/app.json b/packages/web/i18n/zh-CN/app.json index f58186211..db1ccd15f 100644 --- a/packages/web/i18n/zh-CN/app.json +++ b/packages/web/i18n/zh-CN/app.json @@ -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": "文件链接", diff --git a/packages/web/i18n/zh-Hant/app.json b/packages/web/i18n/zh-Hant/app.json index c8ac07101..a5400e276 100644 --- a/packages/web/i18n/zh-Hant/app.json +++ b/packages/web/i18n/zh-Hant/app.json @@ -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": "檔案連結", diff --git a/projects/app/src/global/core/api/appReq.d.ts b/projects/app/src/global/core/api/appReq.d.ts index 6fe202f19..4b5d53142 100644 --- a/projects/app/src/global/core/api/appReq.d.ts +++ b/projects/app/src/global/core/api/appReq.d.ts @@ -10,7 +10,6 @@ export type GetAppChatLogsProps = { sources?: ChatSourceEnum[]; tmbIds?: string[]; chatSearch?: string; - locale?: keyof I18nName; }; export type GetAppChatLogsParams = PaginationProps; diff --git a/projects/app/src/pageComponents/app/detail/Logs/LogTable.tsx b/projects/app/src/pageComponents/app/detail/Logs/LogTable.tsx index 88e99ba73..a581a81bb 100644 --- a/projects/app/src/pageComponents/app/detail/Logs/LogTable.tsx +++ b/projects/app/src/pageComponents/app/detail/Logs/LogTable.tsx @@ -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(); + 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 ] ); diff --git a/projects/app/src/pages/api/core/app/exportChatLogs.ts b/projects/app/src/pages/api/core/app/exportChatLogs.ts index d75518fe3..6f1b92c33 100644 --- a/projects/app/src/pages/api/core/app/exportChatLogs.ts +++ b/projects/app/src/pages/api/core/app/exportChatLogs.ts @@ -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; logKeys: AppLogKeysEnum[]; - locale?: keyof I18nName; }; async function handler(req: ApiRequestProps, res: NextApiResponse) { @@ -51,7 +50,6 @@ async function handler(req: ApiRequestProps, res: NextAp sources, tmbIds, chatSearch, - locale = 'en', title, sourcesMap, logKeys = [] @@ -61,6 +59,7 @@ async function handler(req: ApiRequestProps, res: NextAp throw new Error('缺少参数'); } + const locale = getLocale(req); const timezoneCode = getTimezoneCodeFromStr(dateStart); const { teamId, tmbId, app } = await authApp({ diff --git a/projects/app/src/pages/api/core/app/getChatLogs.ts b/projects/app/src/pages/api/core/app/getChatLogs.ts index 45047dbe2..1eecbd211 100644 --- a/projects/app/src/pages/api/core/app/getChatLogs.ts +++ b/projects/app/src/pages/api/core/app/getChatLogs.ts @@ -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, _res: NextApiResponse ): Promise> { - 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,