From 44cdb3dceaf6414d3ca3e5fd864fed4ed1e335fa Mon Sep 17 00:00:00 2001 From: Panpan Date: Wed, 16 Apr 2025 21:08:13 +0800 Subject: [PATCH] feat: improve embedding sys.user_id and conversion id info usage (#18035) --- .../base/chat/chat-with-history/hooks.tsx | 23 +++++++--- .../base/chat/embedded-chatbot/hooks.tsx | 23 +++++++--- web/app/components/share/utils.ts | 44 ++++++++++++++----- web/service/base.ts | 8 ++-- web/service/fetch.ts | 34 ++++++-------- 5 files changed, 85 insertions(+), 47 deletions(-) diff --git a/web/app/components/base/chat/chat-with-history/hooks.tsx b/web/app/components/base/chat/chat-with-history/hooks.tsx index 9afaca2568..91ceaffd1e 100644 --- a/web/app/components/base/chat/chat-with-history/hooks.tsx +++ b/web/app/components/base/chat/chat-with-history/hooks.tsx @@ -16,7 +16,7 @@ import type { Feedback, } from '../types' import { CONVERSATION_ID_INFO } from '../constants' -import { buildChatItemTree } from '../utils' +import { buildChatItemTree, getProcessedSystemVariablesFromUrlParams } from '../utils' import { addFileInfos, sortAgentSorts } from '../../../tools/utils' import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' import { @@ -106,6 +106,13 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { }, [isInstalledApp, installedAppInfo, appInfo]) const appId = useMemo(() => appData?.app_id, [appData]) + const [userId, setUserId] = useState() + useEffect(() => { + getProcessedSystemVariablesFromUrlParams().then(({ user_id }) => { + setUserId(user_id) + }) + }, []) + useEffect(() => { if (appData?.site.default_language) changeLanguage(appData.site.default_language) @@ -124,18 +131,24 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { setSidebarCollapseState(localState === 'collapsed') } }, [appId]) - const [conversationIdInfo, setConversationIdInfo] = useLocalStorageState>(CONVERSATION_ID_INFO, { + const [conversationIdInfo, setConversationIdInfo] = useLocalStorageState>>(CONVERSATION_ID_INFO, { defaultValue: {}, }) - const currentConversationId = useMemo(() => conversationIdInfo?.[appId || ''] || '', [appId, conversationIdInfo]) + const currentConversationId = useMemo(() => conversationIdInfo?.[appId || '']?.[userId || 'DEFAULT'] || '', [appId, conversationIdInfo, userId]) const handleConversationIdInfoChange = useCallback((changeConversationId: string) => { if (appId) { + let prevValue = conversationIdInfo?.[appId || ''] + if (typeof prevValue === 'string') + prevValue = {} setConversationIdInfo({ ...conversationIdInfo, - [appId || '']: changeConversationId, + [appId || '']: { + ...prevValue, + [userId || 'DEFAULT']: changeConversationId, + }, }) } - }, [appId, conversationIdInfo, setConversationIdInfo]) + }, [appId, conversationIdInfo, setConversationIdInfo, userId]) const [newConversationId, setNewConversationId] = useState('') const chatShouldReloadKey = useMemo(() => { diff --git a/web/app/components/base/chat/embedded-chatbot/hooks.tsx b/web/app/components/base/chat/embedded-chatbot/hooks.tsx index a5665ab346..d6a7b230e4 100644 --- a/web/app/components/base/chat/embedded-chatbot/hooks.tsx +++ b/web/app/components/base/chat/embedded-chatbot/hooks.tsx @@ -15,7 +15,7 @@ import type { Feedback, } from '../types' import { CONVERSATION_ID_INFO } from '../constants' -import { buildChatItemTree, getProcessedInputsFromUrlParams } from '../utils' +import { buildChatItemTree, getProcessedInputsFromUrlParams, getProcessedSystemVariablesFromUrlParams } from '../utils' import { getProcessedFilesFromResponse } from '../../file-uploader/utils' import { fetchAppInfo, @@ -72,23 +72,36 @@ export const useEmbeddedChatbot = () => { }, [appInfo]) const appId = useMemo(() => appData?.app_id, [appData]) + const [userId, setUserId] = useState() + useEffect(() => { + getProcessedSystemVariablesFromUrlParams().then(({ user_id }) => { + setUserId(user_id) + }) + }, []) + useEffect(() => { if (appInfo?.site.default_language) changeLanguage(appInfo.site.default_language) }, [appInfo]) - const [conversationIdInfo, setConversationIdInfo] = useLocalStorageState>(CONVERSATION_ID_INFO, { + const [conversationIdInfo, setConversationIdInfo] = useLocalStorageState>>(CONVERSATION_ID_INFO, { defaultValue: {}, }) - const currentConversationId = useMemo(() => conversationIdInfo?.[appId || ''] || '', [appId, conversationIdInfo]) + const currentConversationId = useMemo(() => conversationIdInfo?.[appId || '']?.[userId || 'DEFAULT'] || '', [appId, conversationIdInfo, userId]) const handleConversationIdInfoChange = useCallback((changeConversationId: string) => { if (appId) { + let prevValue = conversationIdInfo?.[appId || ''] + if (typeof prevValue === 'string') + prevValue = {} setConversationIdInfo({ ...conversationIdInfo, - [appId || '']: changeConversationId, + [appId || '']: { + ...prevValue, + [userId || 'DEFAULT']: changeConversationId, + }, }) } - }, [appId, conversationIdInfo, setConversationIdInfo]) + }, [appId, conversationIdInfo, setConversationIdInfo, userId]) const [newConversationId, setNewConversationId] = useState('') const chatShouldReloadKey = useMemo(() => { diff --git a/web/app/components/share/utils.ts b/web/app/components/share/utils.ts index f3ef12e4aa..9ce891a50c 100644 --- a/web/app/components/share/utils.ts +++ b/web/app/components/share/utils.ts @@ -2,29 +2,44 @@ import { CONVERSATION_ID_INFO } from '../base/chat/constants' import { fetchAccessToken } from '@/service/share' import { getProcessedSystemVariablesFromUrlParams } from '../base/chat/utils' +export const isTokenV1 = (token: Record) => { + return !token.version +} + +export const getInitialTokenV2 = (): Record => ({ + version: 2, +}) + export const checkOrSetAccessToken = async () => { const sharedToken = globalThis.location.pathname.split('/').slice(-1)[0] - const accessToken = localStorage.getItem('token') || JSON.stringify({ [sharedToken]: '' }) - let accessTokenJson = { [sharedToken]: '' } + const userId = (await getProcessedSystemVariablesFromUrlParams()).user_id + const accessToken = localStorage.getItem('token') || JSON.stringify(getInitialTokenV2()) + let accessTokenJson = getInitialTokenV2() try { accessTokenJson = JSON.parse(accessToken) + if (isTokenV1(accessTokenJson)) + accessTokenJson = getInitialTokenV2() } catch { } - if (!accessTokenJson[sharedToken]) { - const sysUserId = (await getProcessedSystemVariablesFromUrlParams()).user_id - const res = await fetchAccessToken(sharedToken, sysUserId) - accessTokenJson[sharedToken] = res.access_token + if (!accessTokenJson[sharedToken]?.[userId || 'DEFAULT']) { + const res = await fetchAccessToken(sharedToken, userId) + accessTokenJson[sharedToken] = { + ...accessTokenJson[sharedToken], + [userId || 'DEFAULT']: res.access_token, + } localStorage.setItem('token', JSON.stringify(accessTokenJson)) } } -export const setAccessToken = async (sharedToken: string, token: string) => { - const accessToken = localStorage.getItem('token') || JSON.stringify({ [sharedToken]: '' }) - let accessTokenJson = { [sharedToken]: '' } +export const setAccessToken = async (sharedToken: string, token: string, user_id?: string) => { + const accessToken = localStorage.getItem('token') || JSON.stringify(getInitialTokenV2()) + let accessTokenJson = getInitialTokenV2() try { accessTokenJson = JSON.parse(accessToken) + if (isTokenV1(accessTokenJson)) + accessTokenJson = getInitialTokenV2() } catch { @@ -32,17 +47,22 @@ export const setAccessToken = async (sharedToken: string, token: string) => { localStorage.removeItem(CONVERSATION_ID_INFO) - accessTokenJson[sharedToken] = token + accessTokenJson[sharedToken] = { + ...accessTokenJson[sharedToken], + [user_id || 'DEFAULT']: token, + } localStorage.setItem('token', JSON.stringify(accessTokenJson)) } export const removeAccessToken = () => { const sharedToken = globalThis.location.pathname.split('/').slice(-1)[0] - const accessToken = localStorage.getItem('token') || JSON.stringify({ [sharedToken]: '' }) - let accessTokenJson = { [sharedToken]: '' } + const accessToken = localStorage.getItem('token') || JSON.stringify(getInitialTokenV2()) + let accessTokenJson = getInitialTokenV2() try { accessTokenJson = JSON.parse(accessToken) + if (isTokenV1(accessTokenJson)) + accessTokenJson = getInitialTokenV2() } catch { diff --git a/web/service/base.ts b/web/service/base.ts index f265d8052c..e3d1dc0ca2 100644 --- a/web/service/base.ts +++ b/web/service/base.ts @@ -287,9 +287,9 @@ const handleStream = ( const baseFetch = base -export const upload = (options: any, isPublicAPI?: boolean, url?: string, searchParams?: string): Promise => { +export const upload = async (options: any, isPublicAPI?: boolean, url?: string, searchParams?: string): Promise => { const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX - const token = getAccessToken(isPublicAPI) + const token = await getAccessToken(isPublicAPI) const defaultOptions = { method: 'POST', url: (url ? `${urlPrefix}${url}` : `${urlPrefix}/files/upload`) + (searchParams || ''), @@ -324,7 +324,7 @@ export const upload = (options: any, isPublicAPI?: boolean, url?: string, search }) } -export const ssePost = ( +export const ssePost = async ( url: string, fetchOptions: FetchOptionType, otherOptions: IOtherOptions, @@ -385,7 +385,7 @@ export const ssePost = ( if (body) options.body = JSON.stringify(body) - const accessToken = getAccessToken(isPublicAPI) + const accessToken = await getAccessToken(isPublicAPI) ; (options.headers as Headers).set('Authorization', `Bearer ${accessToken}`) globalThis.fetch(urlWithPrefix, options as RequestInit) diff --git a/web/service/fetch.ts b/web/service/fetch.ts index 75dd775f6c..fc41310c80 100644 --- a/web/service/fetch.ts +++ b/web/service/fetch.ts @@ -3,6 +3,8 @@ import ky from 'ky' import type { IOtherOptions } from './base' import Toast from '@/app/components/base/toast' import { API_PREFIX, MARKETPLACE_API_PREFIX, PUBLIC_API_PREFIX } from '@/config' +import { getInitialTokenV2, isTokenV1 } from '@/app/components/share/utils' +import { getProcessedSystemVariablesFromUrlParams } from '@/app/components/base/chat/utils' const TIME_OUT = 100000 @@ -67,44 +69,34 @@ const beforeErrorToast = (otherOptions: IOtherOptions): BeforeErrorHook => { } } -export const getPublicToken = () => { - let token = '' - const sharedToken = globalThis.location.pathname.split('/').slice(-1)[0] - const accessToken = localStorage.getItem('token') || JSON.stringify({ [sharedToken]: '' }) - let accessTokenJson = { [sharedToken]: '' } - try { - accessTokenJson = JSON.parse(accessToken) - } - catch { } - token = accessTokenJson[sharedToken] - return token || '' -} - -export function getAccessToken(isPublicAPI?: boolean) { +export async function getAccessToken(isPublicAPI?: boolean) { if (isPublicAPI) { const sharedToken = globalThis.location.pathname.split('/').slice(-1)[0] - const accessToken = localStorage.getItem('token') || JSON.stringify({ [sharedToken]: '' }) - let accessTokenJson = { [sharedToken]: '' } + const userId = (await getProcessedSystemVariablesFromUrlParams()).user_id + const accessToken = localStorage.getItem('token') || JSON.stringify({ version: 2 }) + let accessTokenJson: Record = { version: 2 } try { accessTokenJson = JSON.parse(accessToken) + if (isTokenV1(accessTokenJson)) + accessTokenJson = getInitialTokenV2() } catch { } - return accessTokenJson[sharedToken] + return accessTokenJson[sharedToken]?.[userId || 'DEFAULT'] } else { return localStorage.getItem('console_token') || '' } } -const beforeRequestPublicAuthorization: BeforeRequestHook = (request) => { - const token = getAccessToken(true) +const beforeRequestPublicAuthorization: BeforeRequestHook = async (request) => { + const token = await getAccessToken(true) request.headers.set('Authorization', `Bearer ${token}`) } -const beforeRequestAuthorization: BeforeRequestHook = (request) => { - const accessToken = getAccessToken() +const beforeRequestAuthorization: BeforeRequestHook = async (request) => { + const accessToken = await getAccessToken() request.headers.set('Authorization', `Bearer ${accessToken}`) }