From 1d6b00c2e1fa233eb73b35182db49948a9518a18 Mon Sep 17 00:00:00 2001 From: Germey Date: Sat, 30 Sep 2023 20:32:00 +0800 Subject: [PATCH] integrate api --- src/components/chat/InputBox.vue | 15 +++- src/components/chat/Message.vue | 3 +- src/operators/chat/constants.ts | 5 ++ src/operators/chat/index.ts | 2 + src/operators/chat/models.ts | 52 ++++++++++++++ src/operators/chat/operator.ts | 40 +++++++++++ src/operators/index.ts | 1 + src/pages/chat/Conversation.vue | 113 ++++++++++++++++++++++++++++--- src/store/index.ts | 47 +++++++------ src/store/models.ts | 49 ++++++++++++++ 10 files changed, 293 insertions(+), 34 deletions(-) create mode 100644 src/operators/chat/constants.ts create mode 100644 src/operators/chat/index.ts create mode 100644 src/operators/chat/models.ts create mode 100644 src/operators/chat/operator.ts create mode 100644 src/store/models.ts diff --git a/src/components/chat/InputBox.vue b/src/components/chat/InputBox.vue index 99beb0a..2f141bc 100644 --- a/src/components/chat/InputBox.vue +++ b/src/components/chat/InputBox.vue @@ -1,6 +1,12 @@ @@ -23,7 +29,7 @@ export default defineComponent({ required: true } }, - emits: ['update:modelValue'], + emits: ['update:modelValue', 'submit'], data() { return { value: this.modelValue @@ -38,6 +44,11 @@ export default defineComponent({ this.value = val; } } + }, + methods: { + onSubmit() { + this.$emit('submit', this.value); + } } }); diff --git a/src/components/chat/Message.vue b/src/components/chat/Message.vue index 32d9754..abe12c8 100644 --- a/src/components/chat/Message.vue +++ b/src/components/chat/Message.vue @@ -14,6 +14,7 @@ import { ElImage, ElButton } from 'element-plus'; import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; import copy from 'copy-to-clipboard'; import MarkdownRenderer from '@/components/common/MarkdownRenderer.vue'; +import { IChatMessage } from '@/operators'; interface IData { copied: boolean; @@ -31,7 +32,7 @@ export default defineComponent({ }, props: { message: { - type: Object as () => IMessage, + type: Object as () => IChatMessage, required: true } }, diff --git a/src/operators/chat/constants.ts b/src/operators/chat/constants.ts new file mode 100644 index 0000000..948a6f7 --- /dev/null +++ b/src/operators/chat/constants.ts @@ -0,0 +1,5 @@ +export const API_ID_CHATGPT = '3333'; +export const API_ID_CHATGPT4 = '3333'; +export const API_ID_CHATGPT_BROWSING = '3333'; +export const API_ID_CHATGPT4_BROWSING = '3333'; +export const API_ID_CHATGPT_16k = '3333'; diff --git a/src/operators/chat/index.ts b/src/operators/chat/index.ts new file mode 100644 index 0000000..4a6cbae --- /dev/null +++ b/src/operators/chat/index.ts @@ -0,0 +1,2 @@ +export * from './models'; +export * from './operator'; diff --git a/src/operators/chat/models.ts b/src/operators/chat/models.ts new file mode 100644 index 0000000..66f8c96 --- /dev/null +++ b/src/operators/chat/models.ts @@ -0,0 +1,52 @@ +export interface IError { + code: string; + detail?: string; +} + +export enum ChatMessageState { + PENDING = 'pending', + ANSWERING = 'answering', + FINISHED = 'finished', + FAILED = 'failed' +} + +export const ROLE_SYSTEM = 'system'; +export const ROLE_ASSISTANT = 'assistant'; +export const ROLE_USER = 'user'; + +export interface IChatMessage { + state?: ChatMessageState; + content: string; + role?: typeof ROLE_SYSTEM | typeof ROLE_ASSISTANT | typeof ROLE_USER; + error?: IError; +} + +export const CHAT_MODEL_CHATGPT = 'chatgpt'; +export const CHAT_MODEL_CHATGPT4 = 'chatgpt4'; +export const CHAT_MODEL_CHATGPT_16K = 'chatgpt-16k'; +export const CHAT_MODEL_CHATGPT_BROWSING = 'chatgpt-browsing'; +export const CHAT_MODEL_CHATGPT4_BROWSING = 'chatgpt4-browsing'; + +export type IChatModel = + | typeof CHAT_MODEL_CHATGPT + | typeof CHAT_MODEL_CHATGPT4 + | typeof CHAT_MODEL_CHATGPT_16K + | typeof CHAT_MODEL_CHATGPT_BROWSING + | typeof CHAT_MODEL_CHATGPT4_BROWSING; + +export interface IChatRequest { + question: string; + stateful?: boolean; + conversation_id?: string; +} + +export interface IChatResponse { + answer: string; + delta_answer: string; + conversation_id?: string; +} + +export interface IChatOptions { + stream?: (response: IChatResponse) => void; + api_id: string; +} diff --git a/src/operators/chat/operator.ts b/src/operators/chat/operator.ts new file mode 100644 index 0000000..dce3d2e --- /dev/null +++ b/src/operators/chat/operator.ts @@ -0,0 +1,40 @@ +import axios, { AxiosResponse, AxiosRequestConfig } from 'axios'; +import { IChatOptions, IChatRequest, IChatResponse } from './models'; +import store from '@/store'; +import { IApplication } from '@/store/models'; +class ChatOperator { + async request(data: IChatRequest, config?: IChatOptions): Promise> { + const applications: IApplication[] = store.getters.applications; + // find related application + const application = applications?.filter((application: IApplication) => { + return (application.api_id = config?.api_id); + })?.[0]; + if (!application) { + return Promise.reject('no application'); + } + const token = application?.credential?.token; + if (!token) { + return Promise.reject('no token'); + } + return await axios.post(`/chatgpt`, data, { + headers: { + authorization: `Bearer ${token}`, + accept: 'application/x-ndjson', + 'content-type': 'application/json' + }, + onDownloadProgress: (event) => { + const response = event.target.response; + const lines = response.split('\r\n').filter((line: string) => !!line); + const lastLine = lines[lines.length - 1]; + if (lastLine) { + const jsonData = JSON.parse(lastLine); + if (config?.stream) { + config?.stream(jsonData as IChatResponse); + } + } + } + }); + } +} + +export const chatOperator = new ChatOperator(); diff --git a/src/operators/index.ts b/src/operators/index.ts index cbcf3b4..0391628 100644 --- a/src/operators/index.ts +++ b/src/operators/index.ts @@ -2,3 +2,4 @@ export * from './instance'; export * from './application'; export * from './user'; export * from './api'; +export * from './chat'; diff --git a/src/pages/chat/Conversation.vue b/src/pages/chat/Conversation.vue index f7a4339..6784f19 100644 --- a/src/pages/chat/Conversation.vue +++ b/src/pages/chat/Conversation.vue @@ -1,29 +1,47 @@ diff --git a/src/store/index.ts b/src/store/index.ts index 3265cbc..f8dda98 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -2,45 +2,47 @@ import { createStore, ActionContext } from 'vuex'; import createPersistedState from 'vuex-persistedstate'; import { removeCookies } from '@/utils/cookie'; import { ENDPOINT } from '@/constants'; - -export interface ISetting { - stream?: boolean; - endpoint?: string; -} - -export interface IState { - accessToken: string | undefined; - setting: ISetting; -} +import { IApplication } from '@/operators'; +import { ISetting, IState, IToken } from './models'; const store = createStore({ state(): IState { return { - accessToken: undefined, + token: { + access: undefined, + refresh: undefined + }, setting: { endpoint: ENDPOINT, stream: false - } + }, + applications: [] }; }, mutations: { - setAccessToken(state: IState, payload: string): void { - state.accessToken = payload; + setToken(state: IState, payload: IToken): void { + state.token = { + ...state.token, + ...payload + }; }, setSetting(state: IState, payload: ISetting): void { state.setting = { ...state.setting, ...payload }; + }, + setApplications(state: IState, payload: IApplication[]): void { + state.applications = payload; } }, actions: { - resetAuth({ commit }) { - commit('setAccessToken', undefined); + resetToken({ commit }) { + commit('setToken', {}); removeCookies(); }, - setAccessToken({ commit }: ActionContext, payload: string) { - commit('setAccessToken', payload); + setToken({ commit }: ActionContext, payload: IToken) { + commit('setToken', payload); }, setSetting({ commit }: ActionContext, payload: ISetting) { commit('setSetting', payload); @@ -48,13 +50,16 @@ const store = createStore({ }, getters: { authenticated(state): boolean { - return !!state.accessToken; + return !!state.token.access; }, - accessToken(state): string | undefined { - return state.accessToken; + token(state): IToken { + return state.token; }, setting(state): ISetting | undefined { return state.setting; + }, + applications(state): IApplication[] | undefined { + return state.applications; } }, plugins: [createPersistedState()] diff --git a/src/store/models.ts b/src/store/models.ts new file mode 100644 index 0000000..79c65f7 --- /dev/null +++ b/src/store/models.ts @@ -0,0 +1,49 @@ +export interface IToken { + access?: string; + refresh?: string; +} + +export interface ISetting { + stream?: boolean; + endpoint?: string; +} + +export enum IApplicationType { + API = 'Api', + PROXY = 'Proxy' +} + +export enum ICredentialType { + TOKEN = 'Token', + IDENTITY = 'Identity' +} + +export interface ICredential { + type: ICredentialType; + name?: string; + token?: string; + username?: string; + password?: string; + created_at?: string; + updated_at?: string; +} + +export interface IApplication { + id?: string; + type?: IApplicationType; + api_id?: string; + proxy_id?: string; + api_key?: string; + user_id?: string; + remaining_amount?: number; + used_amount?: number; + credential?: ICredential; + created_at?: string; + updated_at?: string; +} + +export interface IState { + token: IToken; + setting: ISetting; + applications: IApplication[]; +}