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.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"))

View File

@ -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-")

View File

@ -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)

View File

@ -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) => {

View File

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

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 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)
}
/**

View File

@ -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)
}
/**

View File

@ -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<InstanceType<typeof PdfExport>>()
@ -313,6 +314,7 @@ const applicationDetail = computed({
},
set: (v) => {},
})
const isServerHistory = computed(() => chatUser.isServerHistoryUser())
const chatLogData = ref<any[]>([])
@ -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)
}