|
|
@ -1,5 +1,5 @@
|
|
|
|
<script setup lang='ts'>
|
|
|
|
<script setup >
|
|
|
|
import type { Ref } from 'vue'
|
|
|
|
import dayjs from "dayjs";
|
|
|
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
|
|
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
|
|
|
import { useRoute } from 'vue-router'
|
|
|
|
import { useRoute } from 'vue-router'
|
|
|
|
import { storeToRefs } from 'pinia'
|
|
|
|
import { storeToRefs } from 'pinia'
|
|
|
@ -15,7 +15,8 @@ import { useBasicLayout } from '@/hooks/useBasicLayout'
|
|
|
|
import { useChatStore, usePromptStore } from '@/store'
|
|
|
|
import { useChatStore, usePromptStore } from '@/store'
|
|
|
|
import { fetchChatAPIProcess } from '@/api'
|
|
|
|
import { fetchChatAPIProcess } from '@/api'
|
|
|
|
import { t } from '@/locales'
|
|
|
|
import { t } from '@/locales'
|
|
|
|
|
|
|
|
import { sessionDetailForSetup } from '@/store'
|
|
|
|
|
|
|
|
const sessionDetailData=sessionDetailForSetup()
|
|
|
|
let controller = new AbortController()
|
|
|
|
let controller = new AbortController()
|
|
|
|
|
|
|
|
|
|
|
|
const openLongReply = import.meta.env.VITE_GLOB_OPEN_LONG_REPLY === 'true'
|
|
|
|
const openLongReply = import.meta.env.VITE_GLOB_OPEN_LONG_REPLY === 'true'
|
|
|
@ -31,30 +32,114 @@ const { addChat, updateChat, updateChatSome, getChatByUuidAndIndex } = useChat()
|
|
|
|
const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll()
|
|
|
|
const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll()
|
|
|
|
const { usingContext, toggleUsingContext } = useUsingContext()
|
|
|
|
const { usingContext, toggleUsingContext } = useUsingContext()
|
|
|
|
|
|
|
|
|
|
|
|
const { uuid } = route.params as { uuid: string }
|
|
|
|
const { uuid } = route.params
|
|
|
|
|
|
|
|
|
|
|
|
const dataSources = computed(() => chatStore.getChatByUuid(+uuid))
|
|
|
|
|
|
|
|
const conversationList = computed(() => dataSources.value.filter(item => (!item.inversion && !!item.conversationOptions)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const prompt = ref<string>('')
|
|
|
|
const dataSources = sessionDetailForSetup().sessionDetail
|
|
|
|
const loading = ref<boolean>(false)
|
|
|
|
const conversationList = computed(() => dataSources.filter(item => (!item.inversion && !!item.conversationOptions)))
|
|
|
|
const inputRef = ref<Ref | null>(null)
|
|
|
|
const dataSessionDetail=ref([])
|
|
|
|
|
|
|
|
const prompt = ref('')
|
|
|
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
|
|
|
const inputRef = ref(null)
|
|
|
|
|
|
|
|
|
|
|
|
// 添加PromptStore
|
|
|
|
// 添加PromptStore
|
|
|
|
const promptStore = usePromptStore()
|
|
|
|
const promptStore = usePromptStore()
|
|
|
|
|
|
|
|
|
|
|
|
// 使用storeToRefs,保证store修改后,联想部分能够重新渲染
|
|
|
|
// 使用storeToRefs,保证store修改后,联想部分能够重新渲染
|
|
|
|
const { promptList: promptTemplate } = storeToRefs<any>(promptStore)
|
|
|
|
const { promptList: promptTemplate } = storeToRefs(promptStore)
|
|
|
|
|
|
|
|
|
|
|
|
// 未知原因刷新页面,loading 状态不会重置,手动重置
|
|
|
|
// 未知原因刷新页面,loading 状态不会重置,手动重置
|
|
|
|
dataSources.value.forEach((item, index) => {
|
|
|
|
dataSources.forEach((item, index) => {
|
|
|
|
if (item.loading)
|
|
|
|
if (item.loading)
|
|
|
|
updateChatSome(+uuid, index, { loading: false })
|
|
|
|
updateChatSome(+uuid, index, { loading: false })
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
function handleSubmit() {
|
|
|
|
function handleSubmit() {
|
|
|
|
onConversation()
|
|
|
|
dataSources.push({
|
|
|
|
|
|
|
|
dateTime: dayjs().format('YYYY/MM/DD HH:mm:ss'),
|
|
|
|
|
|
|
|
text: prompt.value,
|
|
|
|
|
|
|
|
inversion: true,
|
|
|
|
|
|
|
|
error: false,
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
sendDataStream()
|
|
|
|
|
|
|
|
console.log(sessionDetailForSetup().sessionDetail,'sessionDetailForSetup().sessionDetail')
|
|
|
|
|
|
|
|
/* onConversation() */
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const API_URL = 'http://114.218.158.24:9020/chat/completion';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const createParams = () => {
|
|
|
|
|
|
|
|
const sessionDetail = sessionDetailForSetup().sessionDetail;
|
|
|
|
|
|
|
|
const messages = sessionDetail.map(x => ({
|
|
|
|
|
|
|
|
content: x.text,
|
|
|
|
|
|
|
|
role: x.inversion ? 'user' : 'assistant'
|
|
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
listUuid: sessionDetailForSetup().currentListUuid,
|
|
|
|
|
|
|
|
messages,
|
|
|
|
|
|
|
|
frequency_penalty: 0,
|
|
|
|
|
|
|
|
max_tokens: 1000,
|
|
|
|
|
|
|
|
model: sessionDetailForSetup().gptMode,
|
|
|
|
|
|
|
|
presence_penalty: 0,
|
|
|
|
|
|
|
|
stream: true,
|
|
|
|
|
|
|
|
temperature: 1,
|
|
|
|
|
|
|
|
top_p: 1
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleResponseStream = async (reader) => {
|
|
|
|
|
|
|
|
const { done, value } = await reader.read();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!done) {
|
|
|
|
|
|
|
|
let decoded = new TextDecoder().decode(value);
|
|
|
|
|
|
|
|
let decodedArray = decoded.split("data: ");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
decodedArray.forEach((decoded) => {
|
|
|
|
|
|
|
|
if (decoded !== "") {
|
|
|
|
|
|
|
|
if (decoded.trim() === "[DONE]") {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
const response = JSON.parse(decoded).choices[0].delta.content
|
|
|
|
|
|
|
|
? JSON.parse(decoded).choices[0].delta.content
|
|
|
|
|
|
|
|
: "";
|
|
|
|
|
|
|
|
dataSources[dataSources.length - 1].text += response;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await handleResponseStream(reader);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const sendDataStream = async () => {
|
|
|
|
|
|
|
|
dataSources.push({
|
|
|
|
|
|
|
|
dateTime: dayjs().format('YYYY/MM/DD HH:mm:ss'),
|
|
|
|
|
|
|
|
text: '',
|
|
|
|
|
|
|
|
inversion: false,
|
|
|
|
|
|
|
|
error: false,
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const params = createParams();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const response = await fetch(API_URL, {
|
|
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
|
|
timeout: 10000,
|
|
|
|
|
|
|
|
body: JSON.stringify(params),
|
|
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
|
|
|
Accept: "application/json",
|
|
|
|
|
|
|
|
Authorization: localStorage.getItem('token'),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
const contentType = response.headers.get('Content-Type');
|
|
|
|
|
|
|
|
if (!contentType || !contentType.includes('application/json')) {
|
|
|
|
|
|
|
|
const reader = response.body.getReader();
|
|
|
|
|
|
|
|
await handleResponseStream(reader);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
console.error('发生错误:', error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
async function onConversation() {
|
|
|
|
async function onConversation() {
|
|
|
|
let message = prompt.value
|
|
|
|
let message = prompt.value
|
|
|
@ -83,7 +168,7 @@ async function onConversation() {
|
|
|
|
loading.value = true
|
|
|
|
loading.value = true
|
|
|
|
prompt.value = ''
|
|
|
|
prompt.value = ''
|
|
|
|
|
|
|
|
|
|
|
|
let options: Chat.ConversationRequest = {}
|
|
|
|
let options= {}
|
|
|
|
const lastContext = conversationList.value[conversationList.value.length - 1]?.conversationOptions
|
|
|
|
const lastContext = conversationList.value[conversationList.value.length - 1]?.conversationOptions
|
|
|
|
|
|
|
|
|
|
|
|
if (lastContext && usingContext.value)
|
|
|
|
if (lastContext && usingContext.value)
|
|
|
@ -122,7 +207,7 @@ async function onConversation() {
|
|
|
|
const data = JSON.parse(chunk)
|
|
|
|
const data = JSON.parse(chunk)
|
|
|
|
updateChat(
|
|
|
|
updateChat(
|
|
|
|
+uuid,
|
|
|
|
+uuid,
|
|
|
|
dataSources.value.length - 1,
|
|
|
|
dataSources.length - 1,
|
|
|
|
{
|
|
|
|
{
|
|
|
|
dateTime: new Date().toLocaleString(),
|
|
|
|
dateTime: new Date().toLocaleString(),
|
|
|
|
text: lastText + (data.text ?? ''),
|
|
|
|
text: lastText + (data.text ?? ''),
|
|
|
@ -147,18 +232,18 @@ async function onConversation() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
})
|
|
|
|
updateChatSome(+uuid, dataSources.value.length - 1, { loading: false })
|
|
|
|
updateChatSome(+uuid, dataSources.length - 1, { loading: false })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await fetchChatAPIOnce()
|
|
|
|
await fetchChatAPIOnce()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (error: any) {
|
|
|
|
catch (error) {
|
|
|
|
const errorMessage = error?.message ?? t('common.wrong')
|
|
|
|
const errorMessage = error?.message ?? t('common.wrong')
|
|
|
|
|
|
|
|
|
|
|
|
if (error.message === 'canceled') {
|
|
|
|
if (error.message === 'canceled') {
|
|
|
|
updateChatSome(
|
|
|
|
updateChatSome(
|
|
|
|
+uuid,
|
|
|
|
+uuid,
|
|
|
|
dataSources.value.length - 1,
|
|
|
|
dataSources.length - 1,
|
|
|
|
{
|
|
|
|
{
|
|
|
|
loading: false,
|
|
|
|
loading: false,
|
|
|
|
},
|
|
|
|
},
|
|
|
@ -167,12 +252,12 @@ async function onConversation() {
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const currentChat = getChatByUuidAndIndex(+uuid, dataSources.value.length - 1)
|
|
|
|
const currentChat = getChatByUuidAndIndex(+uuid, dataSources.length - 1)
|
|
|
|
|
|
|
|
|
|
|
|
if (currentChat?.text && currentChat.text !== '') {
|
|
|
|
if (currentChat?.text && currentChat.text !== '') {
|
|
|
|
updateChatSome(
|
|
|
|
updateChatSome(
|
|
|
|
+uuid,
|
|
|
|
+uuid,
|
|
|
|
dataSources.value.length - 1,
|
|
|
|
dataSources.length - 1,
|
|
|
|
{
|
|
|
|
{
|
|
|
|
text: `${currentChat.text}\n[${errorMessage}]`,
|
|
|
|
text: `${currentChat.text}\n[${errorMessage}]`,
|
|
|
|
error: false,
|
|
|
|
error: false,
|
|
|
@ -184,7 +269,7 @@ async function onConversation() {
|
|
|
|
|
|
|
|
|
|
|
|
updateChat(
|
|
|
|
updateChat(
|
|
|
|
+uuid,
|
|
|
|
+uuid,
|
|
|
|
dataSources.value.length - 1,
|
|
|
|
dataSources.length - 1,
|
|
|
|
{
|
|
|
|
{
|
|
|
|
dateTime: new Date().toLocaleString(),
|
|
|
|
dateTime: new Date().toLocaleString(),
|
|
|
|
text: errorMessage,
|
|
|
|
text: errorMessage,
|
|
|
@ -202,17 +287,17 @@ async function onConversation() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function onRegenerate(index: number) {
|
|
|
|
async function onRegenerate(index) {
|
|
|
|
if (loading.value)
|
|
|
|
if (loading.value)
|
|
|
|
return
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
controller = new AbortController()
|
|
|
|
controller = new AbortController()
|
|
|
|
|
|
|
|
|
|
|
|
const { requestOptions } = dataSources.value[index]
|
|
|
|
const { requestOptions } = dataSources[index]
|
|
|
|
|
|
|
|
|
|
|
|
let message = requestOptions?.prompt ?? ''
|
|
|
|
let message = requestOptions?.prompt ?? ''
|
|
|
|
|
|
|
|
|
|
|
|
let options: Chat.ConversationRequest = {}
|
|
|
|
let options = {}
|
|
|
|
|
|
|
|
|
|
|
|
if (requestOptions.options)
|
|
|
|
if (requestOptions.options)
|
|
|
|
options = { ...requestOptions.options }
|
|
|
|
options = { ...requestOptions.options }
|
|
|
@ -280,7 +365,7 @@ async function onRegenerate(index: number) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
await fetchChatAPIOnce()
|
|
|
|
await fetchChatAPIOnce()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (error: any) {
|
|
|
|
catch (error) {
|
|
|
|
if (error.message === 'canceled') {
|
|
|
|
if (error.message === 'canceled') {
|
|
|
|
updateChatSome(
|
|
|
|
updateChatSome(
|
|
|
|
+uuid,
|
|
|
|
+uuid,
|
|
|
@ -326,7 +411,7 @@ function handleExport() {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
d.loading = true
|
|
|
|
d.loading = true
|
|
|
|
const ele = document.getElementById('image-wrapper')
|
|
|
|
const ele = document.getElementById('image-wrapper')
|
|
|
|
const canvas = await html2canvas(ele as HTMLDivElement, {
|
|
|
|
const canvas = await html2canvas(ele, {
|
|
|
|
useCORS: true,
|
|
|
|
useCORS: true,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
const imgUrl = canvas.toDataURL('image/png')
|
|
|
|
const imgUrl = canvas.toDataURL('image/png')
|
|
|
@ -345,7 +430,7 @@ function handleExport() {
|
|
|
|
ms.success(t('chat.exportSuccess'))
|
|
|
|
ms.success(t('chat.exportSuccess'))
|
|
|
|
Promise.resolve()
|
|
|
|
Promise.resolve()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (error: any) {
|
|
|
|
catch (error) {
|
|
|
|
ms.error(t('chat.exportFailed'))
|
|
|
|
ms.error(t('chat.exportFailed'))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
finally {
|
|
|
@ -355,7 +440,7 @@ function handleExport() {
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleDelete(index: number) {
|
|
|
|
function handleDelete(index) {
|
|
|
|
if (loading.value)
|
|
|
|
if (loading.value)
|
|
|
|
return
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
@ -385,7 +470,7 @@ function handleClear() {
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleEnter(event: KeyboardEvent) {
|
|
|
|
function handleEnter(event) {
|
|
|
|
if (!isMobile.value) {
|
|
|
|
if (!isMobile.value) {
|
|
|
|
if (event.key === 'Enter' && !event.shiftKey) {
|
|
|
|
if (event.key === 'Enter' && !event.shiftKey) {
|
|
|
|
event.preventDefault()
|
|
|
|
event.preventDefault()
|
|
|
@ -412,7 +497,7 @@ function handleStop() {
|
|
|
|
// 理想状态下其实应该是key作为索引项,但官方的renderOption会出现问题,所以就需要value反renderLabel实现
|
|
|
|
// 理想状态下其实应该是key作为索引项,但官方的renderOption会出现问题,所以就需要value反renderLabel实现
|
|
|
|
const searchOptions = computed(() => {
|
|
|
|
const searchOptions = computed(() => {
|
|
|
|
if (prompt.value.startsWith('/')) {
|
|
|
|
if (prompt.value.startsWith('/')) {
|
|
|
|
return promptTemplate.value.filter((item: { key: string }) => item.key.toLowerCase().includes(prompt.value.substring(1).toLowerCase())).map((obj: { value: any }) => {
|
|
|
|
return promptTemplate.value.filter((item) => item.key.toLowerCase().includes(prompt.value.substring(1).toLowerCase())).map((obj) => {
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
label: obj.value,
|
|
|
|
label: obj.value,
|
|
|
|
value: obj.value,
|
|
|
|
value: obj.value,
|
|
|
@ -425,7 +510,7 @@ const searchOptions = computed(() => {
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// value反渲染key
|
|
|
|
// value反渲染key
|
|
|
|
const renderOption = (option: { label: string }) => {
|
|
|
|
const renderOption = (option) => {
|
|
|
|
for (const i of promptTemplate.value) {
|
|
|
|
for (const i of promptTemplate.value) {
|
|
|
|
if (i.value === option.label)
|
|
|
|
if (i.value === option.label)
|
|
|
|
return [i.key]
|
|
|
|
return [i.key]
|
|
|
@ -460,10 +545,24 @@ onUnmounted(() => {
|
|
|
|
if (loading.value)
|
|
|
|
if (loading.value)
|
|
|
|
controller.abort()
|
|
|
|
controller.abort()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
</script>
|
|
|
|
const value = ref('gpt-3.5-turbo');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const options=ref([{ label: 'GPT-3.5', value: 'gpt-3.5-turbo' },
|
|
|
|
|
|
|
|
{ label: 'GPT-4.0', value: 'gpt-4-1106-preview' },
|
|
|
|
|
|
|
|
{ label: 'GPT-V', value: 'gpt-4-vision-preview' }])
|
|
|
|
|
|
|
|
</script>
|
|
|
|
<template>
|
|
|
|
<template>
|
|
|
|
<div class="flex flex-col w-full h-full">
|
|
|
|
<div class="flex flex-col w-full h-full">
|
|
|
|
|
|
|
|
<div style="margin-top: 15px;margin-left: 15px">
|
|
|
|
|
|
|
|
<a-select
|
|
|
|
|
|
|
|
align="center"
|
|
|
|
|
|
|
|
v-model:value="sessionDetailForSetup().gptMode"
|
|
|
|
|
|
|
|
style="width: 120px"
|
|
|
|
|
|
|
|
:dropdown-match-select-width="false"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<a-select-option align="center" v-for="(item,index) in options" :key="index" :value="item.value">{{item.label}}</a-select-option>
|
|
|
|
|
|
|
|
</a-select>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
<HeaderComponent
|
|
|
|
<HeaderComponent
|
|
|
|
v-if="isMobile"
|
|
|
|
v-if="isMobile"
|
|
|
|
:using-context="usingContext"
|
|
|
|
:using-context="usingContext"
|
|
|
@ -511,7 +610,7 @@ onUnmounted(() => {
|
|
|
|
</main>
|
|
|
|
</main>
|
|
|
|
<footer :class="footerClass">
|
|
|
|
<footer :class="footerClass">
|
|
|
|
<div class="w-full max-w-screen-xl m-auto">
|
|
|
|
<div class="w-full max-w-screen-xl m-auto">
|
|
|
|
<div class="flex items-center justify-between space-x-2">
|
|
|
|
<div class="flex items-center justify-between space-x-2" style="flex-wrap: initial">
|
|
|
|
<HoverButton v-if="!isMobile" @click="handleClear">
|
|
|
|
<HoverButton v-if="!isMobile" @click="handleClear">
|
|
|
|
<span class="text-xl text-[#4f555e] dark:text-white">
|
|
|
|
<span class="text-xl text-[#4f555e] dark:text-white">
|
|
|
|
<SvgIcon icon="ri:delete-bin-line" />
|
|
|
|
<SvgIcon icon="ri:delete-bin-line" />
|
|
|
|