2024-07-14 16:42:38 +08:00
|
|
|
|
<!-- ChatComponent.vue -->
|
|
|
|
|
|
<template>
|
2024-07-25 20:30:28 +08:00
|
|
|
|
<div class="chat" ref="chatContainer">
|
2024-07-14 16:42:38 +08:00
|
|
|
|
<div class="header">
|
|
|
|
|
|
<div class="header__left">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-if="!state.isSidebarOpen"
|
|
|
|
|
|
class="close nav-btn"
|
|
|
|
|
|
@click="state.isSidebarOpen = true"
|
|
|
|
|
|
>
|
|
|
|
|
|
<MenuOutlined />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="newchat nav-btn"
|
|
|
|
|
|
@click="$emit('newconv')"
|
|
|
|
|
|
>
|
2024-07-25 20:30:28 +08:00
|
|
|
|
<PlusCircleOutlined /> <span class="text">新对话</span>
|
2024-07-14 16:42:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="header__right">
|
2024-07-18 02:46:58 +08:00
|
|
|
|
<!-- <div class="nav-btn text metas">
|
|
|
|
|
|
<CompassFilled v-if="meta.use_web" />
|
|
|
|
|
|
<GoldenFilled v-if="meta.use_graph"/>
|
|
|
|
|
|
</div> -->
|
2024-07-29 01:00:02 +08:00
|
|
|
|
<a-dropdown v-if="meta.selectedKB !== null">
|
2024-07-25 20:30:28 +08:00
|
|
|
|
<a class="ant-dropdown-link nav-btn" @click.prevent>
|
2024-07-29 01:00:02 +08:00
|
|
|
|
<!-- <component :is="meta.selectedKB === null ? BookOutlined : BookFilled" /> -->
|
|
|
|
|
|
<BookOutlined />
|
|
|
|
|
|
<span class="text">{{ meta.selectedKB === null ? '不使用' : opts.databases[meta.selectedKB]?.name }}</span>
|
2024-07-17 18:52:20 +08:00
|
|
|
|
</a>
|
|
|
|
|
|
<template #overlay>
|
|
|
|
|
|
<a-menu>
|
2024-07-29 01:00:02 +08:00
|
|
|
|
<a-menu-item v-for="(db, index) in opts.databases" :key="index" @click="meta.selectedKB=index">
|
2024-07-25 20:30:28 +08:00
|
|
|
|
<a href="javascript:;" >{{ db.name }}</a>
|
2024-07-17 18:52:20 +08:00
|
|
|
|
</a-menu-item>
|
2024-07-29 01:00:02 +08:00
|
|
|
|
<a-menu-item @click="meta.selectedKB = null">
|
2024-07-18 02:46:58 +08:00
|
|
|
|
<a href="javascript:;">不使用</a>
|
2024-07-17 18:52:20 +08:00
|
|
|
|
</a-menu-item>
|
|
|
|
|
|
</a-menu>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</a-dropdown>
|
2024-08-07 17:25:06 +08:00
|
|
|
|
<div class="nav-btn text " @click="opts.showPanel = !opts.showPanel">
|
2024-07-29 01:00:02 +08:00
|
|
|
|
<component :is="opts.showPanel ? FolderOpenOutlined : FolderOutlined" /> <span class="text">选项</span>
|
2024-07-22 00:01:01 +08:00
|
|
|
|
</div>
|
2024-08-07 17:25:06 +08:00
|
|
|
|
<div v-if="opts.showPanel" class="my-panal swing-in-top-fwd" ref="panel">
|
2024-08-07 17:42:26 +08:00
|
|
|
|
<div class="flex-center" v-if="configStore.config.enable_knowledge_base">
|
2024-07-18 02:46:58 +08:00
|
|
|
|
知识库
|
|
|
|
|
|
<div @click.stop>
|
|
|
|
|
|
<a-dropdown>
|
|
|
|
|
|
<a class="ant-dropdown-link " @click.prevent>
|
2024-07-29 01:00:02 +08:00
|
|
|
|
<!-- <component :is="meta.selectedKB === null ? BookOutlined : BookFilled" /> -->
|
|
|
|
|
|
<BookOutlined />
|
|
|
|
|
|
<span class="text">{{ meta.selectedKB === null ? '不使用' : opts.databases[meta.selectedKB]?.name }}</span>
|
2024-07-18 02:46:58 +08:00
|
|
|
|
</a>
|
|
|
|
|
|
<template #overlay>
|
|
|
|
|
|
<a-menu>
|
2024-07-29 01:00:02 +08:00
|
|
|
|
<a-menu-item v-for="(db, index) in opts.databases" :key="index" @click="meta.selectedKB=index">
|
2024-07-18 02:46:58 +08:00
|
|
|
|
<a href="javascript:;">{{ db.name }}</a>
|
|
|
|
|
|
</a-menu-item>
|
2024-07-29 01:00:02 +08:00
|
|
|
|
<a-menu-item @click="meta.selectedKB = null">
|
2024-07-18 02:46:58 +08:00
|
|
|
|
<a href="javascript:;">不使用</a>
|
|
|
|
|
|
</a-menu-item>
|
|
|
|
|
|
</a-menu>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</a-dropdown>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2024-08-07 17:42:26 +08:00
|
|
|
|
<div class="flex-center" @click="meta.use_graph = !meta.use_graph" v-if="configStore.config.enable_knowledge_base">
|
2024-07-18 02:46:58 +08:00
|
|
|
|
图数据库 <div @click.stop><a-switch v-model:checked="meta.use_graph" /></div>
|
|
|
|
|
|
</div>
|
2024-08-07 17:42:26 +08:00
|
|
|
|
<div class="flex-center" @click="meta.use_web = !meta.use_web" v-if="configStore.config.enable_search_engine">
|
2024-07-18 02:46:58 +08:00
|
|
|
|
搜索引擎(Bing) <div @click.stop><a-switch v-model:checked="meta.use_web" /></div>
|
|
|
|
|
|
</div>
|
2024-08-07 17:42:26 +08:00
|
|
|
|
<div class="flex-center" @click="meta.rewrite_query = !meta.rewrite_query" v-if="configStore.config.enable_reranker">
|
2024-07-29 01:00:02 +08:00
|
|
|
|
重写查询 <div @click.stop><a-switch v-model:checked="meta.rewrite_query" /></div>
|
|
|
|
|
|
</div>
|
2024-08-07 17:42:26 +08:00
|
|
|
|
<div class="flex-center" @click="meta.rewrite_query = !meta.rewrite_query">
|
2024-07-31 20:22:05 +08:00
|
|
|
|
流式输出 <div @click.stop><a-switch v-model:checked="meta.stream" /></div>
|
|
|
|
|
|
</div>
|
2024-08-07 17:42:26 +08:00
|
|
|
|
<div class="flex-center" @click="meta.summary_title = !meta.summary_title">
|
2024-07-31 20:22:05 +08:00
|
|
|
|
总结对话标题 <div @click.stop><a-switch v-model:checked="meta.summary_title" /></div>
|
|
|
|
|
|
</div>
|
2024-08-07 17:42:26 +08:00
|
|
|
|
<div class="flex-center">
|
|
|
|
|
|
最大历史轮数 <a-input-number id="inputNumber" v-model:value="meta.history_round" :min="1" :max="50" />
|
|
|
|
|
|
</div>
|
2024-07-18 02:46:58 +08:00
|
|
|
|
</div>
|
2024-07-14 16:42:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-if="conv.messages.length == 0" class="chat-examples">
|
|
|
|
|
|
<h1>你好,我是 Athena 😊</h1>
|
2024-07-29 01:00:02 +08:00
|
|
|
|
<div class="opts">
|
2024-07-14 16:42:38 +08:00
|
|
|
|
<div
|
|
|
|
|
|
class="opt__button"
|
|
|
|
|
|
v-for="(exp, key) in examples"
|
|
|
|
|
|
:key="key"
|
|
|
|
|
|
@click="autoSend(exp)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ exp }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2024-07-25 20:30:28 +08:00
|
|
|
|
<div class="chat-box">
|
2024-07-14 16:42:38 +08:00
|
|
|
|
<div
|
|
|
|
|
|
v-for="message in conv.messages"
|
|
|
|
|
|
:key="message.id"
|
|
|
|
|
|
class="message-box"
|
|
|
|
|
|
:class="message.role"
|
|
|
|
|
|
>
|
|
|
|
|
|
<p v-if="message.role=='sent'" style="white-space: pre-line" class="message-text">{{ message.text }}</p>
|
2024-07-17 18:52:20 +08:00
|
|
|
|
<p v-else
|
|
|
|
|
|
v-html="renderMarkdown(message.text)"
|
|
|
|
|
|
class="message-md"
|
|
|
|
|
|
@click="consoleMsg(message)"></p>
|
2024-07-25 20:30:28 +08:00
|
|
|
|
|
2024-07-28 16:16:52 +08:00
|
|
|
|
<div class="refs" v-if="message.role=='received' && message.groupedResults && message.status=='finished'">
|
2024-07-25 20:30:28 +08:00
|
|
|
|
<a-tag
|
2024-07-28 16:16:52 +08:00
|
|
|
|
class="filetag"
|
|
|
|
|
|
v-for="(results, filename) in message.groupedResults"
|
|
|
|
|
|
:key="filename"
|
|
|
|
|
|
@click="opts.openDetail = true"
|
|
|
|
|
|
:bordered="false"
|
2024-07-25 20:30:28 +08:00
|
|
|
|
>
|
2024-07-28 16:16:52 +08:00
|
|
|
|
{{ filename }}
|
|
|
|
|
|
<a-drawer
|
|
|
|
|
|
v-model:open="opts.openDetail"
|
|
|
|
|
|
title="检索详情"
|
|
|
|
|
|
width="800"
|
|
|
|
|
|
:contentWrapperStyle="{ maxWidth: '100%'}"
|
|
|
|
|
|
placement="right"
|
|
|
|
|
|
class="retrieval-detail"
|
|
|
|
|
|
rootClassName="root"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="fileinfo">
|
|
|
|
|
|
<p><strong>文件名:</strong> {{ results[0].file.filename }}</p>
|
|
|
|
|
|
<p><strong>文件类型:</strong> {{ results[0].file.type }}</p>
|
|
|
|
|
|
<p><strong>创建时间:</strong> {{ new Date(results[0].file.created_at * 1000).toLocaleString() }}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-for="(res, idx) in results" :key="idx" class="result-item">
|
|
|
|
|
|
<p class="result-id"><strong>ID:</strong> #{{ res.id }}</p>
|
|
|
|
|
|
<p class="result-distance"><strong>相似度距离:</strong> {{ res.distance }}</p>
|
|
|
|
|
|
<p class="result-rerank-score"><strong>重排序分数:</strong> {{ res.rerank_score }}</p>
|
|
|
|
|
|
<p class="result-text">{{ res.entity.text }}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-drawer>
|
2024-07-25 20:30:28 +08:00
|
|
|
|
</a-tag>
|
|
|
|
|
|
</div>
|
2024-07-14 16:42:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2024-07-25 20:30:28 +08:00
|
|
|
|
<div class="bottom">
|
|
|
|
|
|
<div class="input-box">
|
|
|
|
|
|
<a-textarea
|
|
|
|
|
|
class="user-input"
|
|
|
|
|
|
v-model:value="conv.inputText"
|
|
|
|
|
|
@keydown="handleKeyDown"
|
|
|
|
|
|
placeholder="输入问题……"
|
|
|
|
|
|
:auto-size="{ minRows: 1, maxRows: 10 }"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<a-button size="large" @click="sendMessage" :disabled="(!conv.inputText && !isStreaming)">
|
|
|
|
|
|
<template #icon> <SendOutlined v-if="!isStreaming" /> <LoadingOutlined v-else/> </template>
|
|
|
|
|
|
</a-button>
|
|
|
|
|
|
</div>
|
2024-07-31 20:22:05 +08:00
|
|
|
|
<p class="note">即便强如雅典娜也可能会出错,请注意辨别内容的可靠性 模型供应商:{{ configStore.config?.model_provider }}:{{ configStore.config?.model_name }}</p>
|
2024-07-14 16:42:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2024-07-29 01:00:02 +08:00
|
|
|
|
import { reactive, ref, onMounted, toRefs, nextTick, computed, watch } from 'vue'
|
2024-07-17 18:52:20 +08:00
|
|
|
|
import {
|
|
|
|
|
|
SendOutlined,
|
|
|
|
|
|
MenuOutlined,
|
|
|
|
|
|
FormOutlined,
|
|
|
|
|
|
LoadingOutlined,
|
|
|
|
|
|
BookOutlined,
|
|
|
|
|
|
BookFilled,
|
2024-07-18 02:46:58 +08:00
|
|
|
|
CompassFilled,
|
|
|
|
|
|
GoldenFilled,
|
2024-07-21 18:15:04 +08:00
|
|
|
|
SettingOutlined,
|
2024-07-22 00:01:01 +08:00
|
|
|
|
SettingFilled,
|
2024-07-25 20:30:28 +08:00
|
|
|
|
PlusCircleOutlined,
|
2024-07-27 14:32:57 +08:00
|
|
|
|
FolderOutlined,
|
|
|
|
|
|
FolderOpenOutlined,
|
2024-07-17 18:52:20 +08:00
|
|
|
|
} from '@ant-design/icons-vue'
|
2024-07-29 01:00:02 +08:00
|
|
|
|
import { onClickOutside } from '@vueuse/core'
|
2024-07-14 16:42:38 +08:00
|
|
|
|
import { marked } from 'marked';
|
2024-07-27 14:32:57 +08:00
|
|
|
|
import { useConfigStore } from '@/stores/config'
|
2024-07-14 16:42:38 +08:00
|
|
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
conv: Object,
|
|
|
|
|
|
state: Object
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2024-07-28 16:16:52 +08:00
|
|
|
|
const emit = defineEmits(['rename-title', 'newconv']);
|
2024-07-27 14:32:57 +08:00
|
|
|
|
const configStore = useConfigStore()
|
2024-07-14 16:42:38 +08:00
|
|
|
|
|
|
|
|
|
|
const { conv, state } = toRefs(props)
|
2024-07-25 20:30:28 +08:00
|
|
|
|
const chatContainer = ref(null)
|
2024-07-14 16:42:38 +08:00
|
|
|
|
const isStreaming = ref(false)
|
2024-07-17 18:52:20 +08:00
|
|
|
|
const panel = ref(null)
|
2024-07-14 16:42:38 +08:00
|
|
|
|
const examples = ref([
|
|
|
|
|
|
'写一个冒泡排序',
|
2024-07-28 16:16:52 +08:00
|
|
|
|
'肉碱的分子量是多少?直接回答',
|
|
|
|
|
|
'简述大蒜的功效是什么?',
|
2024-07-14 16:42:38 +08:00
|
|
|
|
'A大于B,B小于C,A和C哪个大?',
|
|
|
|
|
|
'今天天气怎么样?'
|
|
|
|
|
|
])
|
|
|
|
|
|
|
2024-07-28 16:16:52 +08:00
|
|
|
|
const opts = reactive({
|
2024-07-29 01:00:02 +08:00
|
|
|
|
showPanel: false,
|
|
|
|
|
|
openDetail: false,
|
|
|
|
|
|
databases: [],
|
2024-07-28 16:16:52 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2024-07-29 01:00:02 +08:00
|
|
|
|
const meta = reactive(JSON.parse(localStorage.getItem('meta')) || {
|
2024-07-18 02:46:58 +08:00
|
|
|
|
use_graph: false,
|
2024-07-25 20:30:28 +08:00
|
|
|
|
use_web: false,
|
2024-07-18 02:46:58 +08:00
|
|
|
|
graph_name: "neo4j",
|
2024-07-29 01:00:02 +08:00
|
|
|
|
rewrite_query: true,
|
|
|
|
|
|
selectedKB: null,
|
2024-07-31 20:22:05 +08:00
|
|
|
|
stream: true,
|
|
|
|
|
|
summary_title: true,
|
2024-08-07 17:42:26 +08:00
|
|
|
|
history_round: 5,
|
2024-07-18 02:46:58 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2024-07-29 01:00:02 +08:00
|
|
|
|
// 更多选项可以在 marked 文档中找到:https://marked.js.org/
|
2024-07-14 16:42:38 +08:00
|
|
|
|
marked.setOptions({
|
|
|
|
|
|
gfm: true,
|
|
|
|
|
|
breaks: true,
|
|
|
|
|
|
tables: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2024-07-29 01:00:02 +08:00
|
|
|
|
const renderMarkdown = (text) => marked(text)
|
|
|
|
|
|
const consoleMsg = (message) => console.log(message)
|
|
|
|
|
|
onClickOutside(panel, () => setTimeout(() => opts.showPanel = false, 30))
|
2024-07-17 18:52:20 +08:00
|
|
|
|
|
2024-07-18 02:46:58 +08:00
|
|
|
|
const handleKeyDown = (e) => {
|
|
|
|
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
|
sendMessage()
|
|
|
|
|
|
} else if (e.key === 'Enter' && e.shiftKey) {
|
|
|
|
|
|
// Insert a newline character at the current cursor position
|
|
|
|
|
|
const textarea = e.target;
|
|
|
|
|
|
const start = textarea.selectionStart;
|
|
|
|
|
|
const end = textarea.selectionEnd;
|
|
|
|
|
|
conv.value.inputText.value =
|
|
|
|
|
|
conv.value.inputText.value.substring(0, start) +
|
|
|
|
|
|
'\n' +
|
|
|
|
|
|
conv.value.inputText.value.substring(end);
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
textarea.setSelectionRange(start + 1, start + 1);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-14 16:42:38 +08:00
|
|
|
|
const renameTitle = () => {
|
2024-07-31 20:22:05 +08:00
|
|
|
|
if (meta.summary_title) {
|
|
|
|
|
|
const prompt = '请用一个很短的句子关于下面的对话内容的主题起一个名字,不要带标点符号:'
|
|
|
|
|
|
const firstUserMessage = conv.value.messages[0].text
|
|
|
|
|
|
const firstAiMessage = conv.value.messages[1].text
|
|
|
|
|
|
const context = `${prompt}\n\n问题: ${firstUserMessage}\n\n回复: ${firstAiMessage},主题是(一句话):`
|
|
|
|
|
|
simpleCall(context).then((data) => {
|
|
|
|
|
|
emit('rename-title', data.response.split(":")[0])
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
emit('rename-title', conv.value.messages[0].text)
|
|
|
|
|
|
}
|
2024-07-14 16:42:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const scrollToBottom = () => {
|
|
|
|
|
|
setTimeout(() => {
|
2024-07-25 20:30:28 +08:00
|
|
|
|
chatContainer.value.scrollTop = chatContainer.value.scrollHeight - chatContainer.value.clientHeight
|
2024-07-14 16:42:38 +08:00
|
|
|
|
}, 10)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-17 18:52:20 +08:00
|
|
|
|
|
2024-07-14 16:42:38 +08:00
|
|
|
|
const generateRandomHash = (length) => {
|
|
|
|
|
|
let chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
|
|
|
|
let hash = '';
|
|
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
|
|
|
|
hash += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
|
|
|
|
}
|
|
|
|
|
|
return hash;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const appendUserMessage = (message) => {
|
|
|
|
|
|
conv.value.messages.push({
|
|
|
|
|
|
id: generateRandomHash(16),
|
|
|
|
|
|
role: 'sent',
|
|
|
|
|
|
text: message
|
|
|
|
|
|
})
|
|
|
|
|
|
scrollToBottom()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const appendAiMessage = (message, refs=null) => {
|
|
|
|
|
|
conv.value.messages.push({
|
|
|
|
|
|
id: generateRandomHash(16),
|
|
|
|
|
|
role: 'received',
|
|
|
|
|
|
text: message,
|
2024-07-28 16:16:52 +08:00
|
|
|
|
refs,
|
|
|
|
|
|
status: "querying"
|
2024-07-14 16:42:38 +08:00
|
|
|
|
})
|
|
|
|
|
|
scrollToBottom()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-28 16:16:52 +08:00
|
|
|
|
const updateMessage = (text, id, refs, status) => {
|
2024-07-14 16:42:38 +08:00
|
|
|
|
const message = conv.value.messages.find((message) => message.id === id)
|
|
|
|
|
|
if (message) {
|
|
|
|
|
|
message.text = text
|
2024-07-25 20:30:28 +08:00
|
|
|
|
message.refs = refs
|
2024-07-28 16:16:52 +08:00
|
|
|
|
message.status = status
|
2024-07-14 16:42:38 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
console.error('Message not found')
|
|
|
|
|
|
}
|
|
|
|
|
|
scrollToBottom()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-28 16:16:52 +08:00
|
|
|
|
const updateStatus = (id, status) => {
|
|
|
|
|
|
const message = conv.value.messages.find((message) => message.id === id)
|
|
|
|
|
|
if (message) {
|
|
|
|
|
|
message.status = status
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error('Message not found')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log("updateStatus", message, message.refs.knowledge_base.results.length > 0)
|
|
|
|
|
|
if (message.refs.knowledge_base.results.length > 0) {
|
|
|
|
|
|
message.groupedResults = message.refs.knowledge_base.results.reduce((acc, result) => {
|
|
|
|
|
|
const { filename } = result.file;
|
|
|
|
|
|
console.log(acc, result, filename)
|
|
|
|
|
|
if (!acc[filename]) {
|
|
|
|
|
|
acc[filename] = []
|
|
|
|
|
|
}
|
|
|
|
|
|
acc[filename].push(result)
|
|
|
|
|
|
return acc;
|
|
|
|
|
|
}, {})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-14 16:42:38 +08:00
|
|
|
|
const simpleCall = (message) => {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
fetch('/api/call', {
|
|
|
|
|
|
method: 'POST',
|
2024-07-29 01:00:02 +08:00
|
|
|
|
body: JSON.stringify({ query: message, }),
|
|
|
|
|
|
headers: { 'Content-Type': 'application/json' }
|
2024-07-14 16:42:38 +08:00
|
|
|
|
})
|
|
|
|
|
|
.then((response) => response.json())
|
|
|
|
|
|
.then((data) => resolve(data))
|
|
|
|
|
|
.catch((error) => reject(error))
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-29 01:00:02 +08:00
|
|
|
|
const loadDatabases = () => {
|
|
|
|
|
|
fetch('/api/database/', { method: "GET", })
|
|
|
|
|
|
.then(response => response.json())
|
|
|
|
|
|
.then(data => {
|
|
|
|
|
|
console.log(data)
|
|
|
|
|
|
opts.databases = data.databases
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-14 16:42:38 +08:00
|
|
|
|
const sendMessage = () => {
|
2024-07-28 16:16:52 +08:00
|
|
|
|
const user_input = conv.value.inputText.trim()
|
|
|
|
|
|
if (user_input) {
|
2024-07-14 16:42:38 +08:00
|
|
|
|
isStreaming.value = true
|
2024-07-28 16:16:52 +08:00
|
|
|
|
appendUserMessage(user_input)
|
2024-07-31 20:22:05 +08:00
|
|
|
|
appendAiMessage("···", null)
|
2024-07-24 18:32:14 +08:00
|
|
|
|
const cur_res_id = conv.value.messages[conv.value.messages.length - 1].id
|
2024-07-14 16:42:38 +08:00
|
|
|
|
conv.value.inputText = ''
|
2024-07-29 01:00:02 +08:00
|
|
|
|
meta.db_name = opts.databases[meta.selectedKB]?.metaname
|
2024-07-14 16:42:38 +08:00
|
|
|
|
fetch('/api/chat', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
query: user_input,
|
|
|
|
|
|
history: conv.value.history,
|
2024-07-18 02:46:58 +08:00
|
|
|
|
meta: meta
|
2024-07-14 16:42:38 +08:00
|
|
|
|
}),
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
}
|
|
|
|
|
|
}).then((response) => {const reader = response.body.getReader()
|
|
|
|
|
|
const decoder = new TextDecoder()
|
|
|
|
|
|
let buffer = ''
|
|
|
|
|
|
// 逐步读取响应文本
|
|
|
|
|
|
const readChunk = () => {
|
|
|
|
|
|
return reader.read().then(({ done, value }) => {
|
|
|
|
|
|
if (done) {
|
|
|
|
|
|
console.log(conv.value)
|
|
|
|
|
|
console.log('Finished')
|
2024-07-28 16:16:52 +08:00
|
|
|
|
updateStatus(cur_res_id, "finished")
|
2024-07-14 16:42:38 +08:00
|
|
|
|
isStreaming.value = false
|
2024-07-29 01:00:02 +08:00
|
|
|
|
if (conv.value.messages.length === 2) { renameTitle() }
|
2024-07-14 16:42:38 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
buffer += decoder.decode(value, { stream: true })
|
|
|
|
|
|
const message = buffer.trim().split('\n').pop()
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const data = JSON.parse(message)
|
2024-07-28 16:16:52 +08:00
|
|
|
|
updateMessage(data.response, cur_res_id, data.refs, "loading")
|
2024-07-14 16:42:38 +08:00
|
|
|
|
conv.value.history = data.history
|
|
|
|
|
|
buffer = ''
|
|
|
|
|
|
} catch (e) {
|
2024-07-18 02:46:58 +08:00
|
|
|
|
// console.log(e)
|
2024-07-14 16:42:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
return readChunk()
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
return readChunk()
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
2024-07-29 01:00:02 +08:00
|
|
|
|
console.log('请输入消息')
|
2024-07-14 16:42:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const autoSend = (message) => {
|
|
|
|
|
|
conv.value.inputText = message
|
|
|
|
|
|
sendMessage()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-29 01:00:02 +08:00
|
|
|
|
// const clearChat = () => {
|
|
|
|
|
|
// conv.value.messages = []
|
|
|
|
|
|
// conv.value.history = []
|
|
|
|
|
|
// }
|
2024-07-14 16:42:38 +08:00
|
|
|
|
|
2024-07-29 01:00:02 +08:00
|
|
|
|
// 从本地存储加载数据
|
2024-07-14 16:42:38 +08:00
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
scrollToBottom()
|
2024-07-29 01:00:02 +08:00
|
|
|
|
loadDatabases()
|
|
|
|
|
|
const storedMeta = localStorage.getItem('meta');
|
|
|
|
|
|
if (storedMeta) {
|
|
|
|
|
|
const parsedMeta = JSON.parse(storedMeta);
|
|
|
|
|
|
Object.assign(meta, parsedMeta);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 监听 meta 对象的变化,并保存到本地存储
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => meta,
|
|
|
|
|
|
(newMeta) => {
|
|
|
|
|
|
localStorage.setItem('meta', JSON.stringify(newMeta));
|
|
|
|
|
|
},
|
|
|
|
|
|
{ deep: true }
|
|
|
|
|
|
);
|
2024-07-14 16:42:38 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="less" scoped>
|
|
|
|
|
|
.chat {
|
2024-07-25 20:30:28 +08:00
|
|
|
|
position: relative;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100vh;
|
2024-07-14 16:42:38 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2024-07-25 20:30:28 +08:00
|
|
|
|
overflow-x: hidden;
|
2024-07-14 16:42:38 +08:00
|
|
|
|
background: white;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
box-sizing: border-box;
|
2024-07-25 20:30:28 +08:00
|
|
|
|
flex: 5 5 200px;
|
|
|
|
|
|
overflow-y: scroll;
|
2024-07-17 18:52:20 +08:00
|
|
|
|
|
2024-07-25 20:30:28 +08:00
|
|
|
|
.header {
|
|
|
|
|
|
user-select: none;
|
|
|
|
|
|
position: sticky;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
z-index: 10;
|
|
|
|
|
|
background-color: white;
|
|
|
|
|
|
height: var(--header-height);
|
2024-07-17 18:52:20 +08:00
|
|
|
|
display: flex;
|
2024-07-25 20:30:28 +08:00
|
|
|
|
justify-content: space-between;
|
2024-07-17 18:52:20 +08:00
|
|
|
|
align-items: center;
|
2024-07-25 20:30:28 +08:00
|
|
|
|
padding: 1rem;
|
2024-07-14 16:42:38 +08:00
|
|
|
|
|
2024-07-25 20:30:28 +08:00
|
|
|
|
.header__left, .header__right {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-07-14 16:42:38 +08:00
|
|
|
|
|
2024-07-25 20:30:28 +08:00
|
|
|
|
.nav-btn {
|
|
|
|
|
|
height: 2.5rem;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
color: var(--c-text-light-1);
|
|
|
|
|
|
cursor: pointer;
|
2024-07-14 16:42:38 +08:00
|
|
|
|
font-size: 1rem;
|
|
|
|
|
|
width: auto;
|
|
|
|
|
|
padding: 0.5rem 1rem;
|
|
|
|
|
|
|
2024-07-25 20:30:28 +08:00
|
|
|
|
.text {
|
|
|
|
|
|
margin-left: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
2024-07-26 14:13:05 +08:00
|
|
|
|
background-color: var(--main-light-3);
|
2024-07-25 20:30:28 +08:00
|
|
|
|
}
|
2024-07-14 16:42:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-25 20:30:28 +08:00
|
|
|
|
}
|
2024-07-18 02:46:58 +08:00
|
|
|
|
.metas {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-17 18:52:20 +08:00
|
|
|
|
.my-panal {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 100%;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
margin-top: 5px;
|
|
|
|
|
|
background-color: white;
|
|
|
|
|
|
border: 1px solid #ccc;
|
|
|
|
|
|
box-shadow: 0px 0px 10px 1px rgba(0, 0, 0, 0.05);
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
padding: 12px;
|
2024-07-31 20:22:05 +08:00
|
|
|
|
z-index: 11;
|
2024-07-18 02:46:58 +08:00
|
|
|
|
width: 250px;
|
|
|
|
|
|
|
|
|
|
|
|
.flex-center {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: background-color 0.3s;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
2024-07-26 14:13:05 +08:00
|
|
|
|
background-color: var(--main-light-3);
|
2024-07-18 02:46:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-07-17 18:52:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-14 16:42:38 +08:00
|
|
|
|
|
2024-07-25 20:30:28 +08:00
|
|
|
|
.chat-examples {
|
2024-07-14 16:42:38 +08:00
|
|
|
|
padding: 0 50px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 20%;
|
|
|
|
|
|
width: 100%;
|
2024-07-31 20:22:05 +08:00
|
|
|
|
z-index: 9;
|
2024-07-14 16:42:38 +08:00
|
|
|
|
|
2024-07-25 20:30:28 +08:00
|
|
|
|
h1 {
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
2024-07-14 16:42:38 +08:00
|
|
|
|
|
2024-07-29 01:00:02 +08:00
|
|
|
|
.opts {
|
2024-07-25 20:30:28 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 10px;
|
2024-07-14 16:42:38 +08:00
|
|
|
|
|
2024-07-25 20:30:28 +08:00
|
|
|
|
.opt__button {
|
|
|
|
|
|
background-color: white;
|
|
|
|
|
|
color: #222;
|
|
|
|
|
|
padding: 6px 1rem;
|
|
|
|
|
|
border-radius: 1rem;
|
|
|
|
|
|
cursor: pointer;
|
2024-07-26 14:13:05 +08:00
|
|
|
|
// border: 2px solid var(--main-light-4);
|
2024-07-25 20:30:28 +08:00
|
|
|
|
transition: background-color 0.3s;
|
2024-07-26 14:13:05 +08:00
|
|
|
|
box-shadow: 0px 0px 10px 4px var(--main-light-4);
|
2024-07-14 16:42:38 +08:00
|
|
|
|
|
2024-07-25 20:30:28 +08:00
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
background-color: #fcfcfc;
|
|
|
|
|
|
box-shadow: 0px 0px 10px 1px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-07-14 16:42:38 +08:00
|
|
|
|
}
|
2024-07-25 20:30:28 +08:00
|
|
|
|
|
2024-07-14 16:42:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chat-box {
|
2024-07-25 20:30:28 +08:00
|
|
|
|
width: 100%;
|
|
|
|
|
|
max-width: 1100px;
|
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
|
flex-grow: 1;
|
2024-07-14 16:42:38 +08:00
|
|
|
|
padding: 1rem;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
|
2024-07-25 20:30:28 +08:00
|
|
|
|
.message-box {
|
|
|
|
|
|
max-width: 95%;
|
|
|
|
|
|
display: inline-block;
|
|
|
|
|
|
border-radius: 0.8rem;
|
|
|
|
|
|
margin: 0.8rem 0;
|
|
|
|
|
|
padding: 1rem;
|
|
|
|
|
|
user-select: text;
|
|
|
|
|
|
word-break: break-word;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-variation-settings: 'wght' 400, 'opsz' 10.5;
|
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
color: #0D0D0D;
|
|
|
|
|
|
/* box-shadow: 0px 0.3px 0.9px rgba(0, 0, 0, 0.12), 0px 1.6px 3.6px rgba(0, 0, 0, 0.16); */
|
|
|
|
|
|
/* animation: slideInUp 0.1s ease-in; */
|
|
|
|
|
|
}
|
2024-07-14 16:42:38 +08:00
|
|
|
|
|
2024-07-25 20:30:28 +08:00
|
|
|
|
.message-box.sent {
|
|
|
|
|
|
background-color: #efefef;
|
|
|
|
|
|
line-height: 24px;
|
2024-07-26 14:13:05 +08:00
|
|
|
|
background: var(--main-light-3);
|
2024-07-25 20:30:28 +08:00
|
|
|
|
align-self: flex-end;
|
|
|
|
|
|
}
|
2024-07-14 16:42:38 +08:00
|
|
|
|
|
2024-07-25 20:30:28 +08:00
|
|
|
|
.message-box.received {
|
|
|
|
|
|
color: initial;
|
|
|
|
|
|
width: fit-content;
|
|
|
|
|
|
padding-top: 16px;
|
|
|
|
|
|
background-color: #F5F7F8;
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
word-wrap: break-word;
|
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
|
padding-bottom: 0;
|
|
|
|
|
|
text-align: justify;
|
|
|
|
|
|
}
|
2024-07-14 16:42:38 +08:00
|
|
|
|
|
2024-07-25 20:30:28 +08:00
|
|
|
|
p.message-text {
|
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
|
word-wrap: break-word;
|
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
|
}
|
2024-07-14 16:42:38 +08:00
|
|
|
|
|
2024-07-25 20:30:28 +08:00
|
|
|
|
p.message-md {
|
|
|
|
|
|
word-wrap: break-word;
|
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
|
}
|
2024-07-14 16:42:38 +08:00
|
|
|
|
|
2024-07-25 20:30:28 +08:00
|
|
|
|
.refs {
|
|
|
|
|
|
margin-bottom: 20px;
|
2024-07-28 16:16:52 +08:00
|
|
|
|
|
|
|
|
|
|
.filetag:hover {
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.retrieval-detail {
|
|
|
|
|
|
.fileinfo {
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
background: var(--main-light-3);
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
p {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.result-item {
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
border: 1px solid #e8e8e8;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
background: var(--main-light-4);
|
|
|
|
|
|
|
|
|
|
|
|
.result-id,
|
|
|
|
|
|
.result-distance,
|
|
|
|
|
|
.result-rerank-score,
|
|
|
|
|
|
.result-text-label,
|
|
|
|
|
|
.result-text {
|
|
|
|
|
|
margin: 5px 0;
|
|
|
|
|
|
}
|
2024-07-25 20:30:28 +08:00
|
|
|
|
}
|
2024-07-14 16:42:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-25 20:30:28 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.bottom {
|
|
|
|
|
|
position: sticky;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
width: 100%;
|
2024-07-14 16:42:38 +08:00
|
|
|
|
margin: 0 auto;
|
2024-07-25 20:30:28 +08:00
|
|
|
|
padding: 0.5rem 2rem;
|
|
|
|
|
|
background: white;
|
2024-07-14 16:42:38 +08:00
|
|
|
|
|
|
|
|
|
|
|
2024-07-25 20:30:28 +08:00
|
|
|
|
.input-box {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
max-width: 1100px;
|
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
|
align-items: flex-end;
|
|
|
|
|
|
background-color: #F5F7F8;
|
|
|
|
|
|
border-radius: 2rem;
|
|
|
|
|
|
height: auto;
|
|
|
|
|
|
padding: 0.5rem;
|
|
|
|
|
|
|
|
|
|
|
|
.user-input {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
padding: 0.5rem 1rem;
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
font-size: 1.2rem;
|
|
|
|
|
|
margin: 0 0.6rem;
|
|
|
|
|
|
color: #111111;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-variation-settings: 'wght' 400, 'opsz' 10.5;
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
|
|
|
|
|
|
&:focus {
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&:active {
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-07-17 20:00:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-25 20:30:28 +08:00
|
|
|
|
.note {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
font-size: small;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
padding: 0rem;
|
|
|
|
|
|
color: #ccc;
|
|
|
|
|
|
margin: 4px 0;
|
2024-07-14 16:42:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-25 20:30:28 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.ant-dropdown-link {
|
|
|
|
|
|
color: var(--c-text-light-1);
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-14 16:42:38 +08:00
|
|
|
|
.ant-btn-icon-only {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
height: 2.5rem;
|
|
|
|
|
|
background-color: black;
|
|
|
|
|
|
border-radius: 3rem;
|
|
|
|
|
|
color: white;
|
2024-07-25 20:30:28 +08:00
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
}
|
2024-07-14 16:42:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
button:disabled {
|
|
|
|
|
|
background: #D7D7D7;
|
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-31 20:22:05 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.chat::-webkit-scrollbar {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
width: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chat::-webkit-scrollbar-track {
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chat::-webkit-scrollbar-thumb {
|
|
|
|
|
|
background: var(--c-text-dark-2);
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chat::-webkit-scrollbar-thumb:hover {
|
|
|
|
|
|
background: rgb(100, 100, 100);
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chat::-webkit-scrollbar-thumb:active {
|
|
|
|
|
|
background: rgb(68, 68, 68);
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-07 17:25:06 +08:00
|
|
|
|
.slide-out-left{-webkit-animation:slide-out-left .5s cubic-bezier(.55,.085,.68,.53) both;animation:slide-out-left .5s cubic-bezier(.55,.085,.68,.53) both}
|
|
|
|
|
|
.swing-in-top-fwd {-webkit-animation: swing-in-top-fwd 0.5s cubic-bezier(0.175, 0.885, 0.320, 1.275) both;animation: swing-in-top-fwd 0.5s cubic-bezier(0.175, 0.885, 0.320, 1.275) both;}
|
|
|
|
|
|
@-webkit-keyframes swing-in-top-fwd{0%{-webkit-transform:rotateX(-100deg);transform:rotateX(-100deg);-webkit-transform-origin:top;transform-origin:top;opacity:0}100%{-webkit-transform:rotateX(0deg);transform:rotateX(0deg);-webkit-transform-origin:top;transform-origin:top;opacity:1}}@keyframes swing-in-top-fwd{0%{-webkit-transform:rotateX(-100deg);transform:rotateX(-100deg);-webkit-transform-origin:top;transform-origin:top;opacity:0}100%{-webkit-transform:rotateX(0deg);transform:rotateX(0deg);-webkit-transform-origin:top;transform-origin:top;opacity:1}}
|
|
|
|
|
|
@-webkit-keyframes slide-out-left{0%{-webkit-transform:translateX(0);transform:translateX(0);opacity:1}100%{-webkit-transform:translateX(-1000px);transform:translateX(-1000px);opacity:0}}@keyframes slide-out-left{0%{-webkit-transform:translateX(0);transform:translateX(0);opacity:1}100%{-webkit-transform:translateX(-1000px);transform:translateX(-1000px);opacity:0}}
|
2024-07-31 20:22:05 +08:00
|
|
|
|
|
2024-07-25 20:30:28 +08:00
|
|
|
|
@media (max-width: 520px) {
|
|
|
|
|
|
.chat {
|
|
|
|
|
|
height: calc(100vh - 60px);
|
2024-07-14 16:42:38 +08:00
|
|
|
|
}
|
2024-07-25 20:30:28 +08:00
|
|
|
|
|
|
|
|
|
|
.chat-container .chat .header {
|
2024-07-26 14:13:05 +08:00
|
|
|
|
background: var(--main-light-4);
|
2024-07-25 20:30:28 +08:00
|
|
|
|
.header__left, .header__right {
|
|
|
|
|
|
gap: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.nav-btn {
|
|
|
|
|
|
font-size: 1.5rem;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
color: black;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.text {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-07-14 16:42:38 +08:00
|
|
|
|
}
|
2024-07-25 20:30:28 +08:00
|
|
|
|
|
2024-07-27 14:32:57 +08:00
|
|
|
|
.bottom {
|
|
|
|
|
|
padding: 0.5rem 0.5rem;
|
|
|
|
|
|
|
|
|
|
|
|
.input-box {
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
padding: 0.5rem;
|
|
|
|
|
|
|
|
|
|
|
|
textarea.user-input {
|
|
|
|
|
|
padding: 0.5rem 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
.note {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-07-14 16:42:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
</style>
|