知识库
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled

This commit is contained in:
hahwu 2026-05-12 17:56:35 +08:00
parent 2ac2d198a3
commit a1bd2b850e
6 changed files with 983 additions and 7 deletions

View File

@ -44,7 +44,9 @@
"ant-design-vue": "catalog:",
"cal-heatmap": "^4.2.4",
"dayjs": "catalog:",
"dompurify": "^3.4.2",
"json-editor-vue": "^0.18.1",
"marked": "^18.0.3",
"pinia": "catalog:",
"pixi.js": "8.11.0-main.efa7feb",
"prettier-eslint": "^16.4.2",

View File

@ -66,6 +66,10 @@
"noChangesToSave": "No changes to save",
"deleteSuccess": "Delete Success"
},
"knowledge": {
"title": "Knowledge Base",
"chat": "Knowledge Q&A"
},
"operation": {
"title": "Operation",
"apk": "Client APK",

View File

@ -66,6 +66,10 @@
"noChangesToSave": "没有需要保存的更改",
"deleteSuccess": "删除成功"
},
"knowledge": {
"title": "知识库",
"chat": "知识问答"
},
"operation": {
"title": "运营管理",
"apk": "客户端 APK 下载",

View File

@ -0,0 +1,33 @@
import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '#/layouts';
import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [
{
component: BasicLayout,
meta: {
icon: 'lucide:book-open',
order: 1100,
title: $t('page.knowledge.title'),
authority: ['super'],
},
name: 'Knowledge',
path: '/knowledge',
children: [
{
name: 'KnowledgeChat',
path: '/knowledge/chat',
component: () => import('#/views/knowledge/chat.vue'),
meta: {
affixTab: false,
icon: 'lucide:message-square-text',
title: $t('page.knowledge.chat'),
authority: ['super'],
},
},
],
},
];
export default routes;

View File

@ -0,0 +1,913 @@
<script setup lang="ts">
import { nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
import DOMPurify from 'dompurify';
import { Page, VbenIcon } from '@vben/common-ui';
import { Button, Card, Tag } from 'ant-design-vue';
import { marked } from 'marked';
import { useAccessStore } from '@vben/stores';
interface Source {
chunk_id: string;
score: number;
summary?: string;
}
interface Msg {
role: 'bot' | 'err' | 'user';
text: string;
sources?: Source[];
streaming?: boolean;
}
const accessStore = useAccessStore();
const messages = ref<Msg[]>([]);
const input = ref('');
const sending = ref(false);
const logEl = ref<HTMLDivElement>();
const logBottomEl = ref<HTMLDivElement>();
let logObserver: MutationObserver | null = null;
const quickQuestions = [
'AdGiftData 表里有哪些字段?',
'UserBaseData 里和等级相关的字段有哪些?',
'OrderData 表常用索引字段是什么?',
];
marked.setOptions({
breaks: true,
gfm: true,
});
function scrollBottom() {
nextTick(() => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
logBottomEl.value?.scrollIntoView({
block: 'end',
});
logEl.value?.scrollTo({ top: logEl.value.scrollHeight });
});
});
});
}
onMounted(() => {
if (!logEl.value) {
return;
}
logObserver = new MutationObserver(() => {
if (sending.value) {
scrollBottom();
}
});
logObserver.observe(logEl.value, {
characterData: true,
childList: true,
subtree: true,
});
});
onBeforeUnmount(() => {
logObserver?.disconnect();
logObserver = null;
});
function extractStreamText(data: string) {
if (!data || data === '[DONE]') {
return '';
}
try {
const parsed = JSON.parse(data);
if (typeof parsed === 'string') {
return parsed;
}
if (typeof parsed === 'number' || typeof parsed === 'boolean') {
return String(parsed);
}
if (typeof parsed?.token === 'string') {
return parsed.token;
}
if (typeof parsed?.token === 'number' || typeof parsed?.token === 'boolean') {
return String(parsed.token);
}
if (typeof parsed?.content === 'string') {
return parsed.content;
}
if (typeof parsed?.content === 'number' || typeof parsed?.content === 'boolean') {
return String(parsed.content);
}
if (typeof parsed?.text === 'string') {
return parsed.text;
}
if (typeof parsed?.text === 'number' || typeof parsed?.text === 'boolean') {
return String(parsed.text);
}
if (typeof parsed?.answer === 'string') {
return parsed.answer;
}
if (typeof parsed?.answer === 'number' || typeof parsed?.answer === 'boolean') {
return String(parsed.answer);
}
if (typeof parsed?.delta === 'string') {
return parsed.delta;
}
if (typeof parsed?.delta === 'number' || typeof parsed?.delta === 'boolean') {
return String(parsed.delta);
}
if (typeof parsed?.choices?.[0]?.delta?.content === 'string') {
return parsed.choices[0].delta.content;
}
if (
typeof parsed?.choices?.[0]?.delta?.content === 'number'
|| typeof parsed?.choices?.[0]?.delta?.content === 'boolean'
) {
return String(parsed.choices[0].delta.content);
}
return '';
} catch {
return data;
}
}
function renderMarkdown(text: string) {
const html = marked.parse(text, { async: false }) as string;
return DOMPurify.sanitize(html);
}
function applyStreamChunk(raw: string, botMsg: Msg) {
let evt = 'message';
let data = '';
for (const line of raw.split('\n')) {
if (line.startsWith('event:')) evt = line.slice(6).trim();
else if (line.startsWith('data:')) {
data += (data ? '\n' : '') + line.slice(5).replace(/^ /, '');
}
}
if (evt === 'sources') {
botMsg.sources = JSON.parse(data);
scrollBottom();
} else if (evt === 'token' || evt === 'message') {
const chunk = extractStreamText(data);
if (chunk) {
botMsg.text += chunk;
}
scrollBottom();
} else if (evt === 'error') {
botMsg.role = 'err';
botMsg.text = `流式失败: ${data}`;
} else if (evt === 'done') {
botMsg.streaming = false;
scrollBottom();
}
}
async function send() {
const query = input.value.trim();
if (!query || sending.value) return;
messages.value.push({ role: 'user', text: query });
input.value = '';
sending.value = true;
const botMsg = reactive<Msg>({ role: 'bot', text: '', streaming: true });
messages.value.push(botMsg);
scrollBottom();
const token = accessStore.accessToken;
try {
const resp = await fetch('/api/knowledge/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: token ? `Bearer ${token}` : '',
},
body: JSON.stringify({ query, top_k: 30 }),
});
if (!resp.ok || !resp.body) {
throw new Error(`HTTP ${resp.status}: ${await resp.text()}`);
}
// SSE \n\n event: data:
const reader = resp.body.pipeThrough(new TextDecoderStream()).getReader();
let buf = '';
while (true) {
const { done, value } = await reader.read();
if (done) {
const tail = buf.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim();
if (tail) {
applyStreamChunk(tail, botMsg);
}
break;
}
buf += value.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
let idx;
while ((idx = buf.indexOf('\n\n')) >= 0) {
const raw = buf.slice(0, idx);
buf = buf.slice(idx + 2);
applyStreamChunk(raw, botMsg);
}
}
botMsg.streaming = false;
} catch (err: any) {
botMsg.role = 'err';
botMsg.text = `请求失败: ${err.message || err}`;
botMsg.streaming = false;
} finally {
sending.value = false;
scrollBottom();
}
}
function onKeydown(e: KeyboardEvent) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
send();
}
}
function fillQuestion(question: string) {
input.value = question;
}
</script>
<template>
<Page auto-content-height>
<div class="kb-chat-page">
<Card :bordered="false" class="kb-hero-card mb-5">
<div class="kb-hero">
<div class="kb-hero__content">
<div class="kb-hero__title">
<div class="kb-hero__icon">
<VbenIcon icon="solar:book-bookmark-bold" />
</div>
<div>
<div class="kb-hero__heading">配置表知识库问答</div>
<div class="kb-hero__subheading">
基于 BM25 检索与 DeepSeek 流式回答适合快速查询配置字段含义和上下游关系
</div>
</div>
</div>
<div class="kb-hero__tags">
<Tag color="blue" class="kb-hero__tag">
<VbenIcon icon="solar:database-bold" class="mr-1" />
配置检索
</Tag>
<Tag color="gold" class="kb-hero__tag">
<VbenIcon icon="solar:chat-round-dots-bold" class="mr-1" />
流式回答
</Tag>
<Tag color="green" class="kb-hero__tag">
<VbenIcon icon="solar:document-text-bold" class="mr-1" />
来源追踪
</Tag>
</div>
</div>
<div class="kb-hero__examples">
<div class="kb-hero__examples-title">试试这些问题</div>
<div class="kb-hero__examples-list">
<Button
v-for="question in quickQuestions"
:key="question"
class="kb-example-btn"
type="default"
@click="fillQuestion(question)"
>
{{ question }}
</Button>
</div>
</div>
</div>
</Card>
<Card :bordered="false" class="kb-chat-card">
<template #title>
<div class="kb-card-title">
<span class="kb-card-title__text">知识库对话</span>
<Tag color="processing" class="kb-card-title__tag">
{{ sending ? '回答中' : '就绪' }}
</Tag>
</div>
</template>
<div class="kb-chat-shell">
<div ref="logEl" class="kb-log">
<div v-if="messages.length === 0" class="kb-empty-state">
<div class="kb-empty-state__icon">
<VbenIcon icon="solar:chat-round-line-line-duotone" />
</div>
<div class="kb-empty-state__title">开始一次配置检索问答</div>
<div class="kb-empty-state__hint">
例如AdGiftData 表里有哪些字段
</div>
</div>
<div
v-for="(m, i) in messages"
:key="i"
:class="['kb-msg', `kb-msg-${m.role}`]"
>
<div class="kb-msg__meta">
<div :class="['kb-msg__avatar', `kb-msg__avatar-${m.role}`]">
<VbenIcon
:icon="
m.role === 'user'
? 'solar:user-bold'
: m.role === 'err'
? 'solar:danger-circle-bold'
: 'solar:stars-bold'
"
/>
</div>
<div class="kb-msg__role">
{{ m.role === 'user' ? '你' : m.role === 'err' ? '错误' : '助手' }}
</div>
</div>
<div :class="['kb-body', `kb-body-${m.role}`, { 'kb-cursor': m.streaming }]">
<template v-if="m.role === 'user'">
{{ m.text }}
</template>
<div v-else class="kb-markdown" v-html="renderMarkdown(m.text)"></div>
</div>
<details v-if="m.sources && m.sources.length" class="kb-sources">
<summary>
<span>来源片段</span>
<Tag color="default">{{ m.sources.length }}</Tag>
</summary>
<ol>
<li v-for="(s, j) in m.sources" :key="j" class="kb-sources__item">
<span class="kb-score">{{ s.score.toFixed(2) }}</span>
<span class="kb-cid">{{ s.chunk_id }}</span>
<span v-if="s.summary" class="kb-summary">
{{ s.summary.slice(0, 60) }}
</span>
</li>
</ol>
</details>
</div>
<div ref="logBottomEl" class="kb-log-bottom"></div>
</div>
<form class="kb-form" @submit.prevent="send">
<div class="kb-form__editor">
<textarea
v-model="input"
placeholder="输入问题Enter 发送Shift+Enter 换行"
:disabled="sending"
@keydown="onKeydown"
/>
</div>
<div class="kb-form__actions">
<Button
class="kb-send-btn"
type="primary"
:loading="sending"
:disabled="!input.trim()"
@click="send"
>
<template #icon>
<VbenIcon icon="solar:arrow-up-linear" />
</template>
发送
</Button>
</div>
</form>
</div>
</Card>
</div>
</Page>
</template>
<style scoped>
.kb-chat-page {
min-height: 100%;
}
.kb-hero-card,
.kb-chat-card {
overflow: hidden;
border-radius: 24px;
background: rgb(255 255 255 / 96%);
box-shadow: 0 16px 34px rgb(15 23 42 / 8%);
}
.kb-hero {
display: grid;
grid-template-columns: minmax(0, 1.4fr) minmax(280px, 0.9fr);
gap: 24px;
align-items: stretch;
}
.kb-hero__content {
display: flex;
flex-direction: column;
gap: 18px;
}
.kb-hero__title {
display: flex;
gap: 16px;
align-items: flex-start;
}
.kb-hero__icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 52px;
height: 52px;
border-radius: 18px;
background: linear-gradient(135deg, #fde68a 0%, #f59e0b 100%);
color: #78350f;
font-size: 24px;
box-shadow: 0 14px 28px rgb(245 158 11 / 22%);
}
.kb-hero__heading {
font-size: 24px;
font-weight: 700;
color: #0f172a;
line-height: 1.2;
}
.kb-hero__subheading {
margin-top: 8px;
color: #64748b;
line-height: 1.7;
}
.kb-hero__tags {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.kb-hero__tag {
display: inline-flex;
align-items: center;
border-radius: 999px;
padding-inline: 10px;
font-weight: 700;
}
.kb-hero__examples {
border: 1px solid rgb(191 219 254 / 70%);
border-radius: 22px;
background: linear-gradient(180deg, rgb(255 255 255 / 98%) 0%, rgb(239 246 255 / 96%) 100%);
padding: 20px;
}
.kb-hero__examples-title {
margin-bottom: 12px;
font-size: 14px;
font-weight: 700;
color: #1e3a8a;
}
.kb-hero__examples-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.kb-example-btn {
justify-content: flex-start;
min-height: 42px;
border-radius: 14px;
border-color: rgb(191 219 254 / 80%);
background: rgb(255 255 255 / 92%);
color: #1e293b;
font-weight: 600;
}
.kb-card-title {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.kb-card-title__text {
font-size: 16px;
font-weight: 700;
color: #0f172a;
}
.kb-card-title__tag {
border-radius: 999px;
font-weight: 700;
}
.kb-chat-shell {
display: flex;
flex-direction: column;
min-height: 680px;
}
.kb-log {
flex: 1;
overflow-y: auto;
padding: 8px 4px 16px;
}
.kb-empty-state {
display: flex;
min-height: 420px;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 40px 20px;
}
.kb-empty-state__icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 68px;
height: 68px;
border-radius: 22px;
background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
color: #2563eb;
font-size: 30px;
box-shadow: 0 16px 28px rgb(59 130 246 / 16%);
}
.kb-empty-state__title {
margin-top: 18px;
font-size: 20px;
font-weight: 700;
color: #0f172a;
}
.kb-empty-state__hint {
margin-top: 8px;
color: #64748b;
font-size: 14px;
}
.kb-msg {
margin-bottom: 20px;
}
.kb-msg__meta {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.kb-msg__avatar {
display: inline-flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
border-radius: 12px;
font-size: 16px;
}
.kb-msg__avatar-user {
background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
color: #1d4ed8;
}
.kb-msg__avatar-bot {
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
color: #b45309;
}
.kb-msg__avatar-err {
background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
color: #dc2626;
}
.kb-msg__role {
font-size: 13px;
font-weight: 700;
color: #64748b;
}
.kb-body {
padding: 14px 16px;
border-radius: 18px;
word-break: break-word;
border: 1px solid rgb(226 232 240 / 90%);
line-height: 1.75;
box-shadow: 0 10px 20px rgb(15 23 42 / 4%);
}
.kb-body-user {
background: linear-gradient(180deg, rgb(239 246 255 / 95%) 0%, rgb(219 234 254 / 96%) 100%);
border-color: rgb(147 197 253 / 80%);
color: #0f172a;
white-space: pre-wrap;
}
.kb-body-bot {
background: linear-gradient(180deg, rgb(255 255 255 / 98%) 0%, rgb(248 250 252 / 98%) 100%);
color: #1e293b;
}
.kb-body-err {
background: linear-gradient(180deg, rgb(254 242 242 / 98%) 0%, rgb(254 226 226 / 98%) 100%);
color: #b91c1c;
border-color: rgb(252 165 165 / 70%);
}
.kb-markdown :deep(*) {
max-width: 100%;
}
.kb-markdown :deep(> :first-child) {
margin-top: 0;
}
.kb-markdown :deep(> :last-child) {
margin-bottom: 0;
}
.kb-markdown :deep(p),
.kb-markdown :deep(ul),
.kb-markdown :deep(ol),
.kb-markdown :deep(pre),
.kb-markdown :deep(blockquote),
.kb-markdown :deep(table) {
margin: 0 0 12px;
}
.kb-markdown :deep(h1),
.kb-markdown :deep(h2),
.kb-markdown :deep(h3),
.kb-markdown :deep(h4) {
margin: 0 0 10px;
color: #0f172a;
font-weight: 700;
line-height: 1.4;
}
.kb-markdown :deep(h1) {
font-size: 22px;
}
.kb-markdown :deep(h2) {
font-size: 18px;
}
.kb-markdown :deep(h3) {
font-size: 16px;
}
.kb-markdown :deep(ul),
.kb-markdown :deep(ol) {
padding-left: 20px;
}
.kb-markdown :deep(li + li) {
margin-top: 4px;
}
.kb-markdown :deep(code) {
border-radius: 6px;
background: rgb(226 232 240 / 75%);
padding: 2px 6px;
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
font-size: 12px;
}
.kb-markdown :deep(pre) {
overflow-x: auto;
border: 1px solid rgb(226 232 240 / 90%);
border-radius: 14px;
background: #0f172a;
padding: 14px 16px;
color: #e2e8f0;
}
.kb-markdown :deep(pre code) {
background: transparent;
padding: 0;
color: inherit;
font-size: 12px;
}
.kb-markdown :deep(blockquote) {
border-left: 4px solid #93c5fd;
background: rgb(239 246 255 / 90%);
border-radius: 0 12px 12px 0;
padding: 10px 12px;
color: #334155;
}
.kb-markdown :deep(a) {
color: #2563eb;
text-decoration: underline;
}
.kb-markdown :deep(table) {
width: 100%;
border-collapse: collapse;
overflow: hidden;
border-radius: 12px;
}
.kb-markdown :deep(th),
.kb-markdown :deep(td) {
border: 1px solid rgb(226 232 240 / 90%);
padding: 8px 10px;
text-align: left;
vertical-align: top;
}
.kb-markdown :deep(th) {
background: rgb(241 245 249 / 98%);
color: #0f172a;
font-weight: 700;
}
.kb-cursor::after {
content: '▍';
color: #2563eb;
animation: kb-blink 1s steps(2) infinite;
}
@keyframes kb-blink {
50% {
opacity: 0;
}
}
.kb-sources {
margin-top: 8px;
font-size: 13px;
background: rgb(248 250 252 / 98%);
border: 1px solid rgb(226 232 240 / 90%);
border-radius: 14px;
}
.kb-sources summary {
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 10px 14px;
color: #64748b;
font-weight: 600;
user-select: none;
}
.kb-sources[open] summary {
border-bottom: 1px solid rgb(226 232 240 / 90%);
}
.kb-sources ol {
margin: 0;
padding: 10px 14px 14px 30px;
}
.kb-sources__item {
margin: 4px 0;
}
.kb-score {
display: inline-block;
min-width: 42px;
color: #2563eb;
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
font-weight: 700;
}
.kb-cid {
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
color: #334155;
}
.kb-summary {
color: #64748b;
margin-left: 6px;
}
.kb-form {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 16px;
align-items: end;
margin-top: auto;
padding-top: 18px;
border-top: 1px solid rgb(226 232 240 / 90%);
}
.kb-form__editor {
border: 1px solid rgb(226 232 240 / 90%);
border-radius: 20px;
background: linear-gradient(180deg, rgb(255 255 255 / 100%) 0%, rgb(248 250 252 / 100%) 100%);
padding: 12px 14px;
box-shadow: inset 0 1px 0 rgb(255 255 255 / 60%);
}
.kb-form textarea {
flex: 1;
resize: none;
width: 100%;
min-height: 86px;
max-height: 160px;
padding: 0;
border: none;
background: transparent;
color: #0f172a;
font: inherit;
outline: none;
line-height: 1.7;
}
.kb-form textarea:focus {
border-color: transparent;
}
.kb-form__actions {
display: flex;
min-width: 148px;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
}
:deep(.kb-send-btn) {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 120px;
height: 42px;
padding-inline: 18px;
border-radius: 14px;
font-weight: 700;
white-space: nowrap;
box-shadow: 0 12px 24px rgb(37 99 235 / 18%);
}
:deep(.kb-send-btn .ant-btn-icon) {
display: inline-flex;
align-items: center;
justify-content: center;
margin-inline-end: 6px;
line-height: 1;
}
:deep(.kb-send-btn .ant-btn-loading-icon) {
display: inline-flex;
align-items: center;
justify-content: center;
margin-inline-end: 6px;
}
:deep(.kb-send-btn > span) {
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 1;
}
@media (width <= 960px) {
.kb-hero {
grid-template-columns: 1fr;
}
.kb-chat-shell {
min-height: 600px;
}
.kb-form {
grid-template-columns: 1fr;
}
.kb-form__actions {
min-width: 0;
align-items: stretch;
}
.kb-card-title {
align-items: flex-start;
flex-direction: column;
}
}
@media (width <= 640px) {
.kb-hero__title {
flex-direction: column;
}
.kb-log {
padding-inline: 0;
}
.kb-body {
border-radius: 16px;
}
}
</style>

View File

@ -639,9 +639,15 @@ importers:
dayjs:
specifier: 'catalog:'
version: 1.11.13
dompurify:
specifier: ^3.4.2
version: 3.4.2
json-editor-vue:
specifier: ^0.18.1
version: 0.18.1(vue@3.5.13(typescript@5.7.2))
marked:
specifier: ^18.0.3
version: 18.0.3
pinia:
specifier: 2.2.2
version: 2.2.2(typescript@5.7.2)(vue@3.5.13(typescript@5.7.2))
@ -3608,8 +3614,8 @@ packages:
resolution: {integrity: sha512-bmsP4L2HqBF6i6uaMqJMcFBONVjKt+siGluRq4Ca4C0q7W2eMaVZr8iCgF9dKbcVXutftkC7D6z2SaSMmLiDyA==}
engines: {node: '>= 16'}
'@intlify/shared@11.3.0':
resolution: {integrity: sha512-LC6P/uay7rXL5zZ5+5iRJfLs/iUN8apu9tm8YqQVmW3Uq3X4A0dOFUIDuAmB7gAC29wTHOS3EiN/IosNSz0eNQ==}
'@intlify/shared@11.4.2':
resolution: {integrity: sha512-NzpHbguRCsOHDwxmlBa9qu/imc+/QWgsYUaK6FZeNC0wK8QfAbhqrktEp/haVzxU1aikH8IX4ytD+mfFEMi/9A==}
engines: {node: '>= 16'}
'@intlify/shared@12.0.0-alpha.3':
@ -6206,6 +6212,9 @@ packages:
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
engines: {node: '>= 4'}
dompurify@3.4.2:
resolution: {integrity: sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==}
domutils@2.8.0:
resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
@ -7936,6 +7945,11 @@ packages:
mark.js@8.11.1:
resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==}
marked@18.0.3:
resolution: {integrity: sha512-7VT90JOkDeaRWpfjOReRGPEKn0ecdARBkDGL+tT1wZY0efPPqkUxLUSmzy/C7TIylQYJC9STISEsCHrqb/7VIA==}
engines: {node: '>= 20'}
hasBin: true
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
@ -13089,7 +13103,7 @@ snapshots:
'@intlify/shared@10.0.5': {}
'@intlify/shared@11.3.0': {}
'@intlify/shared@11.4.2': {}
'@intlify/shared@12.0.0-alpha.3': {}
@ -13097,8 +13111,8 @@ snapshots:
dependencies:
'@eslint-community/eslint-utils': 4.4.1(eslint@9.16.0(jiti@2.4.0))
'@intlify/bundle-utils': 10.0.0(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))
'@intlify/shared': 11.3.0
'@intlify/vue-i18n-extensions': 7.0.0(@intlify/shared@11.3.0)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))
'@intlify/shared': 11.4.2
'@intlify/vue-i18n-extensions': 7.0.0(@intlify/shared@11.4.2)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))
'@rollup/pluginutils': 5.1.3(rollup@4.28.0)
'@typescript-eslint/scope-manager': 8.17.0
'@typescript-eslint/typescript-estree': 8.17.0(typescript@5.7.2)
@ -13120,11 +13134,11 @@ snapshots:
- supports-color
- typescript
'@intlify/vue-i18n-extensions@7.0.0(@intlify/shared@11.3.0)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))':
'@intlify/vue-i18n-extensions@7.0.0(@intlify/shared@11.4.2)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))':
dependencies:
'@babel/parser': 7.26.2
optionalDependencies:
'@intlify/shared': 11.3.0
'@intlify/shared': 11.4.2
'@vue/compiler-dom': 3.5.13
vue: 3.5.13(typescript@5.7.2)
vue-i18n: 10.0.5(vue@3.5.13(typescript@5.7.2))
@ -16104,6 +16118,10 @@ snapshots:
dependencies:
domelementtype: 2.3.0
dompurify@3.4.2:
optionalDependencies:
'@types/trusted-types': 2.0.7
domutils@2.8.0:
dependencies:
dom-serializer: 1.4.1
@ -18039,6 +18057,8 @@ snapshots:
mark.js@8.11.1: {}
marked@18.0.3: {}
math-intrinsics@1.1.0: {}
mathml-tag-names@2.1.3: {}