zhzabcd's picture
Upload 101 files
755dd12 verified
<script setup lang="ts">
import { IMessage } from '../../interface';
import { computed, ref, watch } from 'vue';
import ChatMessage from './message.vue';
import { useAppStore } from '../../store';
import { chat } from '../../api';
import { SystemPrompts, UserPrompts } from '../../contants';
import { useI18n } from 'vue-i18n';
interface IProps {
ask: string
contexts?: Record<string, any>[]
query: string
answer: string
// watch this value to clear messages
clear?: boolean
}
interface IEmit {
(e: 'message', val: string): void
}
const { t } = useI18n();
const appStore = useAppStore();
const loading = ref(false);
const props = defineProps<IProps>();
const emits = defineEmits<IEmit>();
const messages = ref<IMessage[]>([]);
let abortCtrl: AbortController | null = null;
const queryMessages = computed(() => {
const contexts = props.contexts || [];
const strs = contexts.map((context) => {
return `[${context.id}]: ${context.snippet}`;
});
const ctx = UserPrompts.replace('%s', strs.join('\n'));
const current: IMessage = {
role: 'user',
content: props.ask
};
const firstMessage: IMessage = {
role: 'user',
content: `${ctx}\n${props.query}`
};
const secondMessage: IMessage = {
role: 'assistant',
content: props.answer
};
const chatMessages = messages.value.filter((message) => {
return !!message.content;
});
return [
firstMessage,
secondMessage,
...chatMessages,
current
];
});
watch(() => props.ask, (ask) => {
if (ask && !loading.value) {
messages.value.push({
role: 'user',
content: ask
});
startChat();
}
});
watch(() => props.clear, (clear) => {
if (clear) {
messages.value = [];
loading.value = false;
}
});
const enableReload = computed(() => {
const length = messages.value.length;
return length > 1 && messages.value[length - 1].role === 'user';
});
const onClearMessage = () => {
messages.value = [];
};
async function startChat() {
if (abortCtrl) {
abortCtrl.abort();
abortCtrl = null;
}
const ctrl = new AbortController();
abortCtrl = ctrl;
loading.value = true;
const { model, enableLocal, localModel } = appStore;
const modelName = enableLocal ? localModel : model?.split(':')[1];
messages.value.push({
role: 'assistant',
content: ''
});
const answer = messages.value[messages.value.length - 1];
await chat(queryMessages.value, {
ctrl,
model: modelName,
locally: enableLocal,
system: SystemPrompts,
onMessage: (message) => {
answer.content += message.text || '';
emits('message', answer.content);
}
});
abortCtrl = null;
loading.value = false;
if (!answer.content) {
messages.value.pop();
}
}
</script>
<script lang="ts">
export default {
name: 'ChatPage',
};
</script>
<template>
<div class="w-full">
<div v-if="!messages.length" class="w-full text-center text-zinc-500">
{{ t('tips.continue') }}
</div>
<div class="mt-4 w-full">
<div v-for="(message, index) in messages" :key="index" class="mt-4">
<t-loading v-if="message.role === 'assistant' && !message.content" size="small" />
<ChatMessage v-else :enable-reload="enableReload" :message="message" />
</div>
</div>
<div v-if="messages.length && !loading" class="mt-4 flex items-center justify-center">
<t-button
shape="round"
:disabled="loading"
:loading="loading"
@click="onClearMessage"
>
{{ t('btn.clear') }}
</t-button>
</div>
</div>
</template>