feat:api-key和对话历史

v3.2
panyy 2026-06-29 15:58:04 +08:00
parent 9bddff2914
commit 82ea3bfa7d
9 changed files with 324 additions and 11 deletions

View File

@ -16,6 +16,7 @@ from rest_framework import serializers
from application.models import ApplicationAccessToken, ChatUserType, Application, ApplicationVersion from application.models import ApplicationAccessToken, ChatUserType, Application, ApplicationVersion
from application.serializers.application import ApplicationSerializerModel from application.serializers.application import ApplicationSerializerModel
from common.auth.common import ChatUserToken, ChatAuthentication 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.authentication_type import AuthenticationType
from common.constants.cache_version import Cache_Version from common.constants.cache_version import Cache_Version
from common.database_model_manage.database_model_manage import DatabaseModelManage 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): class AnonymousAuthenticationSerializer(serializers.Serializer):
access_token = serializers.CharField(required=True, label=_("access_token")) 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): def auth(self, request, with_valid=True):
token = request.META.get('HTTP_AUTHORIZATION') token = request.META.get('HTTP_AUTHORIZATION')
token_details = {} token_details = {}
@ -40,11 +61,19 @@ class AnonymousAuthenticationSerializer(serializers.Serializer):
access_token = self.data.get("access_token") access_token = self.data.get("access_token")
application_access_token = QuerySet(ApplicationAccessToken).filter(access_token=access_token).first() application_access_token = QuerySet(ApplicationAccessToken).filter(access_token=access_token).first()
if application_access_token is not None and application_access_token.is_active: 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 _type = AuthenticationType.CHAT_ANONYMOUS_USER
return ChatUserToken(application_access_token.application_id, None, access_token, _type, token = ChatUserToken(application_access_token.application_id, user_id, access_token, _type,
ChatUserType.ANONYMOUS_USER, chat_user_type, chat_user_id, ChatAuthentication(None)).to_token()
chat_user_id, ChatAuthentication(None)).to_token() return {'token': token, 'chat_user_type': chat_user_type}
else: else:
raise NotFound404(404, _("Invalid access_token")) raise NotFound404(404, _("Invalid access_token"))

View File

@ -36,7 +36,7 @@ class ApplicationKey(AuthBaseHandle):
operate=Operate.READ)], operate=Operate.READ)],
application_id=application_api_key.application_id, application_id=application_api_key.application_id,
chat_user_id=str(application_api_key.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): def support(self, request, token: str, get_token_details):
return str(token).startswith("application-") return str(token).startswith("application-")

View File

@ -580,9 +580,7 @@ function chatMessage(chat?: any, problem?: string, re_chat?: boolean, other_para
} }
}) })
.then(() => { .then(() => {
if (props.chatId === 'new') { emit('refresh', chartOpenId.value)
emit('refresh', chartOpenId.value)
}
getSourceDetail(chat) getSourceDetail(chat)
// if (props.type === 'debug-ai-chat') { // if (props.type === 'debug-ai-chat') {
// getSourceDetail(chat) // getSourceDetail(chat)

View File

@ -22,13 +22,17 @@ instance.interceptors.request.use(
if (config.headers === undefined) { if (config.headers === undefined) {
config.headers = new AxiosHeaders() config.headers = new AxiosHeaders()
} }
const { chatUser } = useStore() const { chatUser, login } = useStore()
const token = chatUser.getToken() const token = chatUser.getToken()
const platformToken = login.getToken()
const language = chatUser.getLanguage() const language = chatUser.getLanguage()
config.headers['Accept-Language'] = `${language}` config.headers['Accept-Language'] = `${language}`
if (token) { if (token) {
config.headers['AUTHORIZATION'] = `Bearer ${token}` config.headers['AUTHORIZATION'] = `Bearer ${token}`
} }
if (platformToken) {
config.headers['X-Platform-Authorization'] = `Bearer ${platformToken}`
}
return config return config
}, },
(err: any) => { (err: any) => {

View File

@ -16,6 +16,7 @@ interface Chat {
chatUserProfile?: ChatUserProfile chatUserProfile?: ChatUserProfile
token?: string token?: string
accessToken?: string accessToken?: string
chatUserType?: string
} }
const useChatUserStore = defineStore('chat-user', { const useChatUserStore = defineStore('chat-user', {
@ -41,6 +42,7 @@ const useChatUserStore = defineStore('chat-user', {
async getChatUserProfile() { async getChatUserProfile() {
const res = await ChatAPI.getChatUserProfile() const res = await ChatAPI.getChatUserProfile()
this.chatUserProfile = res.data this.chatUserProfile = res.data
this.setChatUserType('CHAT_USER')
return res.data return res.data
}, },
applicationProfile() { applicationProfile() {
@ -80,12 +82,20 @@ const useChatUserStore = defineStore('chat-user', {
sessionStorage.setItem(`${this.accessToken}-accessToken`, token) sessionStorage.setItem(`${this.accessToken}-accessToken`, token)
localStorage.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() { anonymousAuthentication() {
return ChatAPI.anonymousAuthentication(this.accessToken as string).then((ok) => { 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 return this.token
}) })
}, },
@ -98,12 +108,14 @@ const useChatUserStore = defineStore('chat-user', {
login(request: any, loading?: Ref<boolean>) { login(request: any, loading?: Ref<boolean>) {
return ChatAPI.login(this.accessToken as string, request, loading).then((ok) => { return ChatAPI.login(this.accessToken as string, request, loading).then((ok) => {
this.setToken(ok.data.token) this.setToken(ok.data.token)
this.setChatUserType('CHAT_USER')
return this.token return this.token
}) })
}, },
ldapLogin(request: LoginRequest, loading?: Ref<boolean>) { ldapLogin(request: LoginRequest, loading?: Ref<boolean>) {
return ChatAPI.ldapLogin(this.accessToken as string, request, loading).then((ok) => { return ChatAPI.ldapLogin(this.accessToken as string, request, loading).then((ok) => {
this.setToken(ok.data.token) this.setToken(ok.data.token)
this.setChatUserType('CHAT_USER')
return this.token return this.token
}) })
}, },
@ -112,6 +124,7 @@ const useChatUserStore = defineStore('chat-user', {
sessionStorage.removeItem(`${this.accessToken}-accessToken`) sessionStorage.removeItem(`${this.accessToken}-accessToken`)
localStorage.removeItem(`${this.accessToken}-accessToken`) localStorage.removeItem(`${this.accessToken}-accessToken`)
this.token = undefined this.token = undefined
this.chatUserType = undefined
return true return true
}) })
}, },

View File

@ -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: {} })
},
}

View File

@ -93,7 +93,10 @@ import { hexToRgba } from '@/utils/theme'
import { t } from '@/locales' import { t } from '@/locales'
import ChatHistoryDrawer from './component/ChatHistoryDrawer.vue' import ChatHistoryDrawer from './component/ChatHistoryDrawer.vue'
import chatAPI from '@/api/chat/chat' import chatAPI from '@/api/chat/chat'
import useStore from '@/stores'
import { chatLocalHistory } from '@/utils/chat-local-history'
const { chatUser } = useStore()
const AiChatRef = ref() const AiChatRef = ref()
const loading = ref(false) const loading = ref(false)
const left_loading = ref(false) const left_loading = ref(false)
@ -109,6 +112,7 @@ const applicationDetail = computed({
}, },
set: (v) => {}, set: (v) => {},
}) })
const isServerHistory = computed(() => chatUser.isServerHistoryUser())
const paginationConfig = reactive({ const paginationConfig = reactive({
current_page: 1, current_page: 1,
page_size: 20, page_size: 20,
@ -126,6 +130,15 @@ const customStyle = computed(() => {
}) })
function clearChat() { 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(() => { chatAPI.clearChat(left_loading).then(() => {
currentChatId.value = 'new' currentChatId.value = 'new'
paginationConfig.current_page = 1 paginationConfig.current_page = 1
@ -136,6 +149,17 @@ function clearChat() {
} }
function deleteLog(row: any) { 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(() => { chatAPI.deleteChat(row.id).then(() => {
if (currentChatId.value === row.id) { if (currentChatId.value === row.id) {
currentChatId.value = 'new' currentChatId.value = 'new'
@ -182,6 +206,18 @@ function getChatLog(refresh?: boolean) {
page_size: 20, 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) => { chatAPI.pageChat(page.current_page, page.page_size, left_loading).then((res: any) => {
chatLogData.value = res.data.records chatLogData.value = res.data.records
if (!refresh) { if (!refresh) {
@ -194,6 +230,24 @@ function getChatLog(refresh?: boolean) {
} }
function getChatRecord() { 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 return chatAPI
.pageChatRecord( .pageChatRecord(
currentChatId.value, currentChatId.value,
@ -241,6 +295,14 @@ function refreshFieldTitle(chatId: string, abstract: string) {
function refresh(id: string) { function refresh(id: string) {
currentChatId.value = id currentChatId.value = id
if (!isServerHistory.value) {
chatLocalHistory.saveChat(
chatUser.accessToken,
id,
currentRecordList.value?.[0]?.problem_text || t('chat.createChat'),
currentRecordList.value,
)
}
getChatLog(true) getChatLog(true)
} }
/** /**

View File

@ -93,8 +93,9 @@ import useStore from '@/stores'
import { t } from '@/locales' import { t } from '@/locales'
import ChatHistoryDrawer from './component/ChatHistoryDrawer.vue' import ChatHistoryDrawer from './component/ChatHistoryDrawer.vue'
import chatAPI from '@/api/chat/chat' import chatAPI from '@/api/chat/chat'
import { chatLocalHistory } from '@/utils/chat-local-history'
const { common } = useStore() const { common, chatUser } = useStore()
const AiChatRef = ref() const AiChatRef = ref()
const loading = ref(false) const loading = ref(false)
@ -111,6 +112,7 @@ const applicationDetail = computed({
}, },
set: (v) => {}, set: (v) => {},
}) })
const isServerHistory = computed(() => chatUser.isServerHistoryUser())
const paginationConfig = reactive({ const paginationConfig = reactive({
current_page: 1, current_page: 1,
page_size: 20, page_size: 20,
@ -134,6 +136,15 @@ const classObj = computed(() => {
}) })
function clearChat() { 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(() => { chatAPI.clearChat(left_loading).then(() => {
currentChatId.value = 'new' currentChatId.value = 'new'
paginationConfig.current_page = 1 paginationConfig.current_page = 1
@ -144,6 +155,17 @@ function clearChat() {
} }
function deleteLog(row: any) { 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(() => { chatAPI.deleteChat(row.id).then(() => {
if (currentChatId.value === row.id) { if (currentChatId.value === row.id) {
currentChatId.value = 'new' currentChatId.value = 'new'
@ -188,6 +210,18 @@ function getChatLog(refresh?: boolean) {
page_size: 20, 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) => { chatAPI.pageChat(page.current_page, page.page_size, left_loading).then((res: any) => {
chatLogData.value = res.data.records chatLogData.value = res.data.records
if (!refresh) { if (!refresh) {
@ -200,6 +234,24 @@ function getChatLog(refresh?: boolean) {
} }
function getChatRecord() { 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 return chatAPI
.pageChatRecord( .pageChatRecord(
currentChatId.value, currentChatId.value,
@ -247,6 +299,14 @@ function refreshFieldTitle(chatId: string, abstract: string) {
function refresh(id: string) { function refresh(id: string) {
currentChatId.value = id currentChatId.value = id
if (!isServerHistory.value) {
chatLocalHistory.saveChat(
chatUser.accessToken,
id,
currentRecordList.value?.[0]?.problem_text || t('chat.createChat'),
currentRecordList.value,
)
}
getChatLog(true) getChatLog(true)
} }
/** /**

View File

@ -247,6 +247,7 @@ import PdfExport from '@/components/pdf-export/index.vue'
import ChinaMobileIcon from '@/components/china-mobile-icon/index.vue' import ChinaMobileIcon from '@/components/china-mobile-icon/index.vue'
import LayoutContainer from '@/components/layout-container/index.vue' import LayoutContainer from '@/components/layout-container/index.vue'
import ContentContainer from '@/components/layout-container/ContentContainer.vue' import ContentContainer from '@/components/layout-container/ContentContainer.vue'
import { chatLocalHistory } from '@/utils/chat-local-history'
useResize() useResize()
const pdfExportRef = ref<InstanceType<typeof PdfExport>>() const pdfExportRef = ref<InstanceType<typeof PdfExport>>()
@ -313,6 +314,7 @@ const applicationDetail = computed({
}, },
set: (v) => {}, set: (v) => {},
}) })
const isServerHistory = computed(() => chatUser.isServerHistoryUser())
const chatLogData = ref<any[]>([]) const chatLogData = ref<any[]>([])
@ -334,6 +336,18 @@ function refreshFieldTitle(chatId: string, abstract: string) {
} }
function deleteLog(row: any) { 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(() => { chatAPI.deleteChat(row.id).then(() => {
if (currentChatId.value === row.id) { if (currentChatId.value === row.id) {
currentChatId.value = 'new' currentChatId.value = 'new'
@ -347,6 +361,16 @@ function deleteLog(row: any) {
} }
function clearChat() { 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(() => { chatAPI.clearChat(left_loading).then(() => {
currentChatId.value = 'new' currentChatId.value = 'new'
currentChatName.value = t('chat.createChat') currentChatName.value = t('chat.createChat')
@ -396,6 +420,21 @@ function getChatLog(refresh?: boolean) {
page_size: 20, 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) => { chatAPI.pageChat(page.current_page, page.page_size, left_loading).then((res: any) => {
chatLogData.value = res.data.records chatLogData.value = res.data.records
if (refresh) { if (refresh) {
@ -411,6 +450,25 @@ function getChatLog(refresh?: boolean) {
} }
function getChatRecord() { 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 return chatAPI
.pageChatRecord( .pageChatRecord(
currentChatId.value, currentChatId.value,
@ -464,6 +522,11 @@ const clickListHandle = (item: any) => {
function refresh(id: string) { function refresh(id: string) {
currentChatId.value = id 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) getChatLog(true)
} }