s
This commit is contained in:
parent
19fb7e2bac
commit
b7f46d1c22
2
.env
2
.env
@ -1,7 +1,7 @@
|
||||
# Glob API URL
|
||||
VITE_GLOB_API_URL=/api
|
||||
|
||||
VITE_APP_API_BASE_URL=https://erpapi.fontree.cn
|
||||
VITE_APP_API_BASE_URL=http://114.218.158.24:9020
|
||||
https://erpapi.fontree.cn#正式
|
||||
http://114.218.158.24:9020#测试
|
||||
# Whether long replies are supported, which may result in higher API fees
|
||||
|
@ -43,6 +43,7 @@ const wrapClass = computed(() => {
|
||||
return [
|
||||
'text-wrap',
|
||||
'min-w-[20px]',
|
||||
'min-h-[31px]',
|
||||
'rounded-md',
|
||||
isMobile.value ? 'p-2' : 'px-3 py-2',
|
||||
props.inversion ? 'bg-[#dfd7f3]' : 'bg-[#f4f6f8]',
|
||||
|
@ -45,12 +45,7 @@ const options = computed(() => {
|
||||
label: t('chat.copy'),
|
||||
key: 'copyText',
|
||||
icon: iconRender({ icon: 'ri:file-copy-2-line' }),
|
||||
}/* ,
|
||||
{
|
||||
label: t('common.delete'),
|
||||
key: 'delete',
|
||||
icon: iconRender({ icon: 'ri:delete-bin-line' }),
|
||||
}, */
|
||||
}
|
||||
]
|
||||
|
||||
if (!props.inversion) {
|
||||
@ -76,12 +71,6 @@ function handleSelect(key: 'copyText' | 'delete' | 'toggleRenderType') {
|
||||
emit('delete')
|
||||
}
|
||||
}
|
||||
|
||||
/* function handleRegenerate() {
|
||||
messageRef.value?.scrollIntoView()
|
||||
emit('regenerate')
|
||||
} */
|
||||
|
||||
async function handleCopy() {
|
||||
try {
|
||||
await copyToClip(props.text || '')
|
||||
|
@ -12,7 +12,6 @@ interface ScrollReturn {
|
||||
|
||||
export function useScroll(): ScrollReturn {
|
||||
const scrollRef = ref<ScrollElement>(null)
|
||||
|
||||
const scrollToBottom = async () => {
|
||||
await nextTick()
|
||||
if (scrollRef.value)
|
||||
|
@ -4,7 +4,7 @@ import {Local} from "@/utils/storage/storage";
|
||||
import dayjs from "dayjs";
|
||||
import {computed, onMounted, onUnmounted, ref, watch} from 'vue'
|
||||
import {useRoute} from 'vue-router'
|
||||
import { NAutoComplete, NButton, NInput, useDialog, useMessage } from 'naive-ui'
|
||||
import {NAutoComplete, NButton, NInput, useDialog, useMessage, NBackTop} from 'naive-ui'
|
||||
import {AreaChartOutlined, PlusOutlined} from '@ant-design/icons-vue';
|
||||
import html2canvas from 'html2canvas'
|
||||
import {Message} from './components'
|
||||
@ -20,17 +20,36 @@ import { t } from '@/locales'
|
||||
import {UploadOutlined} from '@ant-design/icons-vue';
|
||||
import {storeToRefs} from 'pinia'
|
||||
import {sessionDetailForSetup} from '@/store'
|
||||
|
||||
const sessionDetailData = sessionDetailForSetup()
|
||||
let controller = new AbortController()
|
||||
const { sessionDetail:dataSources ,currentListUuid,gptMode,isStop,isGPT4} = storeToRefs(sessionDetailData)
|
||||
const {
|
||||
sessionDetail: dataSources,
|
||||
currentListUuid,
|
||||
gptMode,
|
||||
isStop,
|
||||
isGPT4
|
||||
} = storeToRefs(sessionDetailData)
|
||||
|
||||
const dialog = useDialog()
|
||||
const ms = useMessage()
|
||||
const chatStore = useChatStore()
|
||||
const {isMobile} = useBasicLayout()
|
||||
const { addChat, updateChat, updateChatSome, getChatByUuidAndIndex } = useChat()
|
||||
const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll()
|
||||
const { usingContext, toggleUsingContext } = useUsingContext()
|
||||
const {
|
||||
addChat,
|
||||
updateChat,
|
||||
updateChatSome,
|
||||
getChatByUuidAndIndex
|
||||
} = useChat()
|
||||
const {
|
||||
scrollRef,
|
||||
scrollToBottom,
|
||||
scrollToBottomIfAtBottom
|
||||
} = useScroll()
|
||||
const {
|
||||
usingContext,
|
||||
toggleUsingContext
|
||||
} = useUsingContext()
|
||||
const prompt = ref('')
|
||||
const loading = ref(false)
|
||||
const inputRef = ref(null)
|
||||
@ -43,9 +62,11 @@ const { promptList: promptTemplate } = storeToRefs(promptStore)
|
||||
|
||||
// 未知原因刷新页面,loading 状态不会重置,手动重置
|
||||
dataSources.value.forEach((item, index) => {
|
||||
if (item.loading)
|
||||
if (item.loading) {
|
||||
updateChatSome(+uuid, index, {loading: false})
|
||||
}
|
||||
})
|
||||
|
||||
function handleSubmit() {
|
||||
dataSources.value.push({
|
||||
dateTime: dayjs().format('YYYY/MM/DD HH:mm:ss'),
|
||||
@ -57,6 +78,7 @@ function handleSubmit() {
|
||||
})
|
||||
sendDataStream()
|
||||
}
|
||||
|
||||
const API_URL = `${import.meta.env.VITE_APP_API_BASE_URL}/chat/completion`;
|
||||
|
||||
const createParams = () => {
|
||||
@ -64,8 +86,14 @@ const createParams = () => {
|
||||
return {
|
||||
content: (() => {
|
||||
if (gptMode.value === 'gpt-4-vision-preview') {
|
||||
return [{type: "text", text: x.text},...x.fileList.map((y)=>{
|
||||
return {type: "image_url", image_url:y}
|
||||
return [{
|
||||
type: "text",
|
||||
text: x.text
|
||||
}, ...x.fileList.map((y) => {
|
||||
return {
|
||||
type: "image_url",
|
||||
image_url: y
|
||||
}
|
||||
})]
|
||||
} else {
|
||||
return x.text
|
||||
@ -88,18 +116,21 @@ const createParams = () => {
|
||||
};
|
||||
};
|
||||
const handleResponseStream = async (reader) => {
|
||||
const { done, value } = await reader.read();
|
||||
const {
|
||||
done,
|
||||
value
|
||||
} = await reader.read();
|
||||
if (!done) {
|
||||
let decoded = new TextDecoder().decode(value);
|
||||
let decodedArray = decoded.split("data: ");
|
||||
decodedArray.forEach((decoded) => {
|
||||
for (const decoded of decodedArray) {
|
||||
if (decoded !== "") {
|
||||
if (decoded.trim() === "[DONE]") {
|
||||
dataSources.value[dataSources.value.length - 1].loading = false
|
||||
loading.value = false
|
||||
return;
|
||||
} else {
|
||||
if (isStop.value) {
|
||||
dataSources.value[dataSources.value.length - 1].loading = false
|
||||
loading.value = false
|
||||
return;
|
||||
}
|
||||
@ -111,8 +142,7 @@ const handleResponseStream = async (reader) => {
|
||||
dataSources.value[dataSources.value.length - 1].text += response;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
await handleResponseStream(reader);
|
||||
}
|
||||
};
|
||||
@ -152,9 +182,11 @@ const sendDataStream = async () => {
|
||||
console.error('发生错误:', error);
|
||||
}
|
||||
};
|
||||
|
||||
function handleExport() {
|
||||
if (loading.value)
|
||||
if (loading.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const d = dialog.warning({
|
||||
title: t('chat.exportImage'),
|
||||
@ -173,8 +205,9 @@ function handleExport() {
|
||||
tempLink.style.display = 'none'
|
||||
tempLink.href = imgUrl
|
||||
tempLink.setAttribute('download', 'chat-shot.png')
|
||||
if (typeof tempLink.download === 'undefined')
|
||||
if (typeof tempLink.download === 'undefined') {
|
||||
tempLink.setAttribute('target', '_blank')
|
||||
}
|
||||
|
||||
document.body.appendChild(tempLink)
|
||||
tempLink.click()
|
||||
@ -183,11 +216,9 @@ function handleExport() {
|
||||
d.loading = false
|
||||
ms.success(t('chat.exportSuccess'))
|
||||
Promise.resolve()
|
||||
}
|
||||
catch (error) {
|
||||
} catch (error) {
|
||||
ms.error(t('chat.exportFailed'))
|
||||
}
|
||||
finally {
|
||||
} finally {
|
||||
d.loading = false
|
||||
}
|
||||
},
|
||||
@ -195,8 +226,9 @@ function handleExport() {
|
||||
}
|
||||
|
||||
function handleDelete(index) {
|
||||
if (loading.value)
|
||||
if (loading.value) {
|
||||
return
|
||||
}
|
||||
|
||||
dialog.warning({
|
||||
title: t('chat.deleteMessage'),
|
||||
@ -210,8 +242,9 @@ function handleDelete(index) {
|
||||
}
|
||||
|
||||
function handleClear() {
|
||||
if (loading.value)
|
||||
if (loading.value) {
|
||||
return
|
||||
}
|
||||
|
||||
dialog.warning({
|
||||
title: t('chat.clearChat'),
|
||||
@ -230,15 +263,15 @@ function handleEnter(event) {
|
||||
event.preventDefault()
|
||||
handleSubmit()
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (event.key === 'Enter' && event.ctrlKey) {
|
||||
event.preventDefault()
|
||||
handleSubmit()
|
||||
}
|
||||
}
|
||||
}
|
||||
function handleStop() {
|
||||
|
||||
function handleStop(item) {
|
||||
if (loading.value) {
|
||||
loading.value = false
|
||||
isStop.value = true
|
||||
@ -256,8 +289,7 @@ const searchOptions = computed(() => {
|
||||
value: obj.value,
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
})
|
||||
@ -265,15 +297,17 @@ const searchOptions = computed(() => {
|
||||
// value反渲染key
|
||||
const renderOption = (option) => {
|
||||
for (const i of promptTemplate.value) {
|
||||
if (i.value === option.label)
|
||||
if (i.value === option.label) {
|
||||
return [i.key]
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
const placeholder = computed(() => {
|
||||
if (isMobile.value)
|
||||
if (isMobile.value) {
|
||||
return t('chat.placeholderMobile')
|
||||
}
|
||||
return t('chat.placeholder')
|
||||
})
|
||||
|
||||
@ -283,23 +317,26 @@ const buttonDisabled = computed(() => {
|
||||
|
||||
const footerClass = computed(() => {
|
||||
let classes = ['p-4']
|
||||
if (isMobile.value)
|
||||
if (isMobile.value) {
|
||||
classes = ['sticky', 'left-0', 'bottom-0', 'right-0', 'p-2', 'pr-3', 'overflow-hidden']
|
||||
}
|
||||
return classes
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
scrollToBottom()
|
||||
if (inputRef.value && !isMobile.value)
|
||||
if (inputRef.value && !isMobile.value) {
|
||||
inputRef.value?.focus()
|
||||
}
|
||||
})
|
||||
const fileList = ref([
|
||||
]);
|
||||
const fileList = ref([]);
|
||||
onUnmounted(() => {
|
||||
if (loading.value)
|
||||
if (loading.value) {
|
||||
controller.abort()
|
||||
}
|
||||
})
|
||||
|
||||
function getBase64(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
@ -308,6 +345,7 @@ function getBase64(file) {
|
||||
reader.onerror = error => reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
const previewVisible = ref(false);
|
||||
const previewImage = ref('');
|
||||
const previewTitle = ref('');
|
||||
@ -358,7 +396,10 @@ if (res.code===0){
|
||||
}
|
||||
|
||||
const customRequest = async (file) => {
|
||||
const res=await uploadImg({file:file.file,source:'approval'})
|
||||
const res = await uploadImg({
|
||||
file: file.file,
|
||||
source: 'approval'
|
||||
})
|
||||
if (res.code === 0) {
|
||||
file.onSuccess()
|
||||
fileList.value.push({
|
||||
@ -376,6 +417,24 @@ const customRequest=async (file)=>{
|
||||
@handle-clear="handleClear"
|
||||
/>
|
||||
<main class="flex-1 overflow-hidden">
|
||||
<div class="shortcut-arrow">
|
||||
<div class="top">
|
||||
<n-button @click="scrollToBottom" type="primary" dashed circle>
|
||||
<template #icon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48"
|
||||
d="M244 400L100 256l144-144"
|
||||
></path>
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48"
|
||||
d="M120 256h292"
|
||||
></path>
|
||||
</svg>
|
||||
</template>
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="scrollRef" ref="scrollRef" class="h-full overflow-hidden overflow-y-auto">
|
||||
<div
|
||||
id="image-wrapper"
|
||||
@ -392,7 +451,7 @@ const customRequest=async (file)=>{
|
||||
<template v-else>
|
||||
<div>
|
||||
<Message
|
||||
v-for="(item, index) of dataSources.filter(x=>x.text||x.fileList?.length>0)"
|
||||
v-for="(item, index) of dataSources"
|
||||
:key="index"
|
||||
:date-time="item.dateTime"
|
||||
:text="item.text"
|
||||
@ -403,7 +462,7 @@ const customRequest=async (file)=>{
|
||||
@delete="handleDelete(index)"
|
||||
/>
|
||||
<div class="sticky bottom-0 left-0 flex justify-center">
|
||||
<NButton v-if="loading" type="warning" @click="handleStop">
|
||||
<NButton v-if="loading" type="warning" @click="handleStop(item)">
|
||||
<template #icon>
|
||||
<SvgIcon icon="ri:stop-circle-line"/>
|
||||
</template>
|
||||
@ -435,7 +494,9 @@ const customRequest=async (file)=>{
|
||||
</div>
|
||||
</template>
|
||||
<HoverButton @click="visible1=!visible1">
|
||||
<span class="text-xl text-[#4f555e] dark:text-white" style="display: flex;justify-content: center;align-items: center">
|
||||
<span class="text-xl text-[#4f555e] dark:text-white"
|
||||
style="display: flex;justify-content: center;align-items: center"
|
||||
>
|
||||
<SvgIcon icon="ri:upload-2-line"/>
|
||||
</span>
|
||||
</HoverButton>
|
||||
@ -461,7 +522,9 @@ const customRequest=async (file)=>{
|
||||
</div>
|
||||
</template>
|
||||
<HoverButton @click="visible=!visible">
|
||||
<span class="text-xl text-[#4f555e] dark:text-white" style="display: flex;justify-content: center;align-items: center">
|
||||
<span class="text-xl text-[#4f555e] dark:text-white"
|
||||
style="display: flex;justify-content: center;align-items: center"
|
||||
>
|
||||
<AreaChartOutlined/>
|
||||
</span>
|
||||
</HoverButton>
|
||||
@ -508,3 +571,19 @@ const customRequest=async (file)=>{
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.shortcut-arrow {
|
||||
width: min-content;
|
||||
height: min-content;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
left: 50%;
|
||||
bottom: 90px;
|
||||
.top {
|
||||
transform: rotate(270deg) translateX(-50%);
|
||||
width: min-content;
|
||||
height: min-content;
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
Loading…
Reference in New Issue
Block a user