2 Commits

Author SHA1 Message Date
chenchun
6f1efafd86 feat: 发布2.8版本 2025-12-17 12:10:24 +08:00
Gsh
2714a507d9 fix: 文件上传提示优化、element-plus-x版本回退 2025-12-16 22:54:43 +08:00
10 changed files with 15131 additions and 157 deletions

View File

@@ -35,32 +35,37 @@
"@floating-ui/dom": "^1.7.2",
"@floating-ui/vue": "^1.1.7",
"@jsonlee_12138/enum": "^1.0.4",
"@shikijs/transformers": "^3.7.0",
"@vueuse/core": "^13.5.0",
"@vueuse/integrations": "^13.5.0",
"chatarea": "^6.0.3",
"date-fns": "^2.30.0",
"deepmerge": "^4.3.1",
"dompurify": "^3.2.6",
"driver.js": "^1.3.6",
"echarts": "^6.0.0",
"element-plus": "^2.10.4",
"fingerprintjs": "^0.5.3",
"github-markdown-css": "^5.8.1",
"highlight.js": "^11.11.1",
"hook-fetch": "^2.0.4-beta.1",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"mammoth": "^1.11.0",
"mermaid": "11.12.0",
"nprogress": "^0.2.0",
"pdfjs-dist": "^5.4.449",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.4.1",
"prismjs": "^1.30.0",
"property-information": "^7.1.0",
"qrcode": "^1.5.4",
"radash": "^12.1.1",
"reset-css": "^5.0.2",
"vue": "^3.5.17",
"vue-element-plus-x": "1.3.7",
"vue-router": "4",
"xlsx": "^0.18.5",
"@shikijs/transformers": "^3.7.0",
"chatarea": "^6.0.3",
"deepmerge": "^4.3.1",
"dompurify": "^3.2.6",
"github-markdown-css": "^5.8.1",
"highlight.js": "^11.11.1",
"lodash": "^4.17.21",
"mermaid": "11.12.0",
"prismjs": "^1.30.0",
"property-information": "^7.1.0",
"rehype-katex": "^7.0.1",
"rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0",
@@ -69,21 +74,46 @@
"remark-math": "^6.0.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.2",
"reset-css": "^5.0.2",
"shiki": "^3.7.0",
"ts-md5": "^2.0.1",
"unified": "^11.0.5",
"unist-util-visit": "^5.0.0",
"vue": "^3.5.17",
"vue-element-plus-x": "1.3.7",
"vue-router": "4",
"xlsx": "^0.18.5"
"unist-util-visit": "^5.0.0"
},
"devDependencies": {
"@antfu/eslint-config": "^4.16.2",
"@changesets/cli": "^2.29.5",
"@chromatic-com/storybook": "^3.2.7",
"@commitlint/config-conventional": "^19.8.1",
"@types/fingerprintjs__fingerprintjs": "^3.0.2",
"@vitejs/plugin-vue": "^6.0.0",
"@vue/tsconfig": "^0.7.0",
"commitlint": "^19.8.1",
"cz-git": "^1.12.0",
"eslint": "^9.31.0",
"husky": "^9.1.7",
"lint-staged": "^16.1.2",
"postcss": "8.4.31",
"postcss-html": "1.5.0",
"prettier": "^3.6.2",
"rollup-plugin-visualizer": "^6.0.3",
"sass-embedded": "^1.89.2",
"stylelint": "^16.21.1",
"stylelint-config-html": "^1.1.0",
"stylelint-config-recess-order": "^7.1.0",
"stylelint-config-recommended-scss": "^15.0.1",
"stylelint-config-recommended-vue": "^1.6.1",
"stylelint-config-standard": "^38.0.0",
"stylelint-config-standard-scss": "^15.0.1",
"typescript": "~5.8.3",
"typescript-api-pro": "^0.0.7",
"unocss": "66.3.3",
"unplugin-auto-import": "^19.3.0",
"unplugin-vue-components": "^28.8.0",
"vite": "^6.3.5",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-env-typed": "^0.0.2",
"vite-plugin-svg-icons": "^2.0.1",
"vue-tsc": "^3.0.1",
"@chromatic-com/storybook": "^3.2.7",
"@jsonlee_12138/markdown-it-mermaid": "0.0.6",
"@storybook/addon-essentials": "^8.6.14",
"@storybook/addon-onboarding": "^8.6.14",
@@ -97,52 +127,22 @@
"@storybook/vue3": "^8.6.14",
"@storybook/vue3-vite": "^8.6.14",
"@types/dom-speech-recognition": "^0.0.4",
"@types/fingerprintjs__fingerprintjs": "^3.0.2",
"@types/fs-extra": "^11.0.4",
"@types/markdown-it": "^14.1.2",
"@types/prismjs": "^1.26.5",
"@vitejs/plugin-vue": "^6.0.0",
"@vitest/browser": "^3.2.4",
"@vitest/coverage-v8": "^3.2.4",
"@vue/tsconfig": "^0.7.0",
"commitlint": "^19.8.1",
"cz-git": "^1.12.0",
"eslint": "^9.31.0",
"esno": "^4.8.0",
"fast-glob": "^3.3.3",
"husky": "^9.1.7",
"lint-staged": "^16.1.2",
"playwright": "^1.53.2",
"postcss": "8.4.31",
"postcss-html": "1.5.0",
"prettier": "^3.6.2",
"rimraf": "^6.0.1",
"rollup-plugin-visualizer": "^6.0.3",
"sass": "^1.89.2",
"sass-embedded": "^1.89.2",
"storybook": "^8.6.14",
"storybook-dark-mode": "^4.0.2",
"stylelint": "^16.21.1",
"stylelint-config-html": "^1.1.0",
"stylelint-config-recess-order": "^7.1.0",
"stylelint-config-recommended-scss": "^15.0.1",
"stylelint-config-recommended-vue": "^1.6.1",
"stylelint-config-standard": "^38.0.0",
"stylelint-config-standard-scss": "^15.0.1",
"terser": "^5.43.1",
"typescript": "~5.8.3",
"typescript-api-pro": "^0.0.7",
"unocss": "66.3.3",
"unplugin-auto-import": "^19.3.0",
"unplugin-vue-components": "^28.8.0",
"vite": "^6.3.5",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-dts": "^4.5.4",
"vite-plugin-env-typed": "^0.0.2",
"vite-plugin-lib-inject-css": "^2.2.2",
"vite-plugin-svg-icons": "^2.0.1",
"vitest": "^3.2.4",
"vue-tsc": "^3.0.1"
"vitest": "^3.2.4"
},
"config": {
"commitizen": {

14992
Yi.Ai.Vue3/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -6,8 +6,6 @@ import { ElMessage } from 'element-plus';
import mammoth from 'mammoth';
import * as pdfjsLib from 'pdfjs-dist';
import * as XLSX from 'xlsx';
import Popover from '@/components/Popover/index.vue';
import SvgIcon from '@/components/SvgIcon/index.vue';
import { useFilesStore } from '@/stores/modules/files';
// 配置 PDF.js worker - 使用稳定的 CDN
@@ -413,49 +411,73 @@ onChange(async (files) => {
// 处理图片文件
if (isImage) {
try {
// 多级压缩策略:逐步降低质量和分辨率
const compressionLevels = [
{ maxWidth: 800, maxHeight: 800, quality: 0.6 },
{ maxWidth: 600, maxHeight: 600, quality: 0.5 },
{ maxWidth: 400, maxHeight: 400, quality: 0.4 },
];
// 控制参数:是否开启图片压缩
const enableImageCompression = true; // 这里可以设置为变量或从配置读取
let compressedBlob: Blob | null = null;
let finalBlob: Blob = file;
let base64 = '';
let compressionLevel = 0;
// 尝试不同级别的压缩
for (const level of compressionLevels) {
compressionLevel++;
compressedBlob = await compressImage(file, level.maxWidth, level.maxHeight, level.quality);
base64 = await blobToBase64(compressedBlob);
// 检查是否满足总长度限制
const estimatedLength = Math.floor(base64.length * 0.5);
if (totalContentLength + estimatedLength <= MAX_TOTAL_CONTENT_LENGTH) {
// 满足限制,使用当前压缩级别
totalContentLength += estimatedLength;
break;
}
// 如果是最后一级压缩仍然超限,则跳过
if (compressionLevel === compressionLevels.length) {
const fileSizeMB = (file.size / 1024 / 1024).toFixed(2);
ElMessage.error(`${file.name} (${fileSizeMB}MB) 即使压缩后仍超过总内容限制,已跳过`);
compressedBlob = null;
break;
}
}
// 如果压缩失败,跳过此文件
if (!compressedBlob) {
continue;
}
// 计算压缩比例
const originalSize = (file.size / 1024).toFixed(2);
const compressedSize = (compressedBlob.size / 1024).toFixed(2);
console.log(`图片压缩: ${file.name} - 原始: ${originalSize}KB, 压缩后: ${compressedSize}KB (级别${compressionLevel})`);
let finalSize = originalSize;
if (enableImageCompression) {
// 多级压缩策略:逐步降低质量和分辨率
const compressionLevels = [
{ maxWidth: 800, maxHeight: 800, quality: 0.6 },
{ maxWidth: 600, maxHeight: 600, quality: 0.5 },
{ maxWidth: 400, maxHeight: 400, quality: 0.4 },
];
let compressedBlob: Blob | null = null;
// 尝试不同级别的压缩
for (const level of compressionLevels) {
compressionLevel++;
compressedBlob = await compressImage(file, level.maxWidth, level.maxHeight, level.quality);
base64 = await blobToBase64(compressedBlob);
// 检查是否满足总长度限制
const estimatedLength = Math.floor(base64.length * 0.5);
if (totalContentLength + estimatedLength <= MAX_TOTAL_CONTENT_LENGTH) {
// 满足限制,使用当前压缩级别
totalContentLength += estimatedLength;
finalBlob = compressedBlob;
break;
}
// 如果是最后一级压缩仍然超限,则跳过
if (compressionLevel === compressionLevels.length) {
const fileSizeMB = (file.size / 1024 / 1024).toFixed(2);
ElMessage.error(`${file.name} 图片内容过大,请压缩后上传`);
compressedBlob = null;
break;
}
}
// 如果压缩失败,跳过此文件
if (!compressedBlob) {
continue;
}
// 计算压缩比例
finalSize = (finalBlob.size / 1024).toFixed(2);
console.log(`图片压缩: ${file.name} - 原始: ${originalSize}KB, 压缩后: ${finalSize}KB (级别${compressionLevel})`);
}
else {
// 不开启压缩时,直接转换原始文件
base64 = await blobToBase64(file);
// 检查总长度限制
const estimatedLength = Math.floor(base64.length * 0.5);
if (totalContentLength + estimatedLength > MAX_TOTAL_CONTENT_LENGTH) {
const fileSizeMB = (file.size / 1024 / 1024).toFixed(2);
ElMessage.error(`${file.name} (${fileSizeMB}MB) 超过总长度限制,已跳过`);
continue;
}
totalContentLength += estimatedLength;
console.log(`图片未压缩: ${file.name} - 大小: ${originalSize}KB`);
}
arr.push({
uid: crypto.randomUUID(),
@@ -473,8 +495,8 @@ onChange(async (files) => {
});
}
catch (error) {
console.error('压缩图片失败:', error);
ElMessage.error(`${file.name} 压缩失败`);
console.error('处理图片失败:', error);
ElMessage.error(`${file.name} 处理失败`);
continue;
}
}
@@ -493,9 +515,10 @@ onChange(async (files) => {
// 至少保留1000字符才有意义
finalContent = result.content.substring(0, remainingSpace);
wasTruncated = true;
} else if (remainingSpace <= 1000) {
}
else if (remainingSpace <= 1000) {
const fileSizeKB = (file.size / 1024).toFixed(2);
ElMessage.error(`${file.name} (${fileSizeKB}KB) 会超过总内容限制,已跳过`);
ElMessage.error(`${file.name} (${fileSizeKB}KB) 会超过总长度限制,已跳过`);
continue;
}
@@ -542,9 +565,10 @@ onChange(async (files) => {
if (result.content.length > remainingSpace && remainingSpace > 1000) {
finalContent = result.content.substring(0, remainingSpace);
wasTruncated = true;
} else if (remainingSpace <= 1000) {
}
else if (remainingSpace <= 1000) {
const fileSizeKB = (file.size / 1024).toFixed(2);
ElMessage.error(`${file.name} (${fileSizeKB}KB) 会超过总内容限制,已跳过`);
ElMessage.error(`${file.name} (${fileSizeKB}KB) 会超过总长度限制,已跳过`);
continue;
}
@@ -591,9 +615,10 @@ onChange(async (files) => {
if (result.content.length > remainingSpace && remainingSpace > 1000) {
finalContent = result.content.substring(0, remainingSpace);
wasTruncated = true;
} else if (remainingSpace <= 1000) {
}
else if (remainingSpace <= 1000) {
const fileSizeKB = (file.size / 1024).toFixed(2);
ElMessage.error(`${file.name} (${fileSizeKB}KB) 会超过总内容限制,已跳过`);
ElMessage.error(`${file.name} (${fileSizeKB}KB) 会超过总长度限制,已跳过`);
continue;
}
@@ -645,9 +670,10 @@ onChange(async (files) => {
if (finalContent.length > remainingSpace && remainingSpace > 1000) {
finalContent = finalContent.substring(0, remainingSpace);
truncated = true;
} else if (remainingSpace <= 1000) {
}
else if (remainingSpace <= 1000) {
const fileSizeKB = (file.size / 1024).toFixed(2);
ElMessage.error(`${file.name} (${fileSizeKB}KB) 会超过总内容限制,已跳过`);
ElMessage.error(`${file.name} (${fileSizeKB}KB) 会超过总长度限制,已跳过`);
continue;
}

View File

@@ -42,12 +42,8 @@ onMounted(() => {
</script>
<template>
<div v-show="layout === 'blankPage'">
<LayoutBlankPage />
<!-- <component :is="LayoutComponent[layout]" /> -->
</div>
<div v-show="layout !== 'blankPage'">
<LayoutVertical />
<div>
<component :is="LayoutComponent[layout]" />
</div>
</template>

View File

@@ -13,6 +13,7 @@ import { Sender } from 'vue-element-plus-x';
import { useRoute } from 'vue-router';
import { send } from '@/api';
import ModelSelect from '@/components/ModelSelect/index.vue';
import YMarkdown from '@/vue-element-plus-y/components/XMarkdown/index.vue';
import { useGuideTourStore } from '@/stores';
import { useChatStore } from '@/stores/modules/chat';
import { useFilesStore } from '@/stores/modules/files';
@@ -21,7 +22,6 @@ import { useUserStore } from '@/stores/modules/user';
import { getUserProfilePicture, systemProfilePicture } from '@/utils/user.ts';
import '@/styles/github-markdown.css';
import '@/styles/yixin-markdown.scss';
import YMarkdown from '@/vue-element-plus-y/components/XMarkdown/index.vue';
type MessageItem = BubbleProps & {
key: number;

View File

@@ -4,8 +4,7 @@ import type { PluggableList } from 'unified';
import type { PropType } from 'vue';
import type { CustomAttrs, SanitizeOptions, TVueMarkdown } from './types';
import { computed, defineComponent, ref, shallowRef, toRefs, watch } from 'vue';
import { watchDebounced } from '@vueuse/core';
import { defineComponent, shallowRef, toRefs, watch } from 'vue';
// import { useMarkdownContext } from '../components/MarkdownProvider';
import { render } from './hast-to-vnode';
import { useMarkdownProcessor } from './useProcessor';
@@ -64,24 +63,10 @@ const vueMarkdownImpl = defineComponent({
sanitizeOptions
});
// 防抖优化控制markdown更新频率避免流式渲染时频繁触发
const debouncedMarkdown = ref(markdown.value);
watchDebounced(
markdown,
(val) => {
debouncedMarkdown.value = val;
},
{ debounce: 50, maxWait: 200 } // 50ms防抖最多200ms必须更新一次
);
// 缓存优化使用computed缓存解析结果避免重复计算
const hast = computed(() => {
const mdast = processor.value.parse(debouncedMarkdown.value);
return processor.value.runSync(mdast) as Root;
});
return () => {
return render(hast.value, attrs, slots, customAttrs.value);
const mdast = processor.value.parse(markdown.value);
const hast = processor.value.runSync(mdast) as Root;
return render(hast, attrs, slots, customAttrs.value);
};
}
});
@@ -108,25 +93,12 @@ const vueMarkdownAsyncImpl = defineComponent({
});
const hast = shallowRef<Root | null>(null);
// 防抖优化控制markdown更新频率
const debouncedMarkdown = ref(markdown.value);
const process = async (): Promise<void> => {
const mdast = processor.value.parse(debouncedMarkdown.value);
const mdast = processor.value.parse(markdown.value);
hast.value = (await processor.value.run(mdast)) as Root;
};
// 使用防抖watch避免频繁触发异步处理
watchDebounced(
markdown,
(val) => {
debouncedMarkdown.value = val;
},
{ debounce: 50, maxWait: 200 }
);
watch(() => [debouncedMarkdown.value, processor.value], process, { flush: 'post' });
watch(() => [markdown.value, processor.value], process, { flush: 'sync' });
await process();

View File

@@ -1,5 +1,5 @@
import type { TVueMarkdownProps } from '@components/XMarkdownCore';
import type { CodeBlockHeaderExpose } from '@components/XMarkdownCore/components/CodeBlock/shiki-header';
import type { TVueMarkdownProps } from '../';
import type { CodeBlockHeaderExpose } from '../components/CodeBlock/shiki-header';
import type { ElxRunCodeOptions } from '../components/RunCode/type';
import type { InitShikiOptions } from './shikiHighlighter';

View File

@@ -4,8 +4,7 @@
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/vue-element-plus-y/components/*"]
"@/*": ["src/*"]
},
"typeRoots": ["./node_modules/@types", "./types"],
/* Linting */

View File

@@ -11,11 +11,6 @@ declare module 'vue' {
AccountPassword: typeof import('./../src/components/LoginDialog/components/FormLogin/AccountPassword.vue')['default']
APIKeyManagement: typeof import('./../src/components/userPersonalCenter/components/APIKeyManagement.vue')['default']
CardFlipActivity: typeof import('./../src/components/userPersonalCenter/components/CardFlipActivity.vue')['default']
CodeBlock: typeof import('./../src/components/YMarkdownCore/components/CodeBlock/index.vue')['default']
CodeLine: typeof import('./../src/components/YMarkdownCore/components/CodeLine/index.vue')['default']
CodeX: typeof import('./../src/components/YMarkdownCore/components/CodeX/index.vue')['default']
CopyCodeButton: typeof import('./../src/components/YMarkdownCore/components/CodeBlock/copy-code-button.vue')['default']
CustomLoading: typeof import('./../src/components/YMarkdownCore/components/RunCode/components/custom-loading.vue')['default']
DailyTask: typeof import('./../src/components/userPersonalCenter/components/DailyTask.vue')['default']
DeepThinking: typeof import('./../src/components/DeepThinking/index.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert']
@@ -29,6 +24,8 @@ declare module 'vue' {
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
@@ -61,12 +58,9 @@ declare module 'vue' {
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
FilesSelect: typeof import('./../src/components/FilesSelect/index.vue')['default']
HighLightCode: typeof import('./../src/components/YMarkdownCore/components/HighLightCode/index.vue')['default']
IconSelect: typeof import('./../src/components/IconSelect/index.vue')['default']
Indexl: typeof import('./../src/components/SupportModelProducts/indexl.vue')['default']
LoginDialog: typeof import('./../src/components/LoginDialog/index.vue')['default']
Mermaid: typeof import('./../src/components/YMarkdownCore/components/Mermaid/index.vue')['default']
MermaidToolbar: typeof import('./../src/components/YMarkdownCore/components/Mermaid/MermaidToolbar.vue')['default']
ModeList: typeof import('./../src/components/modeList/index.vue')['default']
ModelSelect: typeof import('./../src/components/ModelSelect/index.vue')['default']
NavDialog: typeof import('./../src/components/userPersonalCenter/NavDialog.vue')['default']
@@ -80,10 +74,6 @@ declare module 'vue' {
RegistrationForm: typeof import('./../src/components/LoginDialog/components/FormLogin/RegistrationForm.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
RunCode: typeof import('./../src/components/YMarkdownCore/components/RunCode/index.vue')['default']
RunCodeButton: typeof import('./../src/components/YMarkdownCore/components/CodeBlock/run-code-button.vue')['default']
RunCodeContent: typeof import('./../src/components/YMarkdownCore/components/RunCode/components/run-code-content.vue')['default']
RunCodeHeader: typeof import('./../src/components/YMarkdownCore/components/RunCode/components/run-code-header.vue')['default']
SupportModelList: typeof import('./../src/components/userPersonalCenter/components/SupportModelList.vue')['default']
SvgIcon: typeof import('./../src/components/SvgIcon/index.vue')['default']
SystemAnnouncementDialog: typeof import('./../src/components/SystemAnnouncementDialog/index.vue')['default']
@@ -92,7 +82,6 @@ declare module 'vue' {
UserManagement: typeof import('./../src/components/userPersonalCenter/components/UserManagement.vue')['default']
VerificationCode: typeof import('./../src/components/LoginDialog/components/FormLogin/VerificationCode.vue')['default']
WelecomeText: typeof import('./../src/components/WelecomeText/index.vue')['default']
YMarkdownAsync: typeof import('./../src/components/YMarkdownAsync/index.vue')['default']
}
export interface GlobalDirectives {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']