Phoenix 7 months ago
parent b7f46d1c22
commit bf5f9f7752

@ -2,8 +2,6 @@
VITE_GLOB_API_URL=/api VITE_GLOB_API_URL=/api
VITE_APP_API_BASE_URL=http://114.218.158.24:9020 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 # Whether long replies are supported, which may result in higher API fees
VITE_GLOB_OPEN_LONG_REPLY=true VITE_GLOB_OPEN_LONG_REPLY=true

@ -0,0 +1,9 @@
# Glob API URL
VITE_GLOB_API_URL=/api
VITE_APP_API_BASE_URL=https://erpapi.fontree.cn
# Whether long replies are supported, which may result in higher API fees
VITE_GLOB_OPEN_LONG_REPLY=true
# When you want to use PWA
VITE_GLOB_APP_PWA=false

2889
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -12,9 +12,9 @@
], ],
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "run-p type-check build-only", "build": "run-p build-only",
"preview": "vite preview", "preview": "vite preview",
"build-only": "vite build", "build-only": "vite build --mode prod",
"type-check": "vue-tsc --noEmit", "type-check": "vue-tsc --noEmit",
"lint": "eslint .", "lint": "eslint .",
"lint:fix": "eslint . --fix", "lint:fix": "eslint . --fix",
@ -65,9 +65,12 @@
"less": "^4.1.3", "less": "^4.1.3",
"lint-staged": "^13.1.2", "lint-staged": "^13.1.2",
"markdown-it-link-attributes": "^4.0.1", "markdown-it-link-attributes": "^4.0.1",
"node-sass": "^9.0.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"postcss": "^8.4.21", "postcss": "^8.4.21",
"rimraf": "^4.3.0", "rimraf": "^4.3.0",
"sass": "^1.70.0",
"sass-loader": "^14.1.0",
"tailwindcss": "^3.2.7", "tailwindcss": "^3.2.7",
"typescript": "~4.9.5", "typescript": "~4.9.5",
"unplugin-auto-import": "^0.17.5", "unplugin-auto-import": "^0.17.5",

@ -8,13 +8,13 @@ import zhCN from 'ant-design-vue/es/locale/zh_CN';
const { theme } = useTheme() const { theme } = useTheme()
const { language } = useLanguage() const { language } = useLanguage()
import {Local} from './utils/storage/storage.js' import {Local} from './utils/storage/storage.js'
const props = window.$wujie?.props; /* const props = window.$wujie?.props;
if (props){ /!* if (props){
Local.set('token',props.token) Local.set('token',props.token)
Local.set('mode',props.mode) Local.set('mode',props.mode)
Local.set('userInfo',props.userInfo) Local.set('userInfo',props.userInfo)
Local.set('isGPT4',props.isGPT4) Local.set('isGPT4',props.isGPT4)
} } *!/ */
const themeOverrides = { const themeOverrides = {
common: { common: {
primaryColorHover:'#764CF6', primaryColorHover:'#764CF6',

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 12 12"><g fill="none"><path d="M5 4a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H5zm6 2A5 5 0 1 1 1 6a5 5 0 0 1 10 0zm-1 0a4 4 0 1 0-8 0a4 4 0 0 0 8 0z" fill="currentColor"></path></g></svg>

After

Width:  |  Height:  |  Size: 303 B

@ -1,8 +1,8 @@
import {createSession, getSessionList, postRequest, sessionDetailReq} from "@/api/api"; import {createSession, getSessionList, postRequest, sessionDetailReq} from "@/api/api";
import {useScroll} from "@/views/chat/hooks/useScroll";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ref } from 'vue'; import { ref } from 'vue';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
export const sessionDetailForSetup = defineStore('session', () => { export const sessionDetailForSetup = defineStore('session', () => {
const sessionDetail = ref([]); const sessionDetail = ref([]);
const currentListUuid=ref('') const currentListUuid=ref('')
@ -10,6 +10,7 @@ const currentListUuid=ref('')
const dataList = ref([]); const dataList = ref([]);
const isStop = ref(false); const isStop = ref(false);
const isGPT4 = ref(false); const isGPT4 = ref(false);
const loading=ref(false)
const getDataList = async () => { const getDataList = async () => {
const data = { const data = {
page: 1, page: 1,
@ -63,5 +64,5 @@ const currentListUuid=ref('')
getDataList() getDataList()
} }
} }
return { sessionDetail,currentListUuid ,gptMode,getDataList,dataList,getSessionDetail,createSessionStore,deleteSession,isStop,isGPT4}; return { sessionDetail,currentListUuid ,gptMode,getDataList,dataList,getSessionDetail,createSessionStore,deleteSession,isStop,isGPT4,loading};
}); });

@ -69,6 +69,7 @@ function addCopyEvents() {
const copyBtn = textRef.value.querySelectorAll('.code-block-header__copy') const copyBtn = textRef.value.querySelectorAll('.code-block-header__copy')
copyBtn.forEach((btn) => { copyBtn.forEach((btn) => {
btn.addEventListener('click', () => { btn.addEventListener('click', () => {
console.log(btn.parentElement?.nextElementSibling?.textContent,'btn.parentElement?.nextElementSibling?.textContent')
const code = btn.parentElement?.nextElementSibling?.textContent const code = btn.parentElement?.nextElementSibling?.textContent
if (code) { if (code) {
copyToClip(code).then(() => { copyToClip(code).then(() => {

@ -12,10 +12,15 @@ interface ScrollReturn {
export function useScroll(): ScrollReturn { export function useScroll(): ScrollReturn {
const scrollRef = ref<ScrollElement>(null) const scrollRef = ref<ScrollElement>(null)
const scrollToBottom = async () => { const scrollToBottom = async (value:ScrollBehavior) => {
await nextTick() await nextTick()
if (scrollRef.value) if (scrollRef.value){
scrollRef.value.scrollTop = scrollRef.value.scrollHeight scrollRef.value.scrollTo({
top: scrollRef.value.scrollHeight, // 滚动到底部
behavior: value// 滚动行为平滑过渡
});
}
/* scrollRef.value.scrollTop = scrollRef.value.scrollHeight */
} }
const scrollToTop = async () => { const scrollToTop = async () => {

@ -20,7 +20,7 @@ import {t} from '@/locales'
import {UploadOutlined} from '@ant-design/icons-vue'; import {UploadOutlined} from '@ant-design/icons-vue';
import {storeToRefs} from 'pinia' import {storeToRefs} from 'pinia'
import {sessionDetailForSetup} from '@/store' import {sessionDetailForSetup} from '@/store'
import StopSvg from '@/assets/RecordStop12Regular.svg'
const sessionDetailData = sessionDetailForSetup() const sessionDetailData = sessionDetailForSetup()
let controller = new AbortController() let controller = new AbortController()
const { const {
@ -28,7 +28,8 @@ const {
currentListUuid, currentListUuid,
gptMode, gptMode,
isStop, isStop,
isGPT4 isGPT4,
loading
} = storeToRefs(sessionDetailData) } = storeToRefs(sessionDetailData)
const dialog = useDialog() const dialog = useDialog()
@ -51,7 +52,6 @@ const {
toggleUsingContext toggleUsingContext
} = useUsingContext() } = useUsingContext()
const prompt = ref('') const prompt = ref('')
const loading = ref(false)
const inputRef = ref(null) const inputRef = ref(null)
// PromptStore // PromptStore
@ -68,19 +68,32 @@ dataSources.value.forEach((item, index) => {
}) })
function handleSubmit() { function handleSubmit() {
if (loading.value){
handleStop()
}else {
dataSources.value.push({ dataSources.value.push({
dateTime: dayjs().format('YYYY/MM/DD HH:mm:ss'), dateTime: dayjs().format('YYYY/MM/DD HH:mm:ss'),
text: prompt.value, text: prompt.value?.trim(),
inversion: true, inversion: true,
error: false, error: false,
fileList: fileList.value.map(x => x.url), fileList: fileList.value.map(x => x.url),
loading: false loading: false
}) })
sendDataStream() sendDataStream()
scrollToBottom('smooth')
}
} }
//
/* const isTopBottom=()=>{
if ( scrollRef.value.scrollTop ===0){
const API_URL = `${import.meta.env.VITE_APP_API_BASE_URL}/chat/completion`; }
} */
const API_URL = `${import.meta.env.VITE_APP_API_BASE_URL}/chat/completion`;
const createParams = () => { const createParams = () => {
const messages = dataSources.value.map((x) => { const messages = dataSources.value.map((x) => {
return { return {
@ -160,7 +173,6 @@ const sendDataStream = async () => {
}); });
visible.value = false visible.value = false
try { try {
loading.value = true
const response = await fetch(API_URL, { const response = await fetch(API_URL, {
method: "POST", method: "POST",
timeout: 10000, timeout: 10000,
@ -271,7 +283,7 @@ function handleEnter(event) {
} }
} }
function handleStop(item) { function handleStop() {
if (loading.value) { if (loading.value) {
loading.value = false loading.value = false
isStop.value = true isStop.value = true
@ -312,7 +324,7 @@ const placeholder = computed(() => {
}) })
const buttonDisabled = computed(() => { const buttonDisabled = computed(() => {
return loading.value || !prompt.value || prompt.value.trim() === '' return prompt.value.trim() === '' &&!loading.value
}) })
const footerClass = computed(() => { const footerClass = computed(() => {
@ -322,14 +334,35 @@ const footerClass = computed(() => {
} }
return classes return classes
}) })
const isShowBottom=ref(false)
onMounted(() => { onMounted(() => {
scrollRef.value.addEventListener('scroll', function() {
scrollToBottom() if (scrollRef.value.scrollTop + scrollRef.value.clientHeight +100>= scrollRef.value.scrollHeight) {
isShowBottom.value=false
}else {
isShowBottom.value=true
}
});
if (inputRef.value && !isMobile.value) { if (inputRef.value && !isMobile.value) {
inputRef.value?.focus() inputRef.value?.focus()
} }
}) })
const currentColor = ref('#ff0000'); //
const colors = ['#ff0000', '#ffffff']; //
let intervalId = null;
//
const changeColor = () => {
currentColor.value = currentColor.value === colors[0] ? colors[1] : colors[0];
};
watch(loading,()=>{
if (loading.value){
intervalId = setInterval(changeColor, 1000);
}else {
clearInterval(intervalId);
}
})
const fileList = ref([]); const fileList = ref([]);
onUnmounted(() => { onUnmounted(() => {
if (loading.value) { if (loading.value) {
@ -345,7 +378,6 @@ function getBase64(file) {
reader.onerror = error => reject(error); reader.onerror = error => reject(error);
}); });
} }
const previewVisible = ref(false); const previewVisible = ref(false);
const previewImage = ref(''); const previewImage = ref('');
const previewTitle = ref(''); const previewTitle = ref('');
@ -394,7 +426,10 @@ const upItemImage1 = async (file) => {
sendDataStream() sendDataStream()
} }
} }
watch(dataSources,()=>{
loading.value=false
scrollToBottom('auto')
})
const customRequest = async (file) => { const customRequest = async (file) => {
const res = await uploadImg({ const res = await uploadImg({
file: file.file, file: file.file,
@ -417,9 +452,10 @@ const customRequest = async (file) => {
@handle-clear="handleClear" @handle-clear="handleClear"
/> />
<main class="flex-1 overflow-hidden"> <main class="flex-1 overflow-hidden">
<div class="shortcut-arrow"> <transition name="fade">
<div class="shortcut-arrow" v-if="isShowBottom">
<div class="top"> <div class="top">
<n-button @click="scrollToBottom" type="primary" dashed circle> <n-button @click="scrollToBottom('smooth')" type="primary" dashed circle>
<template #icon> <template #icon>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512"> <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" <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48"
@ -433,7 +469,7 @@ const customRequest = async (file) => {
</n-button> </n-button>
</div> </div>
</div> </div>
</transition>
<div id="scrollRef" ref="scrollRef" class="h-full overflow-hidden overflow-y-auto"> <div id="scrollRef" ref="scrollRef" class="h-full overflow-hidden overflow-y-auto">
<div <div
@ -462,12 +498,12 @@ const customRequest = async (file) => {
@delete="handleDelete(index)" @delete="handleDelete(index)"
/> />
<div class="sticky bottom-0 left-0 flex justify-center"> <div class="sticky bottom-0 left-0 flex justify-center">
<NButton v-if="loading" type="warning" @click="handleStop(item)"> <!-- <NButton v-if="loading" type="warning" @click="handleStop(item)">
<template #icon> <template #icon>
<SvgIcon icon="ri:stop-circle-line"/> <SvgIcon icon="ri:stop-circle-line"/>
</template> </template>
停止响应 停止响应
</NButton> </NButton>-->
</div> </div>
</div> </div>
</template> </template>
@ -561,8 +597,13 @@ const customRequest = async (file) => {
</NAutoComplete> </NAutoComplete>
<NButton color="#8a2be2" type="primary" :disabled="buttonDisabled" @click="handleSubmit"> <NButton color="#8a2be2" type="primary" :disabled="buttonDisabled" @click="handleSubmit">
<template #icon> <template #icon>
<span class="dark:text-black"> <span class="dark:text-black" v-if="!loading">
<SvgIcon icon="ri:send-plane-fill"/> <SvgIcon icon="ri:send-plane-fill"/>
</span>
<span class="dark:text-black" v-if="loading">
<svg style="width:100%;height:100%;" xmlns="http://www.w3.org/2000/svg" :style="{color:currentColor}" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 12 12"><g fill="none"><path d="M5 4a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H5zm6 2A5 5 0 1 1 1 6a5 5 0 0 1 10 0zm-1 0a4 4 0 1 0-8 0a4 4 0 0 0 8 0z" fill="currentColor"></path></g></svg>
</span> </span>
</template> </template>
</NButton> </NButton>
@ -572,13 +613,19 @@ const customRequest = async (file) => {
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.fade-enter-active, .fade-leave-active {
transition: opacity 0.4s ease-in-out;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
.shortcut-arrow { .shortcut-arrow {
width: min-content; width: min-content;
height: min-content; height: min-content;
position: absolute; position: absolute;
z-index: 10; z-index: 10;
left: 50%; left: 50%;
bottom: 90px; bottom: 100px;
.top { .top {
transform: rotate(270deg) translateX(-50%); transform: rotate(270deg) translateX(-50%);
width: min-content; width: min-content;

@ -1,35 +1,45 @@
<script setup> <script setup>
import {createSession} from "@/api/api"; import {createSession} from "@/api/api";
import {Local} from "@/utils/storage/storage";
import {storeToRefs} from "pinia"; import {storeToRefs} from "pinia";
import { computed, ref, watch } from 'vue' import {computed, ref, watch} from 'vue'
import { NButton, NLayoutSider, useDialog,NSelect } from 'naive-ui' import {NButton, NLayoutSider, useDialog, NSelect} from 'naive-ui'
import List from './List.vue' import List from './List.vue'
import Footer from './Footer.vue' import Footer from './Footer.vue'
import { useAppStore, useChatStore } from '@/store' import {useAppStore, useChatStore} from '@/store'
import { useBasicLayout } from '@/hooks/useBasicLayout' import {useBasicLayout} from '@/hooks/useBasicLayout'
import { PromptStore, SvgIcon } from '@/components/common' import {PromptStore, SvgIcon} from '@/components/common'
import { t } from '@/locales' import {t} from '@/locales'
import {sessionDetailForSetup} from "@/store"; import {sessionDetailForSetup} from "@/store";
const sessionDetailData=sessionDetailForSetup()
const {sessionDetail, dataList,gptMode,currentListUuid,isGPT4 } = storeToRefs(sessionDetailData) const sessionDetailData = sessionDetailForSetup()
const {
sessionDetail,
dataList,
gptMode,
currentListUuid,
isGPT4
} = storeToRefs(sessionDetailData)
const appStore = useAppStore() const appStore = useAppStore()
const chatStore = useChatStore() const chatStore = useChatStore()
const dialog = useDialog() const dialog = useDialog()
const { isMobile } = useBasicLayout() const {isMobile} = useBasicLayout()
const show = ref(false) const show = ref(false)
const collapsed = computed(() => appStore.siderCollapsed) const collapsed = computed(() => appStore.siderCollapsed)
async function handleAdd() { async function handleAdd() {
await sessionDetailForSetup().createSessionStore() await sessionDetailForSetup().createSessionStore()
sessionDetail.value=[] sessionDetail.value = []
await chatStore.setActive(currentListUuid.value) await chatStore.setActive(currentListUuid.value)
} }
function handleUpdateCollapsed() { function handleUpdateCollapsed() {
appStore.setSiderCollapsed(!collapsed.value) appStore.setSiderCollapsed(!collapsed.value)
} }
function handleClearAll() { function handleClearAll() {
dialog.warning({ dialog.warning({
title: t('chat.deleteMessage'), title: t('chat.deleteMessage'),
@ -38,8 +48,9 @@ function handleClearAll() {
negativeText: t('common.no'), negativeText: t('common.no'),
onPositiveClick: () => { onPositiveClick: () => {
chatStore.clearHistory() chatStore.clearHistory()
if (isMobile.value) if (isMobile.value) {
appStore.setSiderCollapsed(true) appStore.setSiderCollapsed(true)
}
}, },
}) })
} }
@ -62,14 +73,32 @@ const mobileSafeArea = computed(() => {
} }
return {} return {}
}) })
const options=ref([{ label: 'GPT-3.5', value: 'gpt-3.5-turbo' }, const options = () => {
{ label: 'GPT-4.0', value: 'gpt-4-1106-preview' ,permission:'isGPT4'}, return Local.get('ruleBtn').find(x => x === 'gpt-4-btn') ? [{
{ label: 'GPT-V', value: 'gpt-4-vision-preview',permission:'isGPT4' }]) label: 'GPT-3.5',
setTimeout(()=>{ value: 'gpt-3.5-turbo',
if (localStorage.getItem('isGPT4')!=='true'){ permission: 'gpt-3.5-btn'
options.value=options.value.filter(item=>item.permission!=='isGPT4') },
} {
},1000) label: 'GPT-4.0',
value: 'gpt-4-1106-preview',
permission: 'gpt-4-btn'
},
{
label: 'GPT-V',
value: 'gpt-4-vision-preview',
permission: 'gpt-4-btn'
}] : [{
label: 'GPT-4.0',
value: 'gpt-4-1106-preview',
permission: 'gpt-4-btn'
},
{
label: 'GPT-V',
value: 'gpt-4-vision-preview',
permission: 'gpt-4-btn'
}]
}
watch( watch(
isMobile, isMobile,
(val) => { (val) => {
@ -97,17 +126,20 @@ watch(
<div class="flex flex-col h-full" :style="mobileSafeArea"> <div class="flex flex-col h-full" :style="mobileSafeArea">
<main class="flex flex-col flex-1 min-h-0"> <main class="flex flex-col flex-1 min-h-0">
<div class="p-4" style="display: flex"> <div class="p-4" style="display: flex">
<NButton color="#764CF6" style="flex:0.9;display: flex;justify-content: center;align-items: center;margin-right: auto" dashed block @click="handleAdd"> <NButton color="#764CF6"
style="flex:0.9;display: flex;justify-content: center;align-items: center;margin-right: auto" dashed
block @click="handleAdd"
>
新建会话 新建会话
</NButton> </NButton>
<div style="width: 110px"> <div style="width: 110px">
<n-select v-model:value="gptMode" :options="options" /> <n-select v-model:value="gptMode" :options="options()"/>
</div> </div>
</div> </div>
<div class="flex-1 min-h-0 pb-4 overflow-hidden"> <div class="flex-1 min-h-0 pb-4 overflow-hidden">
<List /> <List/>
</div> </div>
<!-- <div class="flex items-center p-4 space-x-4"> <!-- <div class="flex items-center p-4 space-x-4">
<div class="flex-1"> <div class="flex-1">
<NButton block @click="show = true"> <NButton block @click="show = true">
{{ $t('store.siderButton') }} {{ $t('store.siderButton') }}
@ -118,11 +150,11 @@ watch(
</NButton> </NButton>
</div>--> </div>-->
</main> </main>
<!-- <Footer />--> <!-- <Footer />-->
</div> </div>
</NLayoutSider> </NLayoutSider>
<template v-if="isMobile"> <template v-if="isMobile">
<div v-show="!collapsed" class="fixed inset-0 z-40 w-full h-full bg-black/40" @click="handleUpdateCollapsed" /> <div v-show="!collapsed" class="fixed inset-0 z-40 w-full h-full bg-black/40" @click="handleUpdateCollapsed"/>
</template> </template>
<PromptStore v-model:visible="show" /> <PromptStore v-model:visible="show"/>
</template> </template>

Loading…
Cancel
Save