删除老的AI聊天界面

pull/8091/head
JEECG 2025-04-07 14:08:36 +08:00
parent 507289ff6c
commit b878f6b6be
7 changed files with 0 additions and 1340 deletions

View File

@ -1,473 +0,0 @@
<template>
<div class="chatWrap">
<div class="content">
<div class="main">
<div id="scrollRef" ref="scrollRef" class="scrollArea">
<template v-if="chatData.length">
<div class="chatContentArea">
<chatMessage
v-for="(item, index) of chatData"
:key="index"
:date-time="item.dateTime"
:text="item.text"
:inversion="item.inversion"
:error="item.error"
:loading="item.loading"
></chatMessage>
</div>
<div v-if="loading" class="stopArea">
<a-button type="primary" danger @click="handleStop" class="stopBtn">
<svg
t="1706148514627"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="5214"
width="18"
height="18"
>
<path
d="M512 967.111111c-250.311111 0-455.111111-204.8-455.111111-455.111111s204.8-455.111111 455.111111-455.111111 455.111111 204.8 455.111111 455.111111-204.8 455.111111-455.111111 455.111111z m0-56.888889c221.866667 0 398.222222-176.355556 398.222222-398.222222s-176.355556-398.222222-398.222222-398.222222-398.222222 176.355556-398.222222 398.222222 176.355556 398.222222 398.222222 398.222222z"
fill="currentColor"
p-id="5215"
></path>
<path d="M341.333333 341.333333h341.333334v341.333334H341.333333z" fill="currentColor" p-id="5216"></path>
</svg>
<span>停止响应</span>
</a-button>
</div>
</template>
<template v-else>
<div class="emptyArea">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="mr-2 text-3xl iconify iconify--ri"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M16 16a3 3 0 1 1 0 6a3 3 0 0 1 0-6M6 12a4 4 0 1 1 0 8a4 4 0 0 1 0-8m8.5-10a5.5 5.5 0 1 1 0 11a5.5 5.5 0 0 1 0-11"
></path>
</svg>
<span>新建聊天</span>
</div>
</template>
</div>
</div>
<div class="footer">
<div class="topArea">
<presetQuestion @outQuestion="handleOutQuestion"></presetQuestion>
</div>
<div class="bottomArea">
<a-button type="text" class="delBtn" @click="handleDelSession">
<svg
t="1706504908534"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="1584"
width="18"
height="18"
>
<path
d="M816.872727 158.254545h-181.527272V139.636364c0-39.563636-30.254545-69.818182-69.818182-69.818182h-107.054546c-39.563636 0-69.818182 30.254545-69.818182 69.818182v18.618181H207.127273c-48.872727 0-90.763636 41.890909-90.763637 93.09091s41.890909 90.763636 90.763637 90.763636h609.745454c51.2 0 90.763636-41.890909 90.763637-90.763636 0-51.2-41.890909-93.090909-90.763637-93.09091zM435.2 139.636364c0-13.963636 9.309091-23.272727 23.272727-23.272728h107.054546c13.963636 0 23.272727 9.309091 23.272727 23.272728v18.618181h-153.6V139.636364z m381.672727 155.927272H207.127273c-25.6 0-44.218182-20.945455-44.218182-44.218181 0-25.6 20.945455-44.218182 44.218182-44.218182h609.745454c25.6 0 44.218182 20.945455 44.218182 44.218182 0 23.272727-20.945455 44.218182-44.218182 44.218181zM835.490909 407.272727h-121.018182c-13.963636 0-23.272727 9.309091-23.272727 23.272728s9.309091 23.272727 23.272727 23.272727h97.745455V837.818182c0 39.563636-30.254545 69.818182-69.818182 69.818182h-37.236364V602.763636c0-13.963636-9.309091-23.272727-23.272727-23.272727s-23.272727 9.309091-23.272727 23.272727V907.636364h-118.690909V602.763636c0-13.963636-9.309091-23.272727-23.272728-23.272727s-23.272727 9.309091-23.272727 23.272727V907.636364H372.363636V602.763636c0-13.963636-9.309091-23.272727-23.272727-23.272727s-23.272727 9.309091-23.272727 23.272727V907.636364h-34.909091c-39.563636 0-69.818182-30.254545-69.818182-69.818182V453.818182H558.545455c13.963636 0 23.272727-9.309091 23.272727-23.272727s-9.309091-23.272727-23.272727-23.272728H197.818182c-13.963636 0-23.272727 9.309091-23.272727 23.272728V837.818182c0 65.163636 51.2 116.363636 116.363636 116.363636h451.490909c65.163636 0 116.363636-51.2 116.363636-116.363636V430.545455c0-13.963636-11.636364-23.272727-23.272727-23.272728z"
fill="currentColor"
p-id="1585"
></path>
</svg>
</a-button>
<a-button type="text" class="contextBtn" :class="[usingContext && 'enabled']" @click="handleUsingContext">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--ri"
width="20"
height="20"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10a9.956 9.956 0 0 1-4.708-1.175L2 22l1.176-5.29A9.956 9.956 0 0 1 2 12C2 6.477 6.477 2 12 2m0 2a8 8 0 0 0-8 8c0 1.335.326 2.618.94 3.766l.35.654l-.656 2.946l2.948-.654l.653.349A7.955 7.955 0 0 0 12 20a8 8 0 1 0 0-16m1 3v5h4v2h-6V7z"
></path>
</svg>
</a-button>
<a-textarea
ref="inputRef"
v-model:value="prompt"
:autoSize="{ minRows: 1, maxRows: 6 }"
:placeholder="placeholder"
@pressEnter="handleEnter"
autofocus
></a-textarea>
<a-button
@click="
() => {
handleSubmit();
}
"
:disabled="loading"
type="primary"
class="sendBtn"
>
<svg
t="1706147858151"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="4237"
width="1em"
height="1em"
>
<path
d="M865.28 202.5472c-17.1008-15.2576-41.0624-19.6608-62.5664-11.5712L177.7664 427.1104c-23.2448 8.8064-38.5024 29.696-39.6288 54.5792-1.1264 24.8832 11.9808 47.104 34.4064 58.0608l97.5872 47.7184c4.5056 2.2528 8.0896 6.0416 9.9328 10.6496l65.4336 161.1776c7.7824 19.1488 24.4736 32.9728 44.7488 37.0688 20.2752 4.096 41.0624-2.1504 55.6032-16.7936l36.352-36.352c6.4512-6.4512 16.5888-7.8848 24.576-3.3792l156.5696 88.8832c9.4208 5.3248 19.8656 8.0896 30.3104 8.0896 8.192 0 16.4864-1.6384 24.2688-5.0176 17.8176-7.68 30.72-22.8352 35.4304-41.6768l130.7648-527.1552c5.5296-22.016-1.7408-45.2608-18.8416-60.416z m-20.8896 50.7904L713.5232 780.4928c-1.536 6.2464-5.8368 11.3664-11.776 13.9264s-12.5952 2.1504-18.2272-1.024L526.9504 704.512c-9.4208-5.3248-19.8656-7.9872-30.208-7.9872-15.9744 0-31.744 6.144-43.52 17.92l-36.352 36.352c-3.8912 3.8912-8.9088 5.9392-14.2336 6.0416l55.6032-152.1664c0.512-1.3312 1.2288-2.56 2.2528-3.6864l240.3328-246.1696c8.2944-8.4992-2.048-21.9136-12.3904-16.0768L301.6704 559.8208c-4.096-3.584-8.704-6.656-13.6192-9.1136L190.464 502.9888c-11.264-5.5296-11.5712-16.1792-11.4688-19.3536 0.1024-3.1744 1.536-13.824 13.2096-18.2272L817.152 229.2736c10.4448-3.9936 18.0224 1.3312 20.8896 3.8912 2.8672 2.4576 9.0112 9.3184 6.3488 20.1728z"
p-id="4238"
fill="currentColor"
></path>
</svg>
</a-button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type { Ref } from 'vue';
import { computed, ref, createVNode, onUnmounted, onMounted } from 'vue';
import { useScroll } from '../hooks/useScroll';
import { EventSourcePolyfill } from 'event-source-polyfill';
import { ConfigEnum } from '/@/enums/httpEnum';
import { getToken } from '/@/utils/auth';
import { getAppEnvConfig } from '/@/utils/env';
import chatMessage from './chatMessage.vue';
import presetQuestion from './presetQuestion.vue';
import { DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons-vue';
import { message, Modal, Tabs } from 'ant-design-vue';
import { isObject, isString } from '/@/utils/is';
import '../style/github-markdown.less';
import '../style/highlight.less';
import '../style/style.less';
const props = defineProps(['chatData', 'uuid', 'dataSource']);
const emit = defineEmits(['save']);
const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll();
const prompt = ref<string>('');
const loading = ref<boolean>(false);
const inputRef = ref<Ref | null>(null);
// const chatData = computed(() => {
// return props.chatData;
// });
// ,
const usingContext = ref<any>(true);
const uuid = computed(() => {
return props.uuid;
});
let evtSource: any = null;
// const presetQuestion = ref(['', '', '稿']);
const { VITE_GLOB_API_URL } = getAppEnvConfig();
const conversationList = computed(() => props.chatData.filter((item) => !item.inversion && !!item.conversationOptions));
const placeholder = computed(() => {
return '来说点什么吧...Shift + Enter = 换行)';
});
function handleEnter(event: KeyboardEvent) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
handleSubmit();
}
}
function handleSubmit() {
let message = prompt.value;
if (!message || message.trim() === '') return;
prompt.value = '';
onConversation(message);
}
const handleOutQuestion = (message) => {
onConversation(message);
};
async function onConversation(message) {
if (loading.value) return;
loading.value = true;
if (props.chatData.length == 0) {
const findItem = props.dataSource.history.find((item) => item.uuid === uuid.value);
if (findItem && findItem.title == '新建聊天') {
findItem.title = message;
}
}
addChat(uuid.value, {
dateTime: new Date().toLocaleString(),
text: message,
inversion: true,
error: false,
conversationOptions: null,
requestOptions: { prompt: message, options: null },
});
scrollToBottom();
let options: any = {};
const lastContext = conversationList.value[conversationList.value.length - 1]?.conversationOptions;
if (lastContext && usingContext.value) options = { ...lastContext };
addChat(uuid.value, {
dateTime: new Date().toLocaleString(),
text: '思考中...',
loading: true,
inversion: false,
error: false,
conversationOptions: null,
requestOptions: { prompt: message, options: { ...options } },
});
scrollToBottom();
const initEventSource = () => {
let lastText = '';
if (typeof EventSource !== 'undefined') {
const token = getToken();
evtSource = new EventSourcePolyfill(
`${VITE_GLOB_API_URL}/test/ai/chat/send?message=${message}${options.parentMessageId ? '&topicId=' + options.parentMessageId : ''}`,
{
withCredentials: true,
headers: {
[ConfigEnum.TOKEN]: token,
},
}
); //
//
evtSource.onopen = function (e) {
console.log(e);
};
//
evtSource.onmessage = function (e) {
const data = e.data;
let delay = 0;
setTimeout(() => {
if (data === '[DONE]') {
updateChatSome(uuid, props.chatData.length - 1, { loading: false });
scrollToBottom();
handleStop();
evtSource.close(); //
} else {
try {
const _data = JSON.parse(data);
const content = _data.content;
if (content != undefined) {
lastText += content;
updateChat(uuid.value, props.chatData.length - 1, {
dateTime: new Date().toLocaleString(),
text: lastText,
inversion: false,
error: false,
loading: true,
conversationOptions: e.lastEventId == '[ERR]' ? null : { conversationId: data.conversationId, parentMessageId: e.lastEventId },
requestOptions: { prompt: message, options: { ...options } },
});
scrollToBottom();
} else {
// updateChatSome(uuid.value, props.chatData.length - 1, { loading: false });
// scrollToBottom();
// handleStop();
}
} catch (error: any) {
console.log('ai 聊天:::', error);
if (isObject(error) && isString(error.message) && error.message.endsWith('is not valid JSON')) {
return;
}
updateChatSome(uuid.value, props.chatData.length - 1, { loading: false });
scrollToBottom();
handleStop();
evtSource.close(); //
}
}
}, delay);
};
//
evtSource.onerror = function (e) {
// console.log(e);
if (e.error?.message || e.statusText) {
updateChat(uuid.value, props.chatData.length - 1, {
dateTime: new Date().toLocaleString(),
text: e.error?.message ?? e.statusText,
inversion: false,
error: false,
loading: true,
conversationOptions: null,
requestOptions: { prompt: message, options: { ...options } },
});
scrollToBottom();
}
evtSource.close(); //
updateChatSome(uuid.value, props.chatData.length - 1, { loading: false });
handleStop();
};
} else {
console.log('当前浏览器不支持使用EventSource接收服务器推送事件!');
}
};
initEventSource();
}
onUnmounted(() => {
evtSource?.close();
updateChatSome(uuid.value, props.chatData.length - 1, { loading: false });
});
const addChat = (uuid, data) => {
props.chatData.push({ ...data });
};
const updateChat = (uuid, index, data) => {
props.chatData.splice(index, 1, data);
};
const updateChatSome = (uuid, index, data) => {
props.chatData[index] = { ...props.chatData[index], ...data };
};
//
const handleDelSession = () => {
Modal.confirm({
title: '清空会话',
icon: createVNode(ExclamationCircleOutlined),
content: '是否清空会话?',
closable: true,
okText: '确定',
cancelText: '取消',
async onOk() {
try {
return await new Promise<void>((resolve) => {
props.chatData.length = 0;
emit('save');
resolve();
});
} catch {
return console.log('Oops errors!');
}
},
});
};
//
const handleStop = () => {
console.log('ai 聊天:::---停止响应');
if (loading.value) {
loading.value = false;
}
if (evtSource) {
evtSource?.close();
updateChatSome(uuid, props.chatData.length - 1, { loading: false });
}
};
// 使
const handleUsingContext = () => {
usingContext.value = !usingContext.value;
if (usingContext.value) {
message.success('当前模式下, 发送消息会携带之前的聊天记录');
} else {
message.warning('当前模式下, 发送消息不会携带之前的聊天记录');
}
};
onMounted(() => {
scrollToBottom();
});
</script>
<style lang="less" scoped>
.chatWrap {
width: 100%;
height: 100%;
padding: 20px;
.content {
height: 100%;
width: 100%;
background: #fff;
display: flex;
flex-direction: column;
}
}
.main {
flex: 1;
min-height: 0;
.scrollArea {
overflow-y: auto;
height: 100%;
}
.chatContentArea {
padding: 10px;
}
}
.emptyArea {
display: flex;
justify-content: center;
align-items: center;
color: #d4d4d4;
}
.stopArea {
display: flex;
justify-content: center;
padding: 10px 0;
.stopBtn {
display: flex;
justify-content: center;
align-items: center;
svg {
margin-right: 5px;
}
}
}
.footer {
display: flex;
flex-direction: column;
padding: 6px 16px;
.topArea {
padding-left: 94px;
margin-bottom: 6px;
}
.bottomArea {
display: flex;
align-items: center;
.ant-input {
margin: 0 16px;
}
.ant-input,
.ant-btn {
height: 36px;
}
textarea.ant-input {
padding-top: 6px;
padding-bottom: 6px;
}
.contextBtn,
.delBtn {
padding: 0;
width: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.delBtn {
margin-right: 8px;
}
.contextBtn {
color: #a8071a;
&.enabled {
color: @primary-color;
}
font-size: 18px;
}
.sendBtn {
padding: 0 10px;
font-size: 22px;
display: flex;
align-items: center;
}
}
}
</style>

View File

@ -1,74 +0,0 @@
<template>
<div class="chat" :class="[inversion ? 'self' : 'chatgpt']">
<div class="avatar">
<img v-if="inversion" :src="avatar()" />
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" aria-hidden="true" width="1em" height="1em">
<path
d="M29.71,13.09A8.09,8.09,0,0,0,20.34,2.68a8.08,8.08,0,0,0-13.7,2.9A8.08,8.08,0,0,0,2.3,18.9,8,8,0,0,0,3,25.45a8.08,8.08,0,0,0,8.69,3.87,8,8,0,0,0,6,2.68,8.09,8.09,0,0,0,7.7-5.61,8,8,0,0,0,5.33-3.86A8.09,8.09,0,0,0,29.71,13.09Zm-12,16.82a6,6,0,0,1-3.84-1.39l.19-.11,6.37-3.68a1,1,0,0,0,.53-.91v-9l2.69,1.56a.08.08,0,0,1,.05.07v7.44A6,6,0,0,1,17.68,29.91ZM4.8,24.41a6,6,0,0,1-.71-4l.19.11,6.37,3.68a1,1,0,0,0,1,0l7.79-4.49V22.8a.09.09,0,0,1,0,.08L13,26.6A6,6,0,0,1,4.8,24.41ZM3.12,10.53A6,6,0,0,1,6.28,7.9v7.57a1,1,0,0,0,.51.9l7.75,4.47L11.85,22.4a.14.14,0,0,1-.09,0L5.32,18.68a6,6,0,0,1-2.2-8.18Zm22.13,5.14-7.78-4.52L20.16,9.6a.08.08,0,0,1,.09,0l6.44,3.72a6,6,0,0,1-.9,10.81V16.56A1.06,1.06,0,0,0,25.25,15.67Zm2.68-4-.19-.12-6.36-3.7a1,1,0,0,0-1.05,0l-7.78,4.49V9.2a.09.09,0,0,1,0-.09L19,5.4a6,6,0,0,1,8.91,6.21ZM11.08,17.15,8.38,15.6a.14.14,0,0,1-.05-.08V8.1a6,6,0,0,1,9.84-4.61L18,3.6,11.61,7.28a1,1,0,0,0-.53.91ZM12.54,14,16,12l3.47,2v4L16,20l-3.47-2Z"
fill="currentColor"
/>
</svg>
</div>
<div class="content">
<p class="date">{{ dateTime }}</p>
<div class="msgArea">
<chatText :text="text" :inversion="inversion" :error="error" :loading="loading"></chatText>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import chatText from './chatText.vue';
import defaultAvatar from '../assets/avatar.jpg';
import { useUserStore } from '/@/store/modules/user';
const props = defineProps(['dateTime', 'text', 'inversion', 'error', 'loading']);
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
const { userInfo } = useUserStore();
const avatar = () => {
return getFileAccessHttpUrl(userInfo?.avatar)|| defaultAvatar;
};
</script>
<style lang="less" scoped>
.chat {
display: flex;
margin-bottom: 1.5rem;
&.self {
flex-direction: row-reverse;
.avatar {
margin-right: 0;
margin-left: 10px;
}
.msgArea {
flex-direction: row-reverse;
}
.date {
text-align: right;
}
}
}
.avatar {
flex: none;
margin-right: 10px;
img {
width: 34px;
height: 34px;
border-radius: 50%;
overflow: hidden;
}
svg {
font-size: 28px;
}
}
.content {
.date {
color: #b4bbc4;
font-size: 0.75rem;
margin-bottom: 10px;
}
.msgArea {
display: flex;
}
}
</style>

View File

@ -1,186 +0,0 @@
export const localData = {
active: 1002,
usingContext: true,
history: [
{
title: '02',
uuid: 1706083575869,
isEdit: false,
},
{
uuid: 1002,
title: '01',
isEdit: false,
},
],
chat: [
{
uuid: 1706083575869,
data: [
{
dateTime: '2024/1/24 16:06:27',
text: '',
inversion: true,
error: false,
conversationOptions: null,
requestOptions: {
prompt: '',
options: null,
},
},
{
dateTime: '2024/1/24 16:06:29',
text: 'Hello! How can I assist you today?',
inversion: false,
error: false,
loading: false,
conversationOptions: {
parentMessageId: 'chatcmpl-8kSZA0wju7X8sOdJIyxtpDj0RQVu1',
},
requestOptions: {
prompt: '',
options: {},
},
},
],
},
{
uuid: 1002,
data: [
{
dateTime: '2024/1/24 14:01:52',
text: '1',
inversion: true,
error: false,
conversationOptions: null,
requestOptions: {
prompt: '1',
options: null,
},
},
{
dateTime: '2024/1/24 14:01:54',
text: 'Yes, how can I assist you?',
inversion: false,
error: false,
loading: false,
conversationOptions: {
parentMessageId: 'chatcmpl-8kQcb6mbF04o5hpule4SdHk2jFvNQ',
},
requestOptions: {
prompt: '1',
options: {},
},
},
{
dateTime: '2024/1/24 14:03:45',
text: '',
inversion: true,
error: false,
conversationOptions: null,
requestOptions: {
prompt: '',
options: null,
},
},
{
dateTime: '2024/1/24 14:03:47',
text: "I'm sorry if my previous response was not clear. Please let me know how I can help you or what you would like to discuss.",
inversion: false,
error: false,
loading: false,
conversationOptions: {
parentMessageId: 'chatcmpl-8kQeQ2t8YCXmLeF0ECGkkuOJlk4Pi',
},
requestOptions: {
prompt: '',
options: {
parentMessageId: 'chatcmpl-8kQcb6mbF04o5hpule4SdHk2jFvNQ',
},
},
},
{
dateTime: '2024/1/24 14:10:19',
text: 'js ',
inversion: true,
error: false,
conversationOptions: null,
requestOptions: {
prompt: 'js ',
options: null,
},
},
{
dateTime: '2024/1/24 14:10:33',
text: 'JavaScript supports recursion, which is the process of a function calling itself. Recursion can be useful for solving problems that can be broken down into smaller, similar sub-problems.\n\nHere\'s an example of a simple recursive function in JavaScript:\n\n```javascript\nfunction countdown(n) {\n if (n <= 0) {\n console.log("Done!");\n } else {\n console.log(n);\n countdown(n - 1); // recursive call\n }\n}\n\ncountdown(5);\n```\n\nIn this example, the `countdown` function takes an argument `n` and logs the value of `n` to the console. If `n` is greater than zero, it then calls itself with `n - 1`. This process continues until `n` becomes less than or equal to zero, at which point it logs "Done!".\n\nRecursion can be helpful in solving problems that involve tree structures, factorial calculations, searching algorithms, and more. However, it\'s important to use recursion properly to avoid infinite loops or excessive stack usage.',
inversion: false,
error: false,
loading: false,
conversationOptions: {
parentMessageId: 'chatcmpl-8kQkmCbnRe4fG1FhWTlY0EyHTpqau',
},
requestOptions: {
prompt: 'js ',
options: {
parentMessageId: 'chatcmpl-8kQeQ2t8YCXmLeF0ECGkkuOJlk4Pi',
},
},
},
{
dateTime: '2024/1/24 14:17:15',
text: 'js ',
inversion: true,
error: false,
conversationOptions: null,
requestOptions: {
prompt: 'js ',
options: null,
},
},
{
dateTime: '2024/1/24 14:23:50',
text: "Certainly! Here's an example of how you can use recursion in JavaScript:\n\n```javascript\nfunction factorial(n) {\n if (n === 0) {\n return 1;\n } else {\n return n * factorial(n - 1);\n }\n}\n\nconsole.log(factorial(5)); // Output: 120\n```\n\nIn this example, the `factorial` function calculates the factorial of a given number `n` using recursion. If `n` is equal to 0, it returns 1, which is the base case. Otherwise, it recursively calls itself with `n - 1`, multiplying the current value of `n` with the result of the recursive call.\n\nWhen calling `factorial(5)`, the function will execute as follows:\n\n- `factorial(5)` calls `factorial(4)`\n- `factorial(4)` calls `factorial(3)`\n- `factorial(3)` calls `factorial(2)`\n- `factorial(2)` calls `factorial(1)`\n- `factorial(1)` calls `factorial(0)`\n- `factorial(0)` returns 1\n- `factorial(1)` returns 1 * 1 = 1\n- `factorial(2)` returns 2 * 1 = 2\n- `factorial(3)` returns 3 * 2 = 6\n- `factorial(4)` returns 4 * 6 = 24\n- `factorial(5)` returns 5 * 24 = 120\n\nThe final result is then printed to the console using `console.log`.",
inversion: false,
error: false,
loading: false,
conversationOptions: {
parentMessageId: 'chatcmpl-8kQwWVoZoWyqjbWuwMJmu6w3hBvXj',
},
requestOptions: {
prompt: 'js ',
options: {
parentMessageId: 'chatcmpl-8kQkmCbnRe4fG1FhWTlY0EyHTpqau',
},
},
},
{
dateTime: '2024/1/24 15:05:30',
text: '///',
inversion: true,
error: false,
conversationOptions: null,
requestOptions: {
prompt: '///',
options: null,
},
},
{
dateTime: '2024/1/24 15:05:33',
text: "I apologize if my previous response was not what you were expecting. If you have any specific questions or need further assistance, please let me know and I'll be happy to help.",
inversion: false,
error: false,
loading: false,
conversationOptions: {
parentMessageId: 'chatcmpl-8kRcAggkC4u47d34UcQW3cI0htw0w',
},
requestOptions: {
prompt: '///',
options: {
parentMessageId: 'chatcmpl-8kQwWVoZoWyqjbWuwMJmu6w3hBvXj',
},
},
},
],
},
],
};

View File

@ -1,44 +0,0 @@
import type { Ref } from 'vue'
import { nextTick, ref } from 'vue'
type ScrollElement = HTMLDivElement | null
interface ScrollReturn {
scrollRef: Ref<ScrollElement>
scrollToBottom: () => Promise<void>
scrollToTop: () => Promise<void>
scrollToBottomIfAtBottom: () => Promise<void>
}
export function useScroll(): ScrollReturn {
const scrollRef = ref<ScrollElement>(null)
const scrollToBottom = async () => {
await nextTick()
if (scrollRef.value)
scrollRef.value.scrollTop = scrollRef.value.scrollHeight
}
const scrollToTop = async () => {
await nextTick()
if (scrollRef.value)
scrollRef.value.scrollTop = 0
}
const scrollToBottomIfAtBottom = async () => {
await nextTick()
if (scrollRef.value) {
const threshold = 100 // Threshold, indicating the distance threshold to the bottom of the scroll bar.
const distanceToBottom = scrollRef.value.scrollHeight - scrollRef.value.scrollTop - scrollRef.value.clientHeight
if (distanceToBottom <= threshold)
scrollRef.value.scrollTop = scrollRef.value.scrollHeight
}
}
return {
scrollRef,
scrollToBottom,
scrollToTop,
scrollToBottomIfAtBottom,
}
}

View File

@ -1,222 +0,0 @@
<template>
<div ref="chatContainerRef" class="chat-container" :style="chatContainerStyle">
<template v-if="dataSource">
<div class="leftArea" :class="[expand ? 'expand' : 'shrink']">
<div class="content">
<slide v-if="uuid" :dataSource="dataSource" @save="handleSave"></slide>
</div>
<div class="toggle-btn" @click="handleToggle">
<span class="icon">
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M5.64645 3.14645C5.45118 3.34171 5.45118 3.65829 5.64645 3.85355L9.79289 8L5.64645 12.1464C5.45118 12.3417 5.45118 12.6583 5.64645 12.8536C5.84171 13.0488 6.15829 13.0488 6.35355 12.8536L10.8536 8.35355C11.0488 8.15829 11.0488 7.84171 10.8536 7.64645L6.35355 3.14645C6.15829 2.95118 5.84171 2.95118 5.64645 3.14645Z"
fill="currentColor"
></path>
</svg>
</span>
</div>
</div>
<div class="rightArea" :class="[expand ? 'expand' : 'shrink']">
<chat v-if="uuid && chatVisible" :uuid="uuid" :chatData="chatData" :dataSource="dataSource" @save="handleSave"></chat>
</div>
</template>
<Spin v-else :spinning="true"></Spin>
</div>
</template>
<script setup lang="ts">
import slide from './components/slide.vue';
import chat from './components/chat.vue';
import { Spin } from 'ant-design-vue';
import { ref, watch, nextTick, onUnmounted } from 'vue';
import { useUserStore } from '/@/store/modules/user';
import { JEECG_CHAT_KEY } from '/@/enums/cacheEnum';
import { defHttp } from '/@/utils/http/axios';
const configUrl = {
get: '/test/ai/chat/history/get',
save: '/test/ai/chat/history/save',
};
const userId = useUserStore().getUserInfo?.id;
const localKey = JEECG_CHAT_KEY + userId;
let timer: any = null;
let unwatch01: any = null;
let unwatch02: any = null;
const dataSource = ref<any>(null);
const uuid = ref(null);
const chatData = ref([]);
const expand = ref<any>(true);
const chatVisible = ref(true);
const chatContainerRef = ref<any>(null);
const chatContainerStyle = ref({});
const handleToggle = () => {
expand.value = !expand.value;
};
//
const init = () => {
const priming = () => {
dataSource.value = {
active: 1002,
usingContext: true,
history: [{ uuid: 1002, title: '新建聊天', isEdit: false }],
chat: [{ uuid: 1002, data: [] }],
};
};
defHttp
.get({ url: configUrl.get })
.then((res) => {
const { content } = res;
if (content) {
const json = JSON.parse(content);
if (json.history?.length) {
dataSource.value = json;
} else {
priming();
}
} else {
priming();
}
!unwatch01 && execute();
})
.catch(() => {
priming();
});
};
const save = (content) => {
defHttp.post({ url: configUrl.save, params: { content: JSON.stringify(content) } }, { isTransformResponse: false });
};
const handleSave = () => {
//
save(dataSource.value);
setTimeout(() => {
// watchwatch
clearTimeout(timer);
}, 50);
};
// dataSource
const execute = () => {
unwatch01 = watch(
() => dataSource.value.active,
(value) => {
if (value) {
const findItem = dataSource.value.chat.find((item) => item.uuid === value);
if (findItem) {
uuid.value = findItem.uuid;
chatData.value = findItem.data;
}
chatVisible.value = false;
nextTick(() => {
chatVisible.value = true;
});
}
},
{ immediate: true }
);
unwatch02 = watch(dataSource.value, () => {
clearInterval(timer);
timer = setTimeout(() => {
save(dataSource.value);
}, 2e3);
});
};
onUnmounted(() => {
unwatch01 && unwatch01();
unwatch02 && unwatch02();
});
watch(
() => chatContainerRef.value,
() => {
chatContainerStyle.value = { height: `${chatContainerRef.value.offsetHeight}px` };
}
);
init();
</script>
<style scoped lang="less">
@width: 260px;
.chat-container {
position: relative;
height: 100%;
box-shadow:
0 0 #0000,
0 0 #0000,
0 0 #0000,
0 0 #0000,
0 4px 6px -1px rgb(0 0 0 / 0.1),
0 2px 4px -2px rgb(0 0 0 / 0.1);
border-width: 1px;
border-radius: 0.375rem;
display: flex;
overflow: hidden;
:deep(.ant-spin) {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.leftArea {
width: @width;
transition: 0.3s left;
position: absolute;
left: 0;
height: 100%;
.content {
width: 100%;
height: 100%;
overflow: hidden;
}
&.shrink {
left: -@width;
.toggle-btn {
.icon {
transform: rotate(0deg);
}
}
}
.toggle-btn {
transition:
color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
right 0.3s cubic-bezier(0.4, 0, 0.2, 1),
left 0.3s cubic-bezier(0.4, 0, 0.2, 1),
border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
width: 24px;
height: 24px;
position: absolute;
top: 50%;
right: 0;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
color: rgb(51, 54, 57);
border: 1px solid rgb(239, 239, 245);
background-color: #fff;
box-shadow: 0 2px 4px 0px rgba(0, 0, 0, 0.06);
transform: translateX(50%) translateY(-50%);
z-index: 1;
}
.icon {
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
transform: rotate(180deg);
font-size: 18px;
height: 18px;
svg {
height: 1em;
width: 1em;
vertical-align: top;
}
}
}
.rightArea {
margin-left: @width;
transition: 0.3s margin-left;
&.shrink {
margin-left: 0;
}
flex: 1;
min-width: 0;
}
</style>

View File

@ -1,206 +0,0 @@
html.dark {
pre code.hljs {
display: block;
overflow-x: auto;
padding: 1em
}
code.hljs {
padding: 3px 5px
}
.hljs {
color: #abb2bf;
background: #282c34
}
.hljs-keyword,
.hljs-operator,
.hljs-pattern-match {
color: #f92672
}
.hljs-function,
.hljs-pattern-match .hljs-constructor {
color: #61aeee
}
.hljs-function .hljs-params {
color: #a6e22e
}
.hljs-function .hljs-params .hljs-typing {
color: #fd971f
}
.hljs-module-access .hljs-module {
color: #7e57c2
}
.hljs-constructor {
color: #e2b93d
}
.hljs-constructor .hljs-string {
color: #9ccc65
}
.hljs-comment,
.hljs-quote {
color: #b18eb1;
font-style: italic
}
.hljs-doctag,
.hljs-formula {
color: #c678dd
}
.hljs-deletion,
.hljs-name,
.hljs-section,
.hljs-selector-tag,
.hljs-subst {
color: #e06c75
}
.hljs-literal {
color: #56b6c2
}
.hljs-addition,
.hljs-attribute,
.hljs-meta .hljs-string,
.hljs-regexp,
.hljs-string {
color: #98c379
}
.hljs-built_in,
.hljs-class .hljs-title,
.hljs-title.class_ {
color: #e6c07b
}
.hljs-attr,
.hljs-number,
.hljs-selector-attr,
.hljs-selector-class,
.hljs-selector-pseudo,
.hljs-template-variable,
.hljs-type,
.hljs-variable {
color: #d19a66
}
.hljs-bullet,
.hljs-link,
.hljs-meta,
.hljs-selector-id,
.hljs-symbol,
.hljs-title {
color: #61aeee
}
.hljs-emphasis {
font-style: italic
}
.hljs-strong {
font-weight: 700
}
.hljs-link {
text-decoration: underline
}
}
html {
pre code.hljs {
display: block;
overflow-x: auto;
padding: 1em
}
code.hljs {
padding: 3px 5px;
&::-webkit-scrollbar {
height: 4px;
}
}
.hljs {
color: #383a42;
background: #fafafa
}
.hljs-comment,
.hljs-quote {
color: #a0a1a7;
font-style: italic
}
.hljs-doctag,
.hljs-formula,
.hljs-keyword {
color: #a626a4
}
.hljs-deletion,
.hljs-name,
.hljs-section,
.hljs-selector-tag,
.hljs-subst {
color: #e45649
}
.hljs-literal {
color: #0184bb
}
.hljs-addition,
.hljs-attribute,
.hljs-meta .hljs-string,
.hljs-regexp,
.hljs-string {
color: #50a14f
}
.hljs-attr,
.hljs-number,
.hljs-selector-attr,
.hljs-selector-class,
.hljs-selector-pseudo,
.hljs-template-variable,
.hljs-type,
.hljs-variable {
color: #986801
}
.hljs-bullet,
.hljs-link,
.hljs-meta,
.hljs-selector-id,
.hljs-symbol,
.hljs-title {
color: #4078f2
}
.hljs-built_in,
.hljs-class .hljs-title,
.hljs-title.class_ {
color: #c18401
}
.hljs-emphasis {
font-style: italic
}
.hljs-strong {
font-weight: 700
}
.hljs-link {
text-decoration: underline
}
}

View File

@ -1,135 +0,0 @@
.markdown-body {
background-color: transparent;
font-size: 14px;
p {
white-space: pre-wrap;
}
ol {
list-style-type: decimal;
}
ul {
list-style-type: disc;
}
pre code,
pre tt {
line-height: 1.65;
}
.highlight pre,
pre {
background-color: #fff;
}
code.hljs {
padding: 0;
}
.code-block {
&-wrapper {
position: relative;
padding-top: 24px;
}
&-header {
position: absolute;
top: 5px;
right: 0;
width: 100%;
padding: 0 1rem;
display: flex;
justify-content: flex-end;
align-items: center;
color: #b3b3b3;
&__copy {
cursor: pointer;
margin-left: 0.5rem;
user-select: none;
&:hover {
color: #65a665;
}
}
}
}
&.markdown-body-generate>dd:last-child:after,
&.markdown-body-generate>dl:last-child:after,
&.markdown-body-generate>dt:last-child:after,
&.markdown-body-generate>h1:last-child:after,
&.markdown-body-generate>h2:last-child:after,
&.markdown-body-generate>h3:last-child:after,
&.markdown-body-generate>h4:last-child:after,
&.markdown-body-generate>h5:last-child:after,
&.markdown-body-generate>h6:last-child:after,
&.markdown-body-generate>li:last-child:after,
&.markdown-body-generate>ol:last-child li:last-child:after,
&.markdown-body-generate>p:last-child:after,
&.markdown-body-generate>pre:last-child code:after,
&.markdown-body-generate>td:last-child:after,
&.markdown-body-generate>ul:last-child li:last-child:after {
animation: blink 1s steps(5, start) infinite;
color: #000;
content: '_';
font-weight: 700;
margin-left: 3px;
vertical-align: baseline;
}
@keyframes blink {
to {
visibility: hidden;
}
}
}
html.dark {
.markdown-body {
&.markdown-body-generate>dd:last-child:after,
&.markdown-body-generate>dl:last-child:after,
&.markdown-body-generate>dt:last-child:after,
&.markdown-body-generate>h1:last-child:after,
&.markdown-body-generate>h2:last-child:after,
&.markdown-body-generate>h3:last-child:after,
&.markdown-body-generate>h4:last-child:after,
&.markdown-body-generate>h5:last-child:after,
&.markdown-body-generate>h6:last-child:after,
&.markdown-body-generate>li:last-child:after,
&.markdown-body-generate>ol:last-child li:last-child:after,
&.markdown-body-generate>p:last-child:after,
&.markdown-body-generate>pre:last-child code:after,
&.markdown-body-generate>td:last-child:after,
&.markdown-body-generate>ul:last-child li:last-child:after {
color: #65a665;
}
}
.message-reply {
.whitespace-pre-wrap {
white-space: pre-wrap;
color: var(--n-text-color);
}
}
.highlight pre,
pre {
background-color: #282c34;
}
}
@media screen and (max-width: 533px) {
.markdown-body .code-block-wrapper {
padding: unset;
code {
padding: 24px 16px 16px 16px;
}
}
}