mirror of https://github.com/jeecgboot/jeecg-boot
删除老的AI聊天界面
parent
507289ff6c
commit
b878f6b6be
|
@ -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>
|
|
@ -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>
|
|
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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(() => {
|
||||
// 删除标签或清空内容也会触发watch保存,此时不需watch保存需清除
|
||||
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>
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue