v3.8.1发布,上传前端代码

This commit is contained in:
JEECG
2025-06-25 16:04:02 +08:00
parent 3d414aaec8
commit 0148f45979
120 changed files with 4783 additions and 486 deletions

View File

@ -1,9 +1,9 @@
<template>
<div ref="chatContainerRef" class="chat-container" :style="chatContainerStyle">
<template v-if="dataSource">
<div class="leftArea" :class="[expand ? 'expand' : 'shrink']">
<div v-if="isMultiSession" class="leftArea" :class="[expand ? 'expand' : 'shrink']">
<div class="content">
<slide v-if="uuid" :dataSource="dataSource" @save="handleSave" :prologue="prologue" :appData="appData" @click="handleChatClick"></slide>
<slide :source="source" v-if="uuid" :dataSource="dataSource" @save="handleSave" :prologue="prologue" :appData="appData" @click="handleChatClick"></slide>
</div>
<div class="toggle-btn" @click="handleToggle">
<span class="icon">
@ -30,6 +30,7 @@
@reload-message-title="reloadMessageTitle"
:chatTitle="chatTitle"
:quickCommandData="quickCommandData"
:showAdvertising = "showAdvertising"
></chat>
</div>
</template>
@ -46,6 +47,7 @@
import { JEECG_CHAT_KEY } from '/@/enums/cacheEnum';
import { defHttp } from '/@/utils/http/axios';
import { useRouter } from 'vue-router';
import { useAppInject } from "@/hooks/web/useAppInject";
const router = useRouter();
const userId = useUserStore().getUserInfo?.id;
@ -77,6 +79,8 @@
const prologue = ref<string>('');
//快捷指令
const quickCommandData = ref<any>([]);
//是否显示广告位
const showAdvertising = ref<boolean>(false);
const priming = () => {
dataSource.value = {
@ -142,6 +146,13 @@
);
};
//是否为多会话模式
const isMultiSession = ref<boolean>(true);
//是否为手机
const { getIsMobile } = useAppInject();
//来源
const source = ref<string>('');
/**
* 初始化聊天信息
* @param appId
@ -184,6 +195,13 @@
{ name: 'JEECG有哪些优势', descr: "JEECG有哪些优势" },
{ name: 'JEECG可以做哪些事情', descr: "JEECG可以做哪些事情" },];
}
let query: any = router.currentRoute.value.query;
source.value = query.source;
if(query.source){
showAdvertising.value = query.source === 'chatJs';
}else{
showAdvertising.value = false;
}
});
onUnmounted(() => {
@ -203,7 +221,7 @@
await defHttp
.get(
{
url: '/airag/app/queryById',
url: '/airag/chat/init',
params: { id: appId },
},
{ isTransformResponse: false }
@ -220,6 +238,23 @@
if (res.result && res.result.presetQuestion) {
presetQuestion.value = res.result.presetQuestion;
}
if (res.result && res.result.metadata) {
let metadata = JSON.parse(res.result.metadata);
//判斷是否为手机模式
if(!getIsMobile.value){
//是否为多会话模式
if((metadata.multiSession && metadata.multiSession === '1') || !metadata.multiSession) {
isMultiSession.value = true;
} else {
isMultiSession.value = false;
expand.value = false;
}
}
}
if(getIsMobile.value){
isMultiSession.value = false;
expand.value = false;
}
} else {
appData.value = {};
}
@ -281,7 +316,7 @@
.chat-container {
height: 100%;
width: 100%;
position: relative;
position: absolute;
background: white;
display: flex;
overflow: hidden;
@ -339,7 +374,7 @@
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);
box-shadow: 0 2px 4px 0px #e7e9ef;
transform: translateX(50%) translateY(-50%);
z-index: 1;
}

View File

@ -17,19 +17,19 @@
import AiChat from './AiChat.vue';
import { useRouter } from 'vue-router';
//aiChat<EFBFBD><EFBFBD>ref
//aiChatref
const aiChatRef = ref();
//Ӧ<EFBFBD><EFBFBD>id
//应用id
const appId = ref<string>('');
//<EFBFBD>Ƿ<EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
//是否显示聊天
const showChat = ref<any>(false);
const router = useRouter();
//<EFBFBD>ж<EFBFBD><EFBFBD>Ƿ<EFBFBD>Ϊ<EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD>
//判断是否为初始化
const isInit = ref<boolean>(false);
/**
* chatͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>¼<EFBFBD>
* chat图标点击事件
*/
function chatClick() {
showChat.value = !showChat.value;
@ -67,7 +67,7 @@
display: flex;
align-items: center;
justify-content: center;
box-shadow: rgba(0, 0, 0, 0.2) 0 4px 8px 0;
box-shadow: #cccccc 0 4px 8px 0;
}
.footer-close-icon {
color: #0a3069;

View File

@ -3,6 +3,13 @@
<div class="content">
<div class="header-title" v-if="type === 'view' && headerTitle">
{{headerTitle}}
<div v-if="showAdvertising" class="header-advertisint">
AI客服由
<a style="color: #4183c4;margin-left: 2px;margin-right: 2px" href="https://www.qiaoqiaoyun.com/aiCustomerService" target="_blank">
敲敲云
</a>
提供
</div>
</div>
<div class="main">
<div id="scrollRef" ref="scrollRef" class="scrollArea">
@ -19,6 +26,8 @@
:appData="appData"
:presetQuestion="item.presetQuestion"
:images = "item.images"
:retrievalText="item.retrievalText"
:referenceKnowledge="item.referenceKnowledge"
@send="handleOutQuestion"
></chatMessage>
</div>
@ -86,6 +95,7 @@
autofocus
:readonly="loading"
style="border-color: #ffffff !important;box-shadow:none"
@paste="paste"
>
</a-textarea>
<a-button v-if="loading" type="primary" danger @click="handleStopChat" class="stopBtn">
@ -122,7 +132,7 @@
>
<a-tooltip title="图片上传支持jpg/jpeg/png">
<a-button class="sendBtn" type="text">
<Icon icon="ant-design:picture-outlined" style="color: rgba(15,21,40,0.8)"></Icon>
<Icon icon="ant-design:picture-outlined" style="color: #3d4353"></Icon>
</a-button>
</a-tooltip>
</a-upload>
@ -178,21 +188,22 @@
import { defHttp } from '@/utils/http/axios';
import { cloneDeep } from "lodash-es";
import {getFileAccessHttpUrl, getHeaders} from "@/utils/common/compUtils";
import { uploadUrl } from '/@/api/common/api';
import { createImgPreview } from "@/components/Preview";
import { useAppInject } from "@/hooks/web/useAppInject";
import { useGlobSetting } from "@/hooks/setting";
message.config({
prefixCls: 'ai-chat-message',
});
const props = defineProps(['uuid', 'prologue', 'formState', 'url', 'type','historyData','chatTitle','presetQuestion','quickCommandData']);
const props = defineProps(['uuid', 'prologue', 'formState', 'url', 'type','historyData','chatTitle','presetQuestion','quickCommandData','showAdvertising']);
const emit = defineEmits(['save','reload-message-title']);
const { scrollRef, scrollToBottom } = useScroll();
const prompt = ref<string>('');
const loading = ref<boolean>(false);
const inputRef = ref<Ref | null>(null);
const headerTitle = ref<string>(props.chatTitle);
//聊天数据
const chatData = ref<any>([]);
//应用数据
@ -202,15 +213,24 @@
const topicId = ref<string>('');
//请求id
const requestId = ref<string>('');
const { getIsMobile } = useAppInject();
const conversationList = computed(() => chatData.value.filter((item) => item.inversion != 'user' && !!item.conversationOptions));
const placeholder = computed(() => {
return '来说点什么吧...Shift + Enter = 换行)';
if(getIsMobile.value){
return '来说点什么吧...'
} else {
return '来说点什么吧...Shift + Enter = 换行)';
}
});
//token
const headers = getHeaders();
//文本域点击事件
const textareaActive = ref<boolean>(false);
const globSetting = useGlobSetting();
const baseUploadUrl = globSetting.uploadUrl;
const uploadUrl = ref<string>(`${baseUploadUrl}/airag/chat/upload`);
function handleEnter(event: KeyboardEvent) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
@ -269,6 +289,7 @@
error: false,
conversationOptions: null,
requestOptions: { prompt: message, options: { ...options } },
referenceKnowledge: [],
});
scrollToBottom();
@ -318,7 +339,7 @@
dateTime: new Date().toLocaleString(),
content: data,
inversion: 'ai',
error: false,
error: true,
loading: true,
conversationOptions: null,
requestOptions: null,
@ -370,19 +391,27 @@
handleStop();
const knowList = ref<Recordable[]>([])
/**
* 停止消息
*/
function handleStopChat() {
if(requestId.value){
//调用后端接口停止响应
defHttp.get({
url: '/airag/chat/stop/' + requestId.value,
},{ isTransformResponse: false });
//update-begin---author:wangshuai---date:2025-06-03---for:【issues/8338】AI应用聊天回复stop无效仍会继续输出回复---
const currentRequestId = requestId.value
if(currentRequestId){
try{
//调用后端接口停止响应
defHttp.get({
url: '/airag/chat/stop/' + currentRequestId,
},{ isTransformResponse: false });
} finally {
handleStop();
}
//update-end---author:wangshuai---date:2025-06-03---for:【issues/8338】AI应用聊天回复stop无效仍会继续输出回复---
}
handleStop();
}
/**
* 读取文本
* @param message
@ -409,15 +438,16 @@
};
if(headerTitle.value == '新建聊天'){
headerTitle.value = message.length>5?message.substring(0,5):message
headerTitle.value = message.length>10?truncateString(message,10):message
}
emit("reload-message-title",message.length>5?message.substring(0,5):message)
emit("reload-message-title",message.length>10?truncateString(message,10):message)
}
uploadUrlList.value = [];
fileInfoList.value = [];
knowList.value = [];
const readableStream = await defHttp.post(
{
url: props.url,
@ -430,9 +460,18 @@
isTransformResponse: false,
}
).catch((e)=>{
updateChatFail(uuid, chatData.value.length - 1, "服务器错误,请稍后重试!");
handleStop();
return;
//update-begin---author:wangshuai---date:2025-04-28---for:【QQYUN-12297】【AI】聊天超时以后提示---
if(e.code === 'ETIMEDOUT'){
updateChatFail(uuid, chatData.value.length - 1, "当前用户较多,排队中,请稍候再次重试!");
handleStop();
return;
}else{
updateChatFail(uuid, chatData.value.length - 1, "服务器错误,请稍后重试!");
handleStop();
return;
}
console.error(e)
//update-end---author:wangshuai---date:2025-04-28---for:【QQYUN-12297】【AI】聊天超时以后提示---
});
const reader = readableStream.getReader();
const decoder = new TextDecoder('UTF-8');
@ -448,9 +487,9 @@
let result = decoder.decode(value, { stream: true });
result = buffer + result;
const lines = result.split('\n\n');
for (const line of lines) {
for (let line of lines) {
if (line.startsWith('data:')) {
const content = line.replace('data:', '').trim();
let content = line.replace('data:', '').trim();
if(!content){
continue;
}
@ -461,6 +500,9 @@
buffer = "";
try {
//update-begin---author:wangshuai---date:2025-03-13---for:【QQYUN-11572】发布到线上不能实时动态内容不能加载出来得刷新才能看到全部回答---
if(content.indexOf(":::card:::") !== -1){
content = content.replace(/\s+/g, '');
}
let parse = JSON.parse(content);
await renderText(parse,conversationId,text,options).then((res)=>{
text = res.returnText;
@ -482,6 +524,9 @@
buffer = "";
//update-begin---author:wangshuai---date:2025-03-13---for:【QQYUN-11572】发布到线上不能实时动态内容不能加载出来得刷新才能看到全部回答---
try {
if(line.indexOf(":::card:::") !== -1){
line = line.replace(/\s+/g, '');
}
let parse = JSON.parse(line);
await renderText(parse, conversationId, text, options).then((res) => {
text = res.returnText;
@ -523,24 +568,42 @@
async function renderText(item,conversationId,text,options) {
let returnText = "";
if (item.event == 'MESSAGE') {
text = text + item.data.message;
returnText = text;
let message = item.data.message;
let messageText = "";
//update-begin---author:wangshuai---date:2025-04-24---for:应该先判断是否包含card---
if(message && message.indexOf("::card::") !== -1){
messageText = message;
} else {
text = text + item.data.message;
messageText = text;
returnText = text;
}
//update-end---author:wangshuai---date:2025-04-24---for:应该先判断是否包含card---
// 从消息中获取 requestId
if (item.requestId) {
requestId.value = item.requestId;
}
//更新聊天信息
updateChat(uuid.value, chatData.value.length - 1, {
dateTime: new Date().toLocaleString(),
content: text,
content: messageText,
inversion: 'ai',
error: false,
loading: true,
conversationOptions: { conversationId: conversationId, parentMessageId: topicId.value },
requestOptions: { prompt: message, options: { ...options } },
referenceKnowledge: knowList.value,
});
}
if(item.event == 'INIT_REQUEST_ID'){
if (item.requestId) {
requestId.value = item.requestId;
}
}
if (item.event == 'MESSAGE_END') {
topicId.value = item.topicId;
conversationId = item.conversationId;
uuid.value = item.conversationId;
requestId.value = item.requestId;
handleStop();
}
if (item.event == 'FLOW_FINISHED') {
@ -565,41 +628,60 @@
//update-begin---author:wangshuai---date:2025-03-21---for:【QQYUN-11495】【AI】实时展示当前思考进度---
if(item.event === "NODE_STARTED"){
let aiText = "";
if(item.data.type === 'llm'){
aiText = "正在构建响应内容";
if(!item.data || item.data.type !== 'end'){
let aiText = "";
if(item.data.type === 'llm'){
aiText = "正在构建响应内容";
}
if(item.data.type === 'knowledge'){
aiText = "正在对知识库进行深度检索";
}
if(item.data.type === 'classifier'){
aiText = "正在分类";
}
if(item.data.type === 'code'){
aiText = "正在实施代码运行操作";
}
if(item.data.type === 'subflow'){
aiText = "正在运行子流程";
}
if(item.data.type === 'enhanceJava'){
aiText = "正在执行java增强";
}
if(item.data.type === 'http'){
aiText = "正在发送http请求";
}
if(!text){
//更新聊天信息
updateChat(uuid.value, chatData.value.length - 1, {
dateTime: new Date().toLocaleString(),
retrievalText: aiText,
text:"",
inversion: 'ai',
error: false,
loading: true,
conversationOptions: null,
requestOptions: { prompt: message, options: { ...options } },
referenceKnowledge: knowList.value,
});
}
}
if(item.data.type === 'knowledge'){
aiText = "正在对知识库进行深度检索";
}
if(item.data.type === 'classifier'){
aiText = "正在分类";
}
if(item.data.type === 'code'){
aiText = "正在实施代码运行操作";
}
if(item.data.type === 'subflow'){
aiText = "正在运行子流程";
}
if(item.data.type === 'enhanceJava'){
aiText = "正在执行java增强";
}
if(item.data.type === 'http'){
aiText = "正在发送http请求";
}
//更新聊天信息
updateChat(uuid.value, chatData.value.length - 1, {
dateTime: new Date().toLocaleString(),
content: aiText,
inversion: 'ai',
error: false,
loading: true,
conversationOptions: null,
requestOptions: { prompt: message, options: { ...options } },
});
}
//update-end---author:wangshuai---date:2025-03-21---for:【QQYUN-11495】【AI】实时展示当前思考进度---
else if (item.event === 'NODE_FINISHED') {
if(!item.data || item.data.type !== 'end'){
if(item.data.type === 'knowledge'){
const id = item.data.id;
const data = item.data.outputs[id + ".data"]
knowList.value.push(data)
//更新聊天信息
updateChatSome(uuid.value, chatData.value.length - 1, {referenceKnowledge: knowList.value})
}
}
}
if(!returnText){
returnText = text;
}
return { returnText, conversationId };
}
@ -607,7 +689,7 @@
const uploadUrlList = ref<any>([]);
//文件集合
const fileInfoList = ref<any>([]);
/**
* 文件上传回调事件
* @param info
@ -615,8 +697,9 @@
function handleChange(info) {
let { fileList, file } = info;
fileInfoList.value = fileList;
if (file.status === 'error') {
if (file.status === 'error' || (file.response && file.response.code == 500)) {
message.error(file.response?.message || `${file.name} 上传失败,请查看服务端日志`);
return;
}
if (file.status === 'done') {
uploadUrlList.value.push(file.response.message);
@ -625,7 +708,7 @@
/**
* 获取图片地址
*
*
* @param url
*/
function getImage(url) {
@ -643,6 +726,10 @@
return false;
}
}
if(uploadUrlList.value && uploadUrlList.value.length > 2){
message.warning("最多只能上传三张!");
return false;
}
return true;
}
@ -665,7 +752,85 @@
let imageList = [getImage(url)];
createImgPreview({ imageList: imageList, defaultWidth: 700, rememberState: true, onImgLoad });
}
/**
* 截取字符串
* @param str
* @param maxLength
*/
function truncateString(str, maxLength) {
if (str.length <= maxLength){
return str;
}
let chineseCount = 0;
let englishCount = 0;
let digitCount = 0;
let result = '';
for (let i = 0; i < str.length; i++) {
const char = str[i];
if (/[\u4e00-\u9fa5]/.test(char)) { // 判断是否为汉字
chineseCount++;
} else if (/[a-zA-Z]/.test(char)) { // 判断是否为英文字母
englishCount++;
} else if (/\d/.test(char)) { // 判断是否为数字
digitCount++;
}
if (chineseCount + englishCount / 2 + digitCount / 2 > maxLength) {
break;
}
result += char;
}
return result;
}
/**
* 粘贴事件
* @param event
*/
function paste(event) {
if(uploadUrlList.value && uploadUrlList.value.length > 2){
message.warning("最多只能上传三张!");
return;
}
const items = (event.clipboardData || window.clipboardData).items;
if (!items || items.length === 0){
//说明浏览器不支持复制图片
message.error('当前浏览器不支持本地打开图片!');
return;
}
let image = null;
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf('image') !== -1) {
image = items[i].getAsFile();
handleUploadImage(image);
break;
}
}
}
/**
* 粘贴图片
* @param image
*/
async function handleUploadImage(image) {
const isReturn = (fileInfo) => {
try {
if (fileInfo.code === 0) {
let { message } = fileInfo;
uploadUrlList.value.push(message);
fileInfoList.value.push(image);
} else if (fileInfo.code === 500 || fileInfo.code === 510) {
message.error(fileInfo.message || `${image.name} 导入失败`);
}
} catch (error) {
console.log('导入的数据异常', error);
message.error(`${image.name} 导入失败`);
}
};
await defHttp.uploadFile({ url: "/airag/chat/upload" }, { file: image }, { success: isReturn });
}
//监听开场白
watch(
() => props.prologue,
@ -676,8 +841,8 @@
}
} catch (e) {}
}
);
);
//监听开场白预制问题
watch(
() => props.presetQuestion,
@ -698,7 +863,7 @@
},
{ deep: true, immediate: true }
);
//监听历史信息
watch(
() => props.historyData,
@ -840,6 +1005,14 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: flex;
justify-content: space-between;
height: 30px;
.header-advertisint{
display:flex;
margin-right: 20px;
font-size: 12px;
}
}
.chat-textarea{
display: flex;
@ -850,7 +1023,7 @@
border-width: 1px;
flex-direction: column;
transition: width 0.3s;
border-color: rgba(68,83,130,0.2);
border-color: #d2d7e5;
.textarea-top{
border-bottom: 1px solid #f0f0f5;
padding: 12px 28px;
@ -881,10 +1054,10 @@
}
}
.chat-textarea:hover{
border-color: rgba(59,130,246,0.5)
border-color: #9dc1fb;
}
.textarea-active{
border-color: rgba(59,130,246,0.5) !important;
border-color: #98bdfa !important;
}
:deep(.ant-divider-vertical){
margin: 0 2px;
@ -899,7 +1072,7 @@
display: none;
align-items: center;
justify-content: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
box-shadow: 0 2px 4px #e6e6e6;
margin-left: 44px;
margin-top: -4px;
}
@ -908,6 +1081,24 @@
display: flex;
}
}
@media (max-width: 600px) {
//手机下的样式 平板不需要调整
.footer{
padding: 0;
.bottomArea{
.delBtn{
margin-right: 0;
}
}
}
.chatWrap{
padding: 10px 10px 10px 0;
}
.main .chatContentArea{
padding: 10px 0 0 10px;
}
}
</style>
<style lang="less">
.ai-chat-modal{

View File

@ -1,8 +1,8 @@
<template>
<div class="chat" :class="[inversion === 'user' ? 'self' : 'chatgpt']">
<div class="chat" :class="[inversion === 'user' ? 'self' : 'chatgpt']" v-if="getText || (props.presetQuestion && props.presetQuestion.length>0)">
<div class="avatar">
<img v-if="inversion === 'user'" :src="avatar()" />
<img v-else :src="getAiImg()">
<img v-else :src="getAiImg()" />
</div>
<div class="content">
<p class="date">
@ -14,8 +14,24 @@
<img :src="getImageUrl(item)"/>
</div>
</div>
<div class="msgArea">
<chatText :text="text" :inversion="inversion" :error="error" :loading="loading"></chatText>
<div v-if="inversion === 'ai' && retrievalText && loading" class="retrieval">
{{retrievalText}}
</div>
<div v-if="inversion === 'ai' && isCard" class="card">
<a-row>
<a-col :xl="6" :lg="8" :md="10" :sm="24" style="flex:1" v-for="item in getCardList()">
<a-card class="ai-card" @click="aiCardHandleClick(item.linkUrl)">
<div class="ai-card-title">{{item.productName}}</div>
<div class="ai-card-img">
<img :src="item.productImage">
</div>
<span class="ai-card-desc">{{item.descr}}</span>
</a-card>
</a-col>
</a-row>
</div>
<div class="msgArea" v-if="!isCard">
<chatText :text="text" :inversion="inversion" :error="error" :loading="loading" :referenceKnowledge="referenceKnowledge"></chatText>
</div>
<div v-if="presetQuestion" v-for="item in presetQuestion" class="question" @click="presetQuestionClick(item.descr)">
<span>{{item.descr}}</span>
@ -26,13 +42,31 @@
<script setup lang="ts">
import chatText from './chatText.vue';
import defaultAvatar from '/@/assets/images/ai/avatar.jpg';
import defaultAvatar from "@/assets/images/ai/avatar.jpg";
import { useUserStore } from '/@/store/modules/user';
import defaultImg from '../img/ailogo.png';
const props = defineProps(['dateTime', 'text', 'inversion', 'error', 'loading','appData','presetQuestion','images']);
const props = defineProps(['dateTime', 'text', 'inversion', 'error', 'loading','appData','presetQuestion','images','retrievalText', 'referenceKnowledge']);
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
import { createImgPreview } from "@/components/Preview";
import { computed } from "vue";
const getText = computed(()=>{
let text = props.text || props.retrievalText;
if(text){
text = text.trim();
}
return text;
})
const isCard = computed(() => {
let text = props.text;
if (text && text.indexOf('::card::') != -1) {
return true;
}
return false;
});
const { userInfo } = useUserStore();
const avatar = () => {
return getFileAccessHttpUrl(userInfo?.avatar) || defaultAvatar;
@ -44,7 +78,7 @@
/**
* 预设问题点击事件
*
*
*/
function presetQuestionClick(descr) {
emit("send",descr)
@ -52,7 +86,7 @@
/**
* 获取图片
*
*
* @param item
*/
function getImageUrl(item) {
@ -77,7 +111,28 @@
let imageList = [getImageUrl(url)];
createImgPreview({ imageList: imageList, defaultWidth: 700, rememberState: true, onImgLoad });
}
/**
* 获取卡片列表
*/
function getCardList() {
let text = props.text;
let card = text.replace('::card::', '').replace(/\s+/g, '');
try {
return JSON.parse(card);
} catch (e) {
console.log(e)
return '';
}
}
/**
* ai卡片点击事件
* @param url
*/
function aiCardHandleClick(url){
window.open(url,'_blank');
}
</script>
<style lang="less" scoped>
@ -112,6 +167,7 @@
}
}
.content {
width: 90%;
.date {
color: #b4bbc4;
font-size: 0.75rem;
@ -134,14 +190,15 @@
line-height: 1.25rem;
cursor: pointer;
border: 1px solid #f0f0f0;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
box-shadow: 0 2px 4px #e6e6e6;
}
.images{
margin-bottom: 10px;
flex-wrap: wrap;
display: flex;
gap: 10px;
justify-content: end;
.image{
width: 120px;
height: 80px;
@ -154,4 +211,76 @@
}
}
}
.retrieval,
.card {
background-color: #f4f6f8;
font-size: 0.875rem;
line-height: 1.25rem;
border-radius: 0.375rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
}
.retrieval:after{
animation: blink 1s steps(5, start) infinite;
color: #000;
content: '_';
font-weight: 700;
margin-left: 3px;
vertical-align: baseline;
}
.card{
width: 100%;
background-color: unset;
}
.ai-card{
width: 98%;
height: 100%;
cursor: pointer;
.ai-card-title{
width: 100%;
line-height: 20px;
letter-spacing: 0;
white-space: pre-line;
overflow: hidden;
display: -webkit-box;
text-overflow: ellipsis;
-webkit-box-orient: vertical;
font-weight: 600;
font-size: 18px;
text-align: left;
color: #191919;
-webkit-line-clamp: 1;
}
.ai-card-img{
margin-top: 10px;
background-color: transparent;
border-radius: 8px;
display: flex;
width: 100%;
height: max-content;
}
.ai-card-desc{
margin-top: 10px;
width: 100%;
font-size: 14px;
font-weight: 400;
line-height: 20px;
letter-spacing: 0;
white-space: pre-line;
-webkit-box-orient: vertical;
overflow: hidden;
display: -webkit-box;
text-overflow: ellipsis;
text-align: left;
color: #666f;
-webkit-line-clamp: 3;
}
}
@media (max-width: 600px) {
.content{
width: 100%;
}
}
</style>

View File

@ -1,10 +1,26 @@
<template>
<div v-if="text != ''" class="textWrap" :class="[inversion === 'user' ? 'self' : 'chatgpt']" ref="textRef">
<div v-if="inversion != 'user'">
<div class="markdown-body" :class="{ 'markdown-body-generate': loading }" v-html="text" />
<div v-if="inversion != 'user'" :style="{ width: getIsMobile? screenWidth : 'auto' }">
<div class="markdown-body" :class="{ 'markdown-body-generate': loading }" :style="{color:error?'#FF4444 !important':''}" v-html="text" />
<template v-if="showRefKnow">
<a-divider orientation="left">引用</a-divider>
<template v-for="(item, idx) of referenceKnowledge" :key="idx">
<a-tooltip :title="item.substring(0, 800)">
<a-tag >
<a-space>
<img :src="knowledgePng" width="16" height="16"/>
<div style="max-width: 240px; overflow: hidden;white-space: nowrap;text-overflow: ellipsis;">
{{ item }}
</div>
</a-space>
</a-tag>
</a-tooltip>
</template>
</template>
</div>
<div v-else class="msg" v-text="text" />
<div v-else class="msg" v-html="text" />
</div>
<ImageViewer v-if="amplifyImage" :imageUrl="imageUrl" @hide="pictureHide"></ImageViewer>
</template>
<script setup lang="ts">
@ -16,11 +32,21 @@
import './style/github-markdown.less';
import './style/highlight.less';
import './style/style.less';
import ImageViewer from '@/views/super/airag/aiapp/chat/components/ImageViewer.vue';
import { useAppInject } from "@/hooks/web/useAppInject";
import { useGlobSetting } from "@/hooks/setting";
import knowledgePng from '../../aiknowledge/icon/knowledge.png'
const props = defineProps(['dateTime', 'text', 'inversion', 'error', 'loading']);
/**
* 屏幕宽度
*/
const screenWidth = ref<string>();
const { getIsMobile } = useAppInject();
const props = defineProps(['dateTime', 'text', 'inversion', 'error', 'loading', 'referenceKnowledge']);
const textRef = ref();
const mdi = new MarkdownIt({
html: false,
html: true,
linkify: true,
highlight(code, language) {
const validLang = !!(language && hljs.getLanguage(language));
@ -36,11 +62,49 @@
mdi.use(mdKatex, { blockClass: 'katexmath-block rounded-md p-[10px]', errorColor: ' #cc0000' });
const text = computed(() => {
const value = props.text ?? '';
if (props.inversion != 'user') return mdi.render(value);
return value;
let value = props.text ?? '';
if (props.inversion != 'user'){
value = replaceImageWith(value);
value = replaceDomainUrl(value);
return mdi.render(value);
}
return value.replace("\n","<br>");
});
// 是否显示引用知识库
const showRefKnow = computed(() => {
const {loading, referenceKnowledge} = props
if (loading) {
return false;
}
return Array.isArray(referenceKnowledge) && referenceKnowledge.length > 0;
})
//替换图片宽度
const replaceImageWith = markdownContent => {
// 支持图片设置width的写法 ![](/static/jimuImages/screenshot_1617252560523.png =100)
const regex = /!\[([^\]]*)\]\(([^)]+)=([0-9]+)\)/g;
return markdownContent.replace(regex, (match, alt, src, width) => {
let reg = /#\s*{\s*domainURL\s*}/g;
src = src.replace(reg,domainUrl);
return `<div><img src='${src}' alt='${alt}' width='${width}' /></div>`;
});
};
const { domainUrl } = useGlobSetting();
//替换domainURL
const replaceDomainUrl = markdownContent => {
const regex = /!\[([^\]]*)\]\(.*?#\s*{\s*domainURL\s*}.*?\)/g;
return markdownContent.replace(regex, (match) => {
let reg = /#\s*{\s*domainURL\s*}/g;
return match.replace(reg,domainUrl);
})
}
//是否放大图片
const amplifyImage = ref<boolean>(false);
//图片地址
const imageUrl = ref<string>('');
function highlightBlock(str: string, lang?: string) {
return `<pre class="code-block-wrapper"><div class="code-block-header"><span class="code-block-header__lang">${lang}</span><span class="code-block-header__copy">复制代码</span></div><code class="hljs code-block-body ${lang}">${str}</code></pre>`;
}
@ -72,16 +136,74 @@
}
}
/**
* 添加图片点击事件
*/
function addImageClickEvent() {
if (textRef.value) {
const image = textRef.value.querySelectorAll('img');
image.forEach((img) => {
img.addEventListener('click', () => {
imageUrl.value = img.src;
amplifyImage.value = true;
})
});
}
}
/**
* 移出图片点击事件
*/
function removeImageClickEvent(){
if (textRef.value) {
const image = textRef.value.querySelectorAll('img');
image.forEach((img) => {
img.removeEventListener('click', () => { })
});
}
}
/**
* 图片隐藏
*/
function pictureHide(){
amplifyImage.value = false;
imageUrl.value = ""
}
/**
* 设置markdown body整体宽度
*/
function setMarkdownBodyWidth() {
//平板
console.log("window.innerWidth::",window.innerWidth)
if(window.innerWidth>600 && window.innerWidth<1024){
screenWidth.value = window.innerWidth - 120 + 'px';
}else if(window.innerWidth < 600){
//手机
screenWidth.value = window.innerWidth - 60 + 'px';
}
}
onMounted(() => {
addCopyEvents();
addImageClickEvent();
setMarkdownBodyWidth();
window.addEventListener('resize', setMarkdownBodyWidth);
});
onUpdated(() => {
addCopyEvents();
addImageClickEvent();
});
onUnmounted(() => {
removeCopyEvents();
removeImageClickEvent();
window.removeEventListener('resize', setMarkdownBodyWidth);
});
function copyToClip(text: string) {
@ -111,6 +233,18 @@
font-size: 0.875rem;
line-height: 1.25rem;
}
.error {
background: linear-gradient(135deg, #FF4444, #FF914D) !important;
border-radius: 0.375rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
font-size: 0.875rem;
line-height: 1.25rem;
}
.self {
// background-color: #d2f9d1;
background-color: @primary-color;
@ -125,4 +259,11 @@
font-size: 0.875rem;
line-height: 1.25rem;
}
@media (max-width: 1024px) {
//手机和平板下的样式
.textWrap{
margin-left: -40px;
margin-top: 10px;
}
}
</style>

View File

@ -0,0 +1,71 @@
<!--image放大封装-->
<template>
<div class="amplify-image">
<div class="img-preview-content" @click="hideImageClick" @mousewheel="handlePicMousewheel">
<img :src="imageUrl" ref="imageRef" />
</div>
</div>
</template>
<script setup lang="ts">
//图片地址
import {onMounted, ref, unref} from 'vue';
const props = defineProps(['imageUrl']);
const emit = defineEmits(['register', 'hide']);
//图片的ref
const imageRef = ref();
//缩放级别
const scale = ref<number>(1);
/**
* 隐藏图片
*/
function hideImageClick() {
scale.value = 1;
emit('hide')
}
/**
* 鼠标滑轮滚动
* @param event
*/
function handlePicMousewheel(event) {
event.preventDefault();
// 判断是放大还是缩小
const delta = event.deltaY > 0 ? -1 : 1;
const scaleStep = 0.1;
// 更新缩放级别
scale.value = scale.value + delta * scaleStep
imageRef.value.style.transform = `scale(${unref(scale)})`;
}
</script>
<style scoped lang="less">
.amplify-image{
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1000;
background: rgba(0, 0, 0, 0.5);
.img-preview-content{
display: flex;
width: 100%;
height: 100%;
color: #fff;
justify-content: center;
align-items: center;
touch-action: none;
-webkit-user-drag: none;
img{
transition: transform 0.3s;
background-position: center center;
background-repeat: no-repeat;
-webkit-background-size: cover;
-moz-background-size: cover;
background-size: cover;
}
}
}
</style>

View File

@ -2,38 +2,40 @@
(function () {
let widgetInstance = null;
const defaultConfig = {
// ֧<EFBFBD><EFBFBD>'top-left'<EFBFBD><EFBFBD><EFBFBD><EFBFBD>, 'top-right'<EFBFBD><EFBFBD><EFBFBD><EFBFBD>, 'bottom-left'<EFBFBD><EFBFBD><EFBFBD><EFBFBD>, 'bottom-right'<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// 支持'top-left'左上, 'top-right'右上, 'bottom-left'左下, 'bottom-right'右下
iconPosition: 'bottom-right',
//ͼ<EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD>С
iconSize: '30px',
//ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫ
//图标的大小
iconSize: '45px',
//图标的颜色
iconColor: '#155eef',
//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>޸<EFBFBD>
//必填不允许修改
appId: '',
//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><EFBFBD><EFBFBD>
//聊天弹窗的宽度
chatWidth: '800px',
//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ĸ߶<EFBFBD>
//聊天弹窗的高度
chatHeight: '700px',
};
/**
* <EFBFBD><EFBFBD><EFBFBD><EFBFBD>aiͼ<EFBFBD><EFBFBD>
* 创建ai图标
* @param config
*/
function createAiChat(config) {
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ<EFBFBD><EFBFBD>ȷ<EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><EFBFBD>ʵ<EFBFBD><EFBFBD>
// 单例模式,确保只存在一个实例
if (widgetInstance) {
return;
}
// <EFBFBD>ϲ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// 合并配置
const finalConfig = { ...defaultConfig, ...config };
if (!finalConfig.appId) {
console.error('appIdΪ<EFBFBD>գ<EFBFBD>');
console.error('appId为空');
return;
}
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
let body = document.body;
body.style.margin = "0";
// 创建容器
const container = document.createElement('div');
container.style.cssText = `
position: fixed;
@ -41,39 +43,50 @@
${getPositionStyles(finalConfig.iconPosition)}
cursor: pointer;
`;
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD><EFBFBD>
// 创建图标
const icon = document.createElement('div');
icon.style.cssText = `
width: ${finalConfig.iconSize};
height: ${finalConfig.iconSize};
background-color: ${finalConfig.iconColor};
border-radius: 50%;
box-shadow: rgba(0, 0, 0, 0.2) 0 4px 8px 0;
padding: 12px;
box-shadow: #cccccc 0 4px 8px 0;
padding: 10px;
display: flex;
align-items: center;
justify-content: center;
color: white;
box-sizing: border-box;
`;
icon.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 1024 1024" class="iconify iconify--ant-design"><path fill="currentColor" d="M573 421c-23.1 0-41 17.9-41 40s17.9 40 41 40c21.1 0 39-17.9 39-40s-17.9-40-39-40m-280 0c-23.1 0-41 17.9-41 40s17.9 40 41 40c21.1 0 39-17.9 39-40s-17.9-40-39-40"></path><path fill="currentColor" d="M894 345c-48.1-66-115.3-110.1-189-130v.1c-17.1-19-36.4-36.5-58-52.1c-163.7-119-393.5-82.7-513 81c-96.3 133-92.2 311.9 6 439l.8 132.6c0 3.2.5 6.4 1.5 9.4c5.3 16.9 23.3 26.2 40.1 20.9L309 806c33.5 11.9 68.1 18.7 102.5 20.6l-.5.4c89.1 64.9 205.9 84.4 313 49l127.1 41.4c3.2 1 6.5 1.6 9.9 1.6c17.7 0 32-14.3 32-32V753c88.1-119.6 90.4-284.9 1-408M323 735l-12-5l-99 31l-1-104l-8-9c-84.6-103.2-90.2-251.9-11-361c96.4-132.2 281.2-161.4 413-66c132.2 96.1 161.5 280.6 66 412c-80.1 109.9-223.5 150.5-348 102m505-17l-8 10l1 104l-98-33l-12 5c-56 20.8-115.7 22.5-171 7l-.2-.1C613.7 788.2 680.7 742.2 729 676c76.4-105.3 88.8-237.6 44.4-350.4l.6.4c23 16.5 44.1 37.1 62 62c72.6 99.6 68.5 235.2-8 330"></path><path fill="currentColor" d="M433 421c-23.1 0-41 17.9-41 40s17.9 40 41 40c21.1 0 39-17.9 39-40s-17.9-40-39-40"></path></svg>';
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>iframe<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// 创建iframe容器
const iframeContainer = document.createElement('div');
let right = finalConfig.chatWidth === '100%' ? '0' : '10px';
let bottom = finalConfig.chatHeight === '100%' ? '0' : '10px';
let chatWidth = finalConfig.chatWidth;
let chatHeight = finalConfig.chatHeight;
if(isMobileDevice()){
chatWidth = "100%";
chatHeight = "100%";
right = '0';
bottom = '0';
}
iframeContainer.style.cssText = `
position: absolute;
right: 10px;
bottom: 10px;
width: ${finalConfig.chatWidth} !important;
height: ${finalConfig.chatHeight} !important;
position: fixed;
right: ${right};
bottom: ${bottom};
width: ${chatWidth} !important;
height: ${chatHeight} !important;
background: white;
border-radius: 8px;
box-shadow: 0 0 20px rgba(0,0,0,0.2);
box-shadow: 0 0 20px #cccccc;
display: none;
z-index: 10000;
`;
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>iframe
// 创建iframe
const iframe = document.createElement('iframe');
iframe.style.cssText = `
width: 100%;
@ -83,15 +96,23 @@
`;
iframe.id = 'ai-app-chat-document';
iframe.src = getIframeSrc(finalConfig) + '/ai/app/chat/' + finalConfig.appId;
// <20><><EFBFBD><EFBFBD><EFBFBD>رհ<D8B1>ť
//update-begin---author:wangshuai---date:2025-04-25---for:【QQYUN-12159】【AI 广告位】让需要自建AI知识库的用户知道如何通过敲敲云搭建自己的AI知识库---
iframe.src = getIframeSrc(finalConfig) + '/ai/app/chat/' + finalConfig.appId + "?source=chatJs";
//update-end---author:wangshuai---date:2025-04-25---for:【QQYUN-12159】【AI 广告位】让需要自建AI知识库的用户知道如何通过敲敲云搭建自己的AI知识库---
let iconRight = finalConfig.chatWidth === '100%'?'0':'-6px';
let iconTop = finalConfig.chatWidth === '100%'?'0':'-9px';
if(isMobileDevice()){
iconRight = '2px';
iconTop = '2px';
}
// 创建关闭按钮
const closeBtn = document.createElement('div');
closeBtn.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" width="1em" height="1em" viewBox="0 0 1024 1024" class="iconify iconify--ant-design"><path fill="currentColor" fill-rule="evenodd" d="M799.855 166.312c.023.007.043.018.084.059l57.69 57.69c.041.041.052.06.059.084a.1.1 0 0 1 0 .069c-.007.023-.018.042-.059.083L569.926 512l287.703 287.703c.041.04.052.06.059.083a.12.12 0 0 1 0 .07c-.007.022-.018.042-.059.083l-57.69 57.69c-.041.041-.06.052-.084.059a.1.1 0 0 1-.069 0c-.023-.007-.042-.018-.083-.059L512 569.926L224.297 857.629c-.04.041-.06.052-.083.059a.12.12 0 0 1-.07 0c-.022-.007-.042-.018-.083-.059l-57.69-57.69c-.041-.041-.052-.06-.059-.084a.1.1 0 0 1 0-.069c.007-.023.018-.042.059-.083L454.073 512L166.371 224.297c-.041-.04-.052-.06-.059-.083a.12.12 0 0 1 0-.07c.007-.022.018-.042.059-.083l57.69-57.69c.041-.041.06-.052.084-.059a.1.1 0 0 1 .069 0c.023.007.042.018.083.059L512 454.073l287.703-287.702c.04-.041.06-.052.083-.059a.12.12 0 0 1 .07 0Z"></path></svg>';
closeBtn.style.cssText = `
position: absolute;
right: -3px;
top: -10px;
margin-top: ${iconTop};
right: ${iconRight};
cursor: pointer;
background: white;
width: 25px;
@ -100,17 +121,17 @@
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
box-shadow: 0 2px 5px #cccccc;
`;
// <EFBFBD><EFBFBD>װԪ<EFBFBD><EFBFBD>
// 组装元素
iframeContainer.appendChild(closeBtn);
iframeContainer.appendChild(iframe);
document.body.appendChild(iframeContainer);
container.appendChild(icon);
document.body.appendChild(container);
// <EFBFBD>¼<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// 事件监听
icon.addEventListener('click', () => {
iframeContainer.style.display = 'block';
});
@ -119,7 +140,7 @@
iframeContainer.style.display = 'none';
});
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// 保存实例引用
widgetInstance = {
remove: () => {
container.remove();
@ -129,7 +150,7 @@
}
/**
* <EFBFBD><EFBFBD>ȡλ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ
* 获取位置信息
*
* @param position
* @returns {*|string}
@ -145,7 +166,7 @@
}
/**
* <EFBFBD><EFBFBD>ȡsrc<EFBFBD><EFBFBD>ַ
* 获取src地址
*/
function getIframeSrc(finalConfig) {
const specificScript = document.getElementById("e7e007dd52f67fe36365eff636bbffbd");
@ -154,6 +175,14 @@
}
}
// <20><>¶ȫ<C2B6>ַ<EFBFBD><D6B7><EFBFBD>
/**
* 判断是否为手机
* @returns {boolean}
*/
function isMobileDevice() {
return /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}
// 暴露全局方法
window.createAiChat = createAiChat;
})();

View File

@ -1,6 +1,6 @@
<template>
<div class="presetQuestion-wrap">
<svg
<!-- <svg
v-if="btnShow"
class="leftBtn"
:class="leftBtnStatus"
@ -16,7 +16,7 @@
fill="currentColor"
p-id="5071"
></path>
</svg>
</svg>-->
<div class="content">
<ul ref="ulElemRef">
<li v-for="(item, index) in data" :key="index" class="item" @click="handleQuestion(item.descr)">
@ -28,7 +28,7 @@
</li>
</ul>
</div>
<svg
<!-- <svg
v-if="btnShow"
class="rightBtn"
:class="rightBtnStatus"
@ -44,7 +44,7 @@
fill="currentColor"
p-id="5071"
></path>
</svg>
</svg>-->
</div>
</template>
@ -135,7 +135,8 @@ import {ref, onMounted, onBeforeUnmount, watch} from 'vue';
display: flex;
margin-bottom: 0;
width: 100%;
overflow-y: auto;
overflow-y: hidden;
overflow-x: auto;
/* 隐藏所有滚动条 */
&::-webkit-scrollbar {
display: none;
@ -143,8 +144,15 @@ import {ref, onMounted, onBeforeUnmount, watch} from 'vue';
width: 0;
}
}
ul:hover {
&::-webkit-scrollbar {
display: block;
height: 10px;
width: 10px;
}
}
.item {
border: 1px solid rgba(0, 0, 0, 0.1);
border: 1px solid #e6e6e6;
border-radius: 16px;
cursor: pointer;
font-size: 14px;

View File

@ -71,6 +71,13 @@
</li>
</ul>
</div>
<div class="left-footer" v-if="source!='chatJs'">
AI客服由
<a style="color: #4183c4;margin-left: 2px;margin-right: 2px" href="https://www.qiaoqiaoyun.com/aiCustomerService" target="_blank">
敲敲云
</a>
提供
</div>
</div>
</template>
@ -80,7 +87,7 @@
import { defHttp } from '@/utils/http/axios';
import { getFileAccessHttpUrl } from '@/utils/common/compUtils';
import defaultImg from '../img/ailogo.png';
const props = defineProps(['dataSource', 'appData']);
const props = defineProps(['dataSource', 'appData','source']);
const emit = defineEmits(['save', 'click', 'reloadRight', 'prologue']);
const inputRef = ref(null);
const router = useRouter();
@ -149,8 +156,7 @@
emit('click', props.dataSource.history[0].title, findIndex);
} else {
// 删没了(删除了最后一个)
props.dataSource.active = null;
emit('click', "", -1);
handleCreate();
}
}
//update-begin---author:wangshuai---date:2025-03-12---for:【QQYUN-11560】新建聊天内容为空无法删除---
@ -216,6 +222,7 @@
flex: 1;
min-height: 0;
overflow: auto;
margin-bottom: 20px;
&::-webkit-scrollbar {
width: 8px;
height: 8px;
@ -285,6 +292,9 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 14px;
position: relative;
top: 2px;
&.ant-input {
margin-right: 20px;
}
@ -316,4 +326,13 @@
font-size: 16px;
}
}
.left-footer{
display:flex;
margin-right: 20px;
font-size: 12px;
position: absolute;
bottom: 4px;
left: 50px;
width: 100%;
}
</style>

View File

@ -38,10 +38,10 @@ html.dark {
--color-canvas-subtle: #161b22;
--color-border-default: #30363d;
--color-border-muted: #21262d;
--color-neutral-muted: rgba(110, 118, 129, 0.4);
--color-neutral-muted: #bfc4cc;
--color-accent-fg: #58a6ff;
--color-accent-emphasis: #1f6feb;
--color-attention-subtle: rgba(187, 128, 9, 0.15);
--color-attention-subtle: #ece6d9;
--color-danger-fg: #f85149;
}
}
@ -86,7 +86,7 @@ html {
--color-canvas-subtle: #f6f8fa;
--color-border-default: #d0d7de;
--color-border-muted: hsla(210, 18%, 87%, 1);
--color-neutral-muted: rgba(175, 184, 193, 0.2);
--color-neutral-muted: #e7ebf2;
--color-accent-fg: #0969da;
--color-accent-emphasis: #0969da;
--color-attention-subtle: #fff8c5;