Skip to content

Commit

Permalink
update experience for chat
Browse files Browse the repository at this point in the history
  • Loading branch information
Germey committed Oct 3, 2023
1 parent 50e2da1 commit 91c7417
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 41 deletions.
40 changes: 40 additions & 0 deletions src/components/chat/AnsweringMark.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<template>
<div class="box"></div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'AnwseringMark',
data() {
return {};
},
computed: {
conversationId() {
return this.$route.params?.id?.toString();
}
}
});
</script>

<style lang="scss">
@keyframes blink {
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.box {
width: 2px;
height: 16px;
margin-top: 3px;
background-color: var(--el-color-black);
animation: blink 1s infinite;
}
</style>
11 changes: 7 additions & 4 deletions src/components/chat/Message.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,25 @@
<div :class="'message ' + message.role">
<div class="content">
<markdown-renderer :content="message?.content" />
<answering-mark v-if="message.state === messageState.PENDING" />
</div>
</div>
</template>

<script lang="ts">
import { IContent, IError, IMessage, IMessageState } from '@/operators/message/models';
import { IContent, IError, IMessage } from '@/operators/message/models';
import { defineComponent } from 'vue';
import AnsweringMark from './AnsweringMark.vue';
// import MessageContent from './MessageContent.vue';
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';
import { IChatMessage, IChatMessageState } from '@/operators';
interface IData {
copied: boolean;
messageState: typeof IMessageState;
messageState: typeof IChatMessageState;
}
export default defineComponent({
Expand All @@ -28,6 +30,7 @@ export default defineComponent({
// ElImage,
// ElButton,
// FontAwesomeIcon
AnsweringMark,
MarkdownRenderer
},
props: {
Expand All @@ -40,7 +43,7 @@ export default defineComponent({
data(): IData {
return {
copied: false,
messageState: IMessageState
messageState: IChatMessageState
};
},
computed: {
Expand Down
4 changes: 2 additions & 2 deletions src/components/chat/ModelSelector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,14 @@ export default defineComponent({
const options = group.options;
if (options && options.length > 0) {
this.value = options[0];
this.$emit('select', options[0]);
this.$emit('update:modelValue', options[0]);
this.$emit('select', options[0]);
}
},
onCommandChange(command: IChatModel) {
this.value = command;
this.$emit('select', command);
this.$emit('update:modelValue', command);
this.$emit('select', command);
}
}
});
Expand Down
13 changes: 3 additions & 10 deletions src/components/common/ApiStatus.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<span v-if="application && !initializing" class="info">
<span v-if="application" class="info">
{{ $t('conversation.message.usedCount') }}: {{ application?.used_amount }}
{{ $t('conversation.message.remainingCount') }}: {{ application?.remaining_amount }}
</span>
Expand All @@ -8,21 +8,14 @@
<script lang="ts">
import { defineComponent } from 'vue';
import { IApplication } from '@/operators';
import { ElSkeleton } from 'element-plus';
export default defineComponent({
name: 'ApiStatus',
components: {
ElSkeleton
},
components: {},
props: {
application: {
type: Object as () => IApplication,
required: true
},
initializing: {
type: Boolean,
required: true
}
},
data() {
Expand All @@ -36,6 +29,6 @@ export default defineComponent({
<style lang="scss">
.info {
font-size: 14px;
color: var(--el-color-black);
color: var(--el-text-color-regular);
}
</style>
2 changes: 1 addition & 1 deletion src/components/conversation/AnsweringMark.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { defineComponent } from 'vue';
export default defineComponent({
name: 'MessageContent',
name: 'AnwseringMark',
data() {
return {};
},
Expand Down
28 changes: 23 additions & 5 deletions src/operators/chat/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,38 @@ export interface IChatMessage {
error?: IError;
}

export interface IChatOptions {
stream?: (response: IChatResponse) => void;
export interface IChatHistory {
conversation_id: string;
messages: IChatMessage[];
}

export interface IChatAskOptions {
stream?: (response: IChatAskResponse) => void;
token: string;
endpoint?: string;
endpoint: string;
path: string;
}

export interface IChatHistoryOptions {
endpoint: string;
path: string;
}

export interface IChatRequest {
export interface IChatAskRequest {
question: string;
stateful?: boolean;
conversation_id?: string;
}

export interface IChatResponse {
export interface IChatAskResponse {
answer: string;
delta_answer: string;
conversation_id?: string;
}

export interface IChatHistoryRequest {
action: string;
conversation_id: string;
}

export type IChatHistoryResponse = IChatHistory;
25 changes: 20 additions & 5 deletions src/operators/chat/operator.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import axios, { AxiosResponse, AxiosRequestConfig } from 'axios';
import { IChatOptions, IChatRequest, IChatResponse } from './models';
import store from '@/store';
import {
IChatHistoryRequest,
IChatHistoryResponse,
IChatAskOptions,
IChatAskRequest,
IChatAskResponse,
IChatHistoryOptions
} from './models';

class ChatOperator {
async request(data: IChatRequest, options: IChatOptions): Promise<AxiosResponse<IChatResponse>> {
return await axios.post(`/chatgpt`, data, {
async ask(data: IChatAskRequest, options: IChatAskOptions): Promise<AxiosResponse<IChatAskResponse>> {
return await axios.post(options.path, data, {
headers: {
authorization: `Bearer ${options.token}`,
accept: 'application/x-ndjson',
Expand All @@ -19,12 +25,21 @@ class ChatOperator {
if (lastLine) {
const jsonData = JSON.parse(lastLine);
if (options?.stream) {
options?.stream(jsonData as IChatResponse);
options?.stream(jsonData as IChatAskResponse);
}
}
}
});
}

async history(data: IChatHistoryRequest, options: IChatHistoryOptions): Promise<AxiosResponse<IChatHistoryResponse>> {
return await axios.post(options.path, data, {
headers: {
'content-type': 'application/json'
},
baseURL: options.endpoint
});
}
}

export const chatOperator = new ChatOperator();
68 changes: 54 additions & 14 deletions src/pages/chat/Conversation.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div class="page">
<model-selector v-model="model" class="model-selector" @select="onLoadModel" />
<api-status v-if="application" :application="application" :initializing="initializing" />
<model-selector v-model="model" class="model-selector" @select="onSelectModel" />
<api-status :application="application" />
<div class="main">
<introduction v-if="messages && messages.length === 0" />
<div v-else class="messages">
Expand All @@ -17,26 +17,26 @@
</template>

<script lang="ts">
import { IMessage, IMessageState, ROLE_ASSISTANT, ROLE_USER } from '@/operators/message/models';
import { ROLE_ASSISTANT, ROLE_USER } from '@/operators/message/models';
import { defineComponent } from 'vue';
import Message from '@/components/chat/Message.vue';
import {
IChatModel,
IChatMessage,
CHAT_MODEL_CHATGPT,
IChatResponse,
API_ID_CHATGPT,
applicationOperator,
IApplication,
IChatMessageState
IChatMessageState,
IChatAskResponse
} from '@/operators';
import InputBox from '@/components/chat/InputBox.vue';
import ModelSelector from '@/components/chat/ModelSelector.vue';
import { chatOperator } from '@/operators';
import { ERROR_CODE_CANCELED, ERROR_CODE_UNKNOWN } from '@/constants/errorCode';
import axios from 'axios';
import ApiStatus from '@/components/common/ApiStatus.vue';
import { ROUTE_CHAT_CONVERSATION } from '@/router';
import { ROUTE_CHAT_CONVERSATION, ROUTE_CHAT_CONVERSATION_NEW } from '@/router';
export interface IData {
messages: IChatMessage[];
Expand Down Expand Up @@ -71,11 +71,45 @@ export default defineComponent({
return this.$route.params.id?.toString();
}
},
mounted() {
async mounted() {
console.log('mounted');
this.onLoadModel();
await this.onLoadModel();
await this.onLoadHistory();
},
methods: {
async onCreateNewConversation() {
this.messages = [];
await this.$router.push({
name: ROUTE_CHAT_CONVERSATION_NEW
});
},
async onLoadHistory() {
if (!this.conversationId) {
return;
}
const endpoint = this.application?.api?.endpoint;
const path = this.application?.api?.path;
console.log(endpoint, path);
if (!endpoint || !path) {
console.error('no endpoint or path');
return;
}
const { data: data } = await chatOperator.history(
{
action: 'retrieve',
conversation_id: this.conversationId
},
{
endpoint,
path: `${path}/history`
}
);
this.messages = data.messages || [];
},
async onSelectModel() {
await this.onCreateNewConversation();
await this.onLoadModel();
},
async onLoadModel() {
this.initializing = true;
const { data: applications } = await applicationOperator.getAll({
Expand All @@ -95,14 +129,14 @@ export default defineComponent({
role: ROLE_USER
});
this.question = '';
await this.onFetchAnswer();
},
async onFetchAnswer() {
const token = this.application?.credential?.token;
const endpoint = this.application?.api?.endpoint;
const path = this.application?.api?.path;
const question = this.messages[this.messages.length - 1].content;
if (!token || !endpoint || !question) {
if (!token || !endpoint || !question || !path) {
console.error('no token or endpoint or question');
return;
}
Expand All @@ -113,7 +147,7 @@ export default defineComponent({
});
// request server to get answer
chatOperator
.request(
.ask(
{
question,
conversation_id: this.conversationId,
Expand All @@ -122,8 +156,13 @@ export default defineComponent({
{
token,
endpoint,
stream: (response: IChatResponse) => {
this.messages[this.messages.length - 1].content = response.answer;
path,
stream: (response: IChatAskResponse) => {
this.messages[this.messages.length - 1] = {
role: ROLE_ASSISTANT,
content: response.answer,
state: IChatMessageState.ANSWERING
};
if (!this.conversationId) {
this.$router.push({
name: ROUTE_CHAT_CONVERSATION,
Expand All @@ -137,6 +176,7 @@ export default defineComponent({
)
.then(() => {
this.messages[this.messages.length - 1].state = IChatMessageState.FINISHED;
this.onLoadModel();
})
.catch((error) => {
if (this.messages && this.messages.length > 0) {
Expand Down Expand Up @@ -176,7 +216,7 @@ export default defineComponent({
flex: 1;
width: 100%;
overflow-y: scroll;
padding: 15px 0;
padding: 15px;
.messages {
.message {
margin-bottom: 15px;
Expand Down

0 comments on commit 91c7417

Please sign in to comment.