diff --git a/apps/chat/serializers/chat_authentication.py b/apps/chat/serializers/chat_authentication.py index 960c1e1e7..7283afafd 100644 --- a/apps/chat/serializers/chat_authentication.py +++ b/apps/chat/serializers/chat_authentication.py @@ -16,6 +16,7 @@ from rest_framework import serializers from application.models import ApplicationAccessToken, ChatUserType, Application, ApplicationVersion from application.serializers.application import ApplicationSerializerModel from common.auth.common import ChatUserToken, ChatAuthentication +from common.auth.handle.impl.user_token import UserToken from common.constants.authentication_type import AuthenticationType from common.constants.cache_version import Cache_Version from common.database_model_manage.database_model_manage import DatabaseModelManage @@ -26,6 +27,26 @@ from common.utils.rsa_util import get_key_pair_by_sql class AnonymousAuthenticationSerializer(serializers.Serializer): access_token = serializers.CharField(required=True, label=_("access_token")) + @staticmethod + def get_platform_user(request): + platform_auth = request.META.get('HTTP_X_PLATFORM_AUTHORIZATION') + if platform_auth is None or not platform_auth.startswith('Bearer '): + return None + token = platform_auth[7:] + token_details = None + + def get_token_details(): + nonlocal token_details + if token_details is None: + token_details = signing.loads(token) + return token_details + + try: + user, _ = UserToken().handle(request, token, get_token_details) + return user + except Exception: + return None + def auth(self, request, with_valid=True): token = request.META.get('HTTP_AUTHORIZATION') token_details = {} @@ -40,11 +61,19 @@ class AnonymousAuthenticationSerializer(serializers.Serializer): access_token = self.data.get("access_token") application_access_token = QuerySet(ApplicationAccessToken).filter(access_token=access_token).first() if application_access_token is not None and application_access_token.is_active: - chat_user_id = token_details.get('chat_user_id') or str(uuid.uuid7()) + platform_user = self.get_platform_user(request) + if platform_user is not None: + chat_user_id = str(platform_user.id) + chat_user_type = ChatUserType.PLATFORM_USER.value + user_id = platform_user.id + else: + chat_user_id = token_details.get('chat_user_id') or str(uuid.uuid7()) + chat_user_type = ChatUserType.ANONYMOUS_USER.value + user_id = None _type = AuthenticationType.CHAT_ANONYMOUS_USER - return ChatUserToken(application_access_token.application_id, None, access_token, _type, - ChatUserType.ANONYMOUS_USER, - chat_user_id, ChatAuthentication(None)).to_token() + token = ChatUserToken(application_access_token.application_id, user_id, access_token, _type, + chat_user_type, chat_user_id, ChatAuthentication(None)).to_token() + return {'token': token, 'chat_user_type': chat_user_type} else: raise NotFound404(404, _("Invalid access_token")) diff --git a/apps/common/auth/handle/impl/application_key.py b/apps/common/auth/handle/impl/application_key.py index d4fa8933e..e5613f032 100644 --- a/apps/common/auth/handle/impl/application_key.py +++ b/apps/common/auth/handle/impl/application_key.py @@ -36,7 +36,7 @@ class ApplicationKey(AuthBaseHandle): operate=Operate.READ)], application_id=application_api_key.application_id, chat_user_id=str(application_api_key.id), - chat_user_type=ChatUserType.ANONYMOUS_USER.value) + chat_user_type=ChatUserType.APPLICATION_API_KEY.value) def support(self, request, token: str, get_token_details): return str(token).startswith("application-") diff --git a/ui/src/components/ai-chat/index.vue b/ui/src/components/ai-chat/index.vue index d926ff4fc..2e221a2f3 100644 --- a/ui/src/components/ai-chat/index.vue +++ b/ui/src/components/ai-chat/index.vue @@ -580,9 +580,7 @@ function chatMessage(chat?: any, problem?: string, re_chat?: boolean, other_para } }) .then(() => { - if (props.chatId === 'new') { - emit('refresh', chartOpenId.value) - } + emit('refresh', chartOpenId.value) getSourceDetail(chat) // if (props.type === 'debug-ai-chat') { // getSourceDetail(chat) diff --git a/ui/src/request/chat/index.ts b/ui/src/request/chat/index.ts index bb5e344ca..e1bbabe02 100644 --- a/ui/src/request/chat/index.ts +++ b/ui/src/request/chat/index.ts @@ -22,13 +22,17 @@ instance.interceptors.request.use( if (config.headers === undefined) { config.headers = new AxiosHeaders() } - const { chatUser } = useStore() + const { chatUser, login } = useStore() const token = chatUser.getToken() + const platformToken = login.getToken() const language = chatUser.getLanguage() config.headers['Accept-Language'] = `${language}` if (token) { config.headers['AUTHORIZATION'] = `Bearer ${token}` } + if (platformToken) { + config.headers['X-Platform-Authorization'] = `Bearer ${platformToken}` + } return config }, (err: any) => { diff --git a/ui/src/stores/modules/chat-user.ts b/ui/src/stores/modules/chat-user.ts index 125b9afd7..a3dbb5ff1 100644 --- a/ui/src/stores/modules/chat-user.ts +++ b/ui/src/stores/modules/chat-user.ts @@ -16,6 +16,7 @@ interface Chat { chatUserProfile?: ChatUserProfile token?: string accessToken?: string + chatUserType?: string } const useChatUserStore = defineStore('chat-user', { @@ -41,6 +42,7 @@ const useChatUserStore = defineStore('chat-user', { async getChatUserProfile() { const res = await ChatAPI.getChatUserProfile() this.chatUserProfile = res.data + this.setChatUserType('CHAT_USER') return res.data }, applicationProfile() { @@ -80,12 +82,20 @@ const useChatUserStore = defineStore('chat-user', { sessionStorage.setItem(`${this.accessToken}-accessToken`, token) localStorage.setItem(`${this.accessToken}-accessToken`, token) }, + setChatUserType(chatUserType?: string) { + this.chatUserType = chatUserType + }, + isServerHistoryUser() { + return this.chatUserType === 'PLATFORM_USER' || this.chatUserType === 'CHAT_USER' + }, /** *匿名认证 */ anonymousAuthentication() { return ChatAPI.anonymousAuthentication(this.accessToken as string).then((ok) => { - this.setToken(ok.data) + const data = typeof ok.data === 'string' ? { token: ok.data, chat_user_type: undefined } : ok.data + this.setToken(data.token) + this.setChatUserType(data.chat_user_type) return this.token }) }, @@ -98,12 +108,14 @@ const useChatUserStore = defineStore('chat-user', { login(request: any, loading?: Ref) { return ChatAPI.login(this.accessToken as string, request, loading).then((ok) => { this.setToken(ok.data.token) + this.setChatUserType('CHAT_USER') return this.token }) }, ldapLogin(request: LoginRequest, loading?: Ref) { return ChatAPI.ldapLogin(this.accessToken as string, request, loading).then((ok) => { this.setToken(ok.data.token) + this.setChatUserType('CHAT_USER') return this.token }) }, @@ -112,6 +124,7 @@ const useChatUserStore = defineStore('chat-user', { sessionStorage.removeItem(`${this.accessToken}-accessToken`) localStorage.removeItem(`${this.accessToken}-accessToken`) this.token = undefined + this.chatUserType = undefined return true }) }, diff --git a/ui/src/utils/chat-local-history.ts b/ui/src/utils/chat-local-history.ts new file mode 100644 index 000000000..d459a0fb5 --- /dev/null +++ b/ui/src/utils/chat-local-history.ts @@ -0,0 +1,84 @@ +const pageResult = (records: any[], currentPage: number, pageSize: number) => { + const total = records.length + const start = (currentPage - 1) * pageSize + return { + total, + records: records.slice(start, start + pageSize), + } +} + +const getKey = (accessToken?: string) => `chat-local-history:${accessToken || 'default'}` + +const readStore = (accessToken?: string) => { + try { + return JSON.parse(localStorage.getItem(getKey(accessToken)) || '{"chats":[],"records":{}}') + } catch { + return { chats: [], records: {} } + } +} + +const writeStore = (accessToken: string | undefined, store: any) => { + localStorage.setItem(getKey(accessToken), JSON.stringify(store)) +} + +const normalizeRecord = (record: any) => { + const now = new Date().toISOString() + return { + ...record, + id: record.id || record.record_id, + record_id: record.record_id || record.id, + create_time: record.create_time || now, + update_time: record.update_time || now, + write_ed: true, + } +} + +export const chatLocalHistory = { + pageChats(accessToken: string | undefined, currentPage: number, pageSize: number) { + const store = readStore(accessToken) + return pageResult(store.chats || [], currentPage, pageSize) + }, + pageRecords( + accessToken: string | undefined, + chatId: string, + currentPage: number, + pageSize: number, + ) { + const store = readStore(accessToken) + const records = (store.records?.[chatId] || []).map(normalizeRecord) + return pageResult(records.slice().reverse(), currentPage, pageSize) + }, + saveChat(accessToken: string | undefined, chatId: string, abstract: string, records: any[]) { + if (!chatId || chatId === 'new') { + return + } + const store = readStore(accessToken) + const now = new Date().toISOString() + const chats = (store.chats || []).filter((item: any) => item.id !== chatId) + const oldChat = (store.chats || []).find((item: any) => item.id === chatId) + store.chats = [ + { + id: chatId, + application_id: records?.[0]?.application_id, + abstract: abstract || records?.[0]?.problem_text || '', + create_time: oldChat?.create_time || now, + update_time: now, + }, + ...chats, + ] + store.records = store.records || {} + store.records[chatId] = records.map(normalizeRecord) + writeStore(accessToken, store) + }, + deleteChat(accessToken: string | undefined, chatId: string) { + const store = readStore(accessToken) + store.chats = (store.chats || []).filter((item: any) => item.id !== chatId) + if (store.records) { + delete store.records[chatId] + } + writeStore(accessToken, store) + }, + clear(accessToken: string | undefined) { + writeStore(accessToken, { chats: [], records: {} }) + }, +} diff --git a/ui/src/views/chat/embed/index.vue b/ui/src/views/chat/embed/index.vue index 6ab4caf3d..68356b627 100644 --- a/ui/src/views/chat/embed/index.vue +++ b/ui/src/views/chat/embed/index.vue @@ -93,7 +93,10 @@ import { hexToRgba } from '@/utils/theme' import { t } from '@/locales' import ChatHistoryDrawer from './component/ChatHistoryDrawer.vue' import chatAPI from '@/api/chat/chat' +import useStore from '@/stores' +import { chatLocalHistory } from '@/utils/chat-local-history' +const { chatUser } = useStore() const AiChatRef = ref() const loading = ref(false) const left_loading = ref(false) @@ -109,6 +112,7 @@ const applicationDetail = computed({ }, set: (v) => {}, }) +const isServerHistory = computed(() => chatUser.isServerHistoryUser()) const paginationConfig = reactive({ current_page: 1, page_size: 20, @@ -126,6 +130,15 @@ const customStyle = computed(() => { }) function clearChat() { + if (!isServerHistory.value) { + chatLocalHistory.clear(chatUser.accessToken) + currentChatId.value = 'new' + paginationConfig.current_page = 1 + paginationConfig.total = 0 + currentRecordList.value = [] + getChatLog() + return + } chatAPI.clearChat(left_loading).then(() => { currentChatId.value = 'new' paginationConfig.current_page = 1 @@ -136,6 +149,17 @@ function clearChat() { } function deleteLog(row: any) { + if (!isServerHistory.value) { + chatLocalHistory.deleteChat(chatUser.accessToken, row.id) + if (currentChatId.value === row.id) { + currentChatId.value = 'new' + paginationConfig.current_page = 1 + paginationConfig.total = 0 + currentRecordList.value = [] + } + chatLogData.value = chatLogData.value.filter((item) => item.id !== row.id) + return + } chatAPI.deleteChat(row.id).then(() => { if (currentChatId.value === row.id) { currentChatId.value = 'new' @@ -182,6 +206,18 @@ function getChatLog(refresh?: boolean) { page_size: 20, } + if (!isServerHistory.value) { + const data = chatLocalHistory.pageChats(chatUser.accessToken, page.current_page, page.page_size) + chatLogData.value = data.records + if (!refresh) { + paginationConfig.current_page = 1 + paginationConfig.total = 0 + currentRecordList.value = [] + currentChatId.value = 'new' + } + return + } + chatAPI.pageChat(page.current_page, page.page_size, left_loading).then((res: any) => { chatLogData.value = res.data.records if (!refresh) { @@ -194,6 +230,24 @@ function getChatLog(refresh?: boolean) { } function getChatRecord() { + if (!isServerHistory.value) { + const res = chatLocalHistory.pageRecords( + chatUser.accessToken, + currentChatId.value, + paginationConfig.current_page, + paginationConfig.page_size, + ) + paginationConfig.total = res.total + currentRecordList.value = [...res.records, ...currentRecordList.value].sort((a, b) => + a.create_time.localeCompare(b.create_time), + ) + if (paginationConfig.current_page === 1) { + nextTick(() => { + AiChatRef.value.setScrollBottom() + }) + } + return Promise.resolve() + } return chatAPI .pageChatRecord( currentChatId.value, @@ -241,6 +295,14 @@ function refreshFieldTitle(chatId: string, abstract: string) { function refresh(id: string) { currentChatId.value = id + if (!isServerHistory.value) { + chatLocalHistory.saveChat( + chatUser.accessToken, + id, + currentRecordList.value?.[0]?.problem_text || t('chat.createChat'), + currentRecordList.value, + ) + } getChatLog(true) } /** diff --git a/ui/src/views/chat/mobile/index.vue b/ui/src/views/chat/mobile/index.vue index 31f9cc0a8..8b14a5768 100644 --- a/ui/src/views/chat/mobile/index.vue +++ b/ui/src/views/chat/mobile/index.vue @@ -93,8 +93,9 @@ import useStore from '@/stores' import { t } from '@/locales' import ChatHistoryDrawer from './component/ChatHistoryDrawer.vue' import chatAPI from '@/api/chat/chat' +import { chatLocalHistory } from '@/utils/chat-local-history' -const { common } = useStore() +const { common, chatUser } = useStore() const AiChatRef = ref() const loading = ref(false) @@ -111,6 +112,7 @@ const applicationDetail = computed({ }, set: (v) => {}, }) +const isServerHistory = computed(() => chatUser.isServerHistoryUser()) const paginationConfig = reactive({ current_page: 1, page_size: 20, @@ -134,6 +136,15 @@ const classObj = computed(() => { }) function clearChat() { + if (!isServerHistory.value) { + chatLocalHistory.clear(chatUser.accessToken) + currentChatId.value = 'new' + paginationConfig.current_page = 1 + paginationConfig.total = 0 + currentRecordList.value = [] + getChatLog() + return + } chatAPI.clearChat(left_loading).then(() => { currentChatId.value = 'new' paginationConfig.current_page = 1 @@ -144,6 +155,17 @@ function clearChat() { } function deleteLog(row: any) { + if (!isServerHistory.value) { + chatLocalHistory.deleteChat(chatUser.accessToken, row.id) + if (currentChatId.value === row.id) { + currentChatId.value = 'new' + paginationConfig.current_page = 1 + paginationConfig.total = 0 + currentRecordList.value = [] + } + chatLogData.value = chatLogData.value.filter((item) => item.id !== row.id) + return + } chatAPI.deleteChat(row.id).then(() => { if (currentChatId.value === row.id) { currentChatId.value = 'new' @@ -188,6 +210,18 @@ function getChatLog(refresh?: boolean) { page_size: 20, } + if (!isServerHistory.value) { + const data = chatLocalHistory.pageChats(chatUser.accessToken, page.current_page, page.page_size) + chatLogData.value = data.records + if (!refresh) { + paginationConfig.current_page = 1 + paginationConfig.total = 0 + currentRecordList.value = [] + currentChatId.value = 'new' + } + return + } + chatAPI.pageChat(page.current_page, page.page_size, left_loading).then((res: any) => { chatLogData.value = res.data.records if (!refresh) { @@ -200,6 +234,24 @@ function getChatLog(refresh?: boolean) { } function getChatRecord() { + if (!isServerHistory.value) { + const res = chatLocalHistory.pageRecords( + chatUser.accessToken, + currentChatId.value, + paginationConfig.current_page, + paginationConfig.page_size, + ) + paginationConfig.total = res.total + currentRecordList.value = [...res.records, ...currentRecordList.value].sort((a, b) => + a.create_time.localeCompare(b.create_time), + ) + if (paginationConfig.current_page === 1) { + nextTick(() => { + AiChatRef.value.setScrollBottom() + }) + } + return Promise.resolve() + } return chatAPI .pageChatRecord( currentChatId.value, @@ -247,6 +299,14 @@ function refreshFieldTitle(chatId: string, abstract: string) { function refresh(id: string) { currentChatId.value = id + if (!isServerHistory.value) { + chatLocalHistory.saveChat( + chatUser.accessToken, + id, + currentRecordList.value?.[0]?.problem_text || t('chat.createChat'), + currentRecordList.value, + ) + } getChatLog(true) } /** diff --git a/ui/src/views/chat/pc/index.vue b/ui/src/views/chat/pc/index.vue index 47720beae..8df0ff225 100644 --- a/ui/src/views/chat/pc/index.vue +++ b/ui/src/views/chat/pc/index.vue @@ -247,6 +247,7 @@ import PdfExport from '@/components/pdf-export/index.vue' import ChinaMobileIcon from '@/components/china-mobile-icon/index.vue' import LayoutContainer from '@/components/layout-container/index.vue' import ContentContainer from '@/components/layout-container/ContentContainer.vue' +import { chatLocalHistory } from '@/utils/chat-local-history' useResize() const pdfExportRef = ref>() @@ -313,6 +314,7 @@ const applicationDetail = computed({ }, set: (v) => {}, }) +const isServerHistory = computed(() => chatUser.isServerHistoryUser()) const chatLogData = ref([]) @@ -334,6 +336,18 @@ function refreshFieldTitle(chatId: string, abstract: string) { } function deleteLog(row: any) { + if (!isServerHistory.value) { + chatLocalHistory.deleteChat(chatUser.accessToken, row.id) + if (currentChatId.value === row.id) { + currentChatId.value = 'new' + currentChatName.value = t('chat.createChat') + paginationConfig.value.current_page = 1 + paginationConfig.value.total = 0 + currentRecordList.value = [] + } + chatLogData.value = chatLogData.value.filter((item) => item.id !== row.id) + return + } chatAPI.deleteChat(row.id).then(() => { if (currentChatId.value === row.id) { currentChatId.value = 'new' @@ -347,6 +361,16 @@ function deleteLog(row: any) { } function clearChat() { + if (!isServerHistory.value) { + chatLocalHistory.clear(chatUser.accessToken) + currentChatId.value = 'new' + currentChatName.value = t('chat.createChat') + paginationConfig.value.current_page = 1 + paginationConfig.value.total = 0 + currentRecordList.value = [] + getChatLog() + return + } chatAPI.clearChat(left_loading).then(() => { currentChatId.value = 'new' currentChatName.value = t('chat.createChat') @@ -396,6 +420,21 @@ function getChatLog(refresh?: boolean) { page_size: 20, } + if (!isServerHistory.value) { + const data = chatLocalHistory.pageChats(chatUser.accessToken, page.current_page, page.page_size) + chatLogData.value = data.records + if (refresh) { + currentChatName.value = chatLogData.value?.[0]?.abstract || currentChatName.value + } else { + paginationConfig.value.current_page = 1 + paginationConfig.value.total = 0 + currentRecordList.value = [] + currentChatId.value = 'new' + currentChatName.value = t('chat.createChat') + } + return + } + chatAPI.pageChat(page.current_page, page.page_size, left_loading).then((res: any) => { chatLogData.value = res.data.records if (refresh) { @@ -411,6 +450,25 @@ function getChatLog(refresh?: boolean) { } function getChatRecord() { + if (!isServerHistory.value) { + const res = chatLocalHistory.pageRecords( + chatUser.accessToken, + currentChatId.value, + paginationConfig.value.current_page, + paginationConfig.value.page_size, + ) + paginationConfig.value.total = res.total + const list = res.records + currentRecordList.value = [...list, ...currentRecordList.value].sort((a, b) => + a.create_time.localeCompare(b.create_time), + ) + if (paginationConfig.value.current_page === 1) { + nextTick(() => { + AiChatRef.value.setScrollBottom() + }) + } + return Promise.resolve() + } return chatAPI .pageChatRecord( currentChatId.value, @@ -464,6 +522,11 @@ const clickListHandle = (item: any) => { function refresh(id: string) { currentChatId.value = id + if (!isServerHistory.value) { + const abstract = currentRecordList.value?.[0]?.problem_text || t('chat.createChat') + currentChatName.value = abstract + chatLocalHistory.saveChat(chatUser.accessToken, id, abstract, currentRecordList.value) + } getChatLog(true) }