From e575bac340a9077d4e6f0656e12d096b18c3cdde Mon Sep 17 00:00:00 2001 From: hahwu <31872165+hahwu@users.noreply.github.com> Date: Thu, 28 May 2026 14:39:09 +0800 Subject: [PATCH] Add CodeGraph config; fix analytics, chat, UI --- .codegraph/.gitignore | 16 ++ .codegraph/config.json | 143 ++++++++++++++++++ .gitignore | 2 + .../src/router/routes/modules/operation.ts | 22 +-- .../src/views/dashboard/analytics/index.vue | 16 +- apps/web-antd/src/views/knowledge/chat.vue | 124 +++++++++++++-- .../src/views/userlog/userlist/user.vue | 6 +- .../dashboard/analysis/analysis-overview.vue | 4 +- 8 files changed, 297 insertions(+), 36 deletions(-) create mode 100644 .codegraph/.gitignore create mode 100644 .codegraph/config.json diff --git a/.codegraph/.gitignore b/.codegraph/.gitignore new file mode 100644 index 0000000..9de0f16 --- /dev/null +++ b/.codegraph/.gitignore @@ -0,0 +1,16 @@ +# CodeGraph data files +# These are local to each machine and should not be committed + +# Database +*.db +*.db-wal +*.db-shm + +# Cache +cache/ + +# Logs +*.log + +# Hook markers +.dirty diff --git a/.codegraph/config.json b/.codegraph/config.json new file mode 100644 index 0000000..7af60ad --- /dev/null +++ b/.codegraph/config.json @@ -0,0 +1,143 @@ +{ + "version": 1, + "include": [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx", + "**/*.py", + "**/*.go", + "**/*.rs", + "**/*.java", + "**/*.c", + "**/*.h", + "**/*.cpp", + "**/*.hpp", + "**/*.cc", + "**/*.cxx", + "**/*.cs", + "**/*.php", + "**/*.rb", + "**/*.swift", + "**/*.kt", + "**/*.kts", + "**/*.dart", + "**/*.svelte", + "**/*.vue", + "**/*.liquid", + "**/*.pas", + "**/*.dpr", + "**/*.dpk", + "**/*.lpr", + "**/*.dfm", + "**/*.fmx", + "**/*.scala", + "**/*.sc" + ], + "exclude": [ + "**/.git/**", + "**/node_modules/**", + "**/vendor/**", + "**/Pods/**", + "**/dist/**", + "**/build/**", + "**/out/**", + "**/bin/**", + "**/obj/**", + "**/target/**", + "**/*.min.js", + "**/*.bundle.js", + "**/.next/**", + "**/.nuxt/**", + "**/.svelte-kit/**", + "**/.output/**", + "**/.turbo/**", + "**/.cache/**", + "**/.parcel-cache/**", + "**/.vite/**", + "**/.astro/**", + "**/.docusaurus/**", + "**/.gatsby/**", + "**/.webpack/**", + "**/.nx/**", + "**/.yarn/cache/**", + "**/.pnpm-store/**", + "**/storybook-static/**", + "**/.expo/**", + "**/web-build/**", + "**/ios/Pods/**", + "**/ios/build/**", + "**/android/build/**", + "**/android/.gradle/**", + "**/__pycache__/**", + "**/.venv/**", + "**/venv/**", + "**/site-packages/**", + "**/dist-packages/**", + "**/.pytest_cache/**", + "**/.mypy_cache/**", + "**/.ruff_cache/**", + "**/.tox/**", + "**/.nox/**", + "**/*.egg-info/**", + "**/.eggs/**", + "**/go/pkg/mod/**", + "**/target/debug/**", + "**/target/release/**", + "**/.gradle/**", + "**/.m2/**", + "**/generated-sources/**", + "**/.kotlin/**", + "**/.dart_tool/**", + "**/.vs/**", + "**/.nuget/**", + "**/artifacts/**", + "**/publish/**", + "**/cmake-build-*/**", + "**/CMakeFiles/**", + "**/bazel-*/**", + "**/vcpkg_installed/**", + "**/.conan/**", + "**/Debug/**", + "**/Release/**", + "**/x64/**", + "**/.pio/**", + "**/release/**", + "**/*.app/**", + "**/*.asar", + "**/DerivedData/**", + "**/.build/**", + "**/.swiftpm/**", + "**/xcuserdata/**", + "**/Carthage/Build/**", + "**/SourcePackages/**", + "**/__history/**", + "**/__recovery/**", + "**/*.dcu", + "**/.composer/**", + "**/storage/framework/**", + "**/bootstrap/cache/**", + "**/.bundle/**", + "**/tmp/cache/**", + "**/public/assets/**", + "**/public/packs/**", + "**/.yardoc/**", + "**/coverage/**", + "**/htmlcov/**", + "**/.nyc_output/**", + "**/test-results/**", + "**/.coverage/**", + "**/.idea/**", + "**/logs/**", + "**/tmp/**", + "**/temp/**", + "**/_build/**", + "**/docs/_build/**", + "**/site/**" + ], + "languages": [], + "frameworks": [], + "maxFileSize": 1048576, + "extractDocstrings": true, + "trackCallSites": true +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 79b8df9..9dd3de1 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,5 @@ vite.config.ts.* *.sw? .history node-compile-cache/* +.github/* +.agents/* \ No newline at end of file diff --git a/apps/web-antd/src/router/routes/modules/operation.ts b/apps/web-antd/src/router/routes/modules/operation.ts index 8534458..1e7e767 100644 --- a/apps/web-antd/src/router/routes/modules/operation.ts +++ b/apps/web-antd/src/router/routes/modules/operation.ts @@ -59,17 +59,17 @@ const routes: RouteRecordRaw[] = [ authority: ['super', 'AC8001'], }, }, - { - name: 'Order', - path: '/order', - component: () => import('#/views/operation/order/index.vue'), - meta: { - affixTab: true, - icon: 'lets-icons:order', - title: $t('page.operation.order'), - authority: ['super', 'AC5002'], - }, - }, + // { + // name: 'Order', + // path: '/order', + // component: () => import('#/views/operation/order/index.vue'), + // meta: { + // affixTab: true, + // icon: 'lets-icons:order', + // title: $t('page.operation.order'), + // authority: ['super', 'AC5002'], + // }, + // }, { name: 'Activity', path: '/activity', diff --git a/apps/web-antd/src/views/dashboard/analytics/index.vue b/apps/web-antd/src/views/dashboard/analytics/index.vue index 35b837c..5b3158e 100644 --- a/apps/web-antd/src/views/dashboard/analytics/index.vue +++ b/apps/web-antd/src/views/dashboard/analytics/index.vue @@ -4,31 +4,27 @@ import type { AnalysisOverviewItem } from '@vben/common-ui'; import type { TabOption } from '@vben/types'; import { getstatisticsInfo } from '#/api/core/statistics'; import { - AnalysisChartCard, AnalysisChartsTabs, AnalysisOverview, } from '@vben/common-ui'; import AnalyticsTrends from './analytics-trends.vue'; import AnalyticsVisits from './analytics-visits.vue'; -import AnalyticsVisitsData from './analytics-visits-data.vue'; -import AnalyticsVisitsSales from './analytics-visits-sales.vue'; -import AnalyticsVisitsSource from './analytics-visits-source.vue'; const overviewItems = ref([ { icon: 'lets-icons:user', title: '今日注册', totalTitle: '总用户量', - totalValue: 100, - value: 100, + totalValue: 0, + value: 0, }, { icon: 'icon-park-solid:paper-money', title: '今日充值', totalTitle: '总充值', - totalValue: 100, - value: 100, + totalValue: 0, + value: 0, decimals: 2, }, { @@ -40,7 +36,7 @@ const overviewItems = ref([ }, { icon: 'iconamoon:sign-percent', - title: '次留', + title: '昨日次留', totalTitle: 'ARPU', totalValue: 31.02, value: 0.12, @@ -66,7 +62,7 @@ onMounted(async () => { } if (overviewItems.value[3]) { overviewItems.value[3].value = data.remain; - overviewItems.value[3].totalValue = data.totalRecharge / data.totalRegister; + overviewItems.value[3].totalValue = data.totalRegister > 0 ? data.totalRecharge / data.totalRegister : 0; } } diff --git a/apps/web-antd/src/views/knowledge/chat.vue b/apps/web-antd/src/views/knowledge/chat.vue index 372b25e..086c24d 100644 --- a/apps/web-antd/src/views/knowledge/chat.vue +++ b/apps/web-antd/src/views/knowledge/chat.vue @@ -38,6 +38,111 @@ marked.setOptions({ gfm: true, }); +function normalizeMarkdownText(text: string) { + if (!text) { + return ''; + } + + let normalized = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); + + // 流式接口有时会把 markdown 代码块中的换行和缩进转成转义文本, + // 这里在渲染前还原,避免 ```go 仍按普通文本显示。 + const shouldDecodeEscapes = + (!normalized.includes('\n') && /\\[nrt]/.test(normalized)) + || /```[\w-]*\\n/.test(normalized) + || /\\n```/.test(normalized) + || /\\n {2,}\S/.test(normalized); + + if (shouldDecodeEscapes) { + normalized = normalized + .replace(/\\r\\n/g, '\n') + .replace(/\\n/g, '\n') + .replace(/\\r/g, '\n') + .replace(/\\t/g, ' '); + } + + return normalized.replace(/\u200b/g, ''); +} + +function looksLikeCompressedGoSnippet(text: string) { + const compactText = text.trim(); + if (!compactText || compactText.includes('```') || compactText.includes('\n')) { + return false; + } + + const startsWithGoLabel = /^(go|golang)(?=\/\/|\/\*|package\s|import\s|func\s|type\s|const\s|var\s)/i.test(compactText); + const hasGoStructure = + /func\s+[\w]+\s*\(/.test(compactText) + || /if\s+.+\{/.test(compactText) + || /return\s+/.test(compactText) + || /err\s*:=/.test(compactText); + + return startsWithGoLabel || (hasGoStructure && compactText.includes('{') && compactText.includes('}')); +} + +function formatCompressedGoSnippet(text: string) { + let normalized = text.trim().replace(/^golang/i, 'go').replace(/^go(?=\/\/|\/\*|package\s|import\s|func\s|type\s|const\s|var\s)/i, ''); + + normalized = normalized + .replace(/interface\{\}/g, 'interface__EMPTY_BLOCK__') + .replace(/(&?[\w.[\]]+)\{/g, '$1__COMPOSITE_OPEN__'); + + normalized = normalized + .replace(/(\/\/[^\n]*?)(func\s+)/g, '$1\n$2') + .replace(/__COMPOSITE_OPEN__\s+(?=(?:"[^"]+"|[A-Za-z_][\w]*)\s*:)/g, '__COMPOSITE_OPEN__\n') + .replace(/,\s+(?=(?:"[^"]+"|[A-Za-z_][\w]*)\s*:)/g, ',\n') + .replace(/\{\s*/g, '{\n') + .replace(/\s*\}/g, '\n}') + .replace(/;\s*/g, ';\n') + .replace(/\)\s*(if|for|switch|return|var|const|type|func)\b/g, ')\n$1') + .replace(/\}\s*(else\b)/g, '}\n$1') + .replace(/\}\s*(if|for|switch|return|var|const|type|func)\b/g, '}\n$1') + .replace(/\s+(if\s+[^\n{}]+\{)/g, '\n$1') + .replace(/\s+(for\s+[^\n{}]+\{)/g, '\n$1') + .replace(/\s+(switch\s+[^\n{}]+\{)/g, '\n$1') + .replace(/\s+(return\b)/g, '\n$1') + .replace(/\s+(defer\b)/g, '\n$1') + .replace(/\s+(itemList\s*:=)/g, '\n$1') + .replace(/\s+(err\s*:=)/g, '\n$1') + .replace(/\s+(err\s*=)/g, '\n$1') + .replace(/\s+(player\.(?:SendErrClienRes|PushClientRes|PlayMod\.save|TeLog)\()/g, '\n$1') + .replace(/\n{3,}/g, '\n\n'); + + const lines = normalized + .split('\n') + .map((line) => line.trim()) + .filter(Boolean); + + let indentLevel = 0; + const formattedLines = lines.map((line) => { + const restoredLine = line + .replace(/__EMPTY_BLOCK__/g, '{}') + .replace(/__COMPOSITE_OPEN__/g, '{'); + + if (restoredLine.startsWith('}')) { + indentLevel = Math.max(indentLevel - 1, 0); + } + + const formattedLine = `${' '.repeat(indentLevel)}${restoredLine}`; + + if (restoredLine.endsWith('{')) { + indentLevel += 1; + } + + return formattedLine; + }); + + return `\n\n\`\`\`go\n${formattedLines.join('\n')}\n\`\`\``; +} + +function optimizeDisplayMarkdown(text: string) { + const normalized = normalizeMarkdownText(text); + if (looksLikeCompressedGoSnippet(normalized)) { + return formatCompressedGoSnippet(normalized); + } + return normalized; +} + function scrollBottom() { nextTick(() => { requestAnimationFrame(() => { @@ -75,7 +180,7 @@ onBeforeUnmount(() => { }); function extractStreamText(data: string) { - if (!data || data === '[DONE]') { + if (data === '[DONE]') { return ''; } @@ -133,27 +238,29 @@ function extractStreamText(data: string) { } function renderMarkdown(text: string) { - const html = marked.parse(text, { async: false }) as string; + const html = marked.parse(optimizeDisplayMarkdown(text), { async: false }) as string; return DOMPurify.sanitize(html); } function applyStreamChunk(raw: string, botMsg: Msg) { let evt = 'message'; - let data = ''; + const dataLines: string[] = []; 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(/^ /, ''); + dataLines.push(line.slice(5).replace(/^ /, '')); } } + const data = dataLines.join('\n'); + if (evt === 'sources') { botMsg.sources = JSON.parse(data); scrollBottom(); } else if (evt === 'token' || evt === 'message') { const chunk = extractStreamText(data); - if (chunk) { + if (chunk || dataLines.length > 0) { botMsg.text += chunk; } scrollBottom(); @@ -199,7 +306,7 @@ async function send() { while (true) { const { done, value } = await reader.read(); if (done) { - const tail = buf.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim(); + const tail = buf.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); if (tail) { applyStreamChunk(tail, botMsg); } @@ -328,10 +435,7 @@ function fillQuestion(question: string) {
- -
+
diff --git a/apps/web-antd/src/views/userlog/userlist/user.vue b/apps/web-antd/src/views/userlog/userlist/user.vue index a465815..a06002d 100644 --- a/apps/web-antd/src/views/userlog/userlist/user.vue +++ b/apps/web-antd/src/views/userlog/userlist/user.vue @@ -26,7 +26,7 @@ import orderTable from './order-table.vue'; // url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转 // 例如:url: /dashboard/workspace const projectItems: WorkbenchProjectItem[] = []; -const gmPermissions:boolean = useAccess().hasAccessByRoles(['super']) || useAccess().hasAccessByRoles(['AC9301']); +const gmPermissions:boolean = useAccess().hasAccessByRoles(['super']) || useAccess().hasAccessByCodes(['AC3003']); const chargeDisplay = computed(() => (Number(info.value?.Charge ?? 0)).toFixed(2)); const chessBufferCount = computed(() => info.value.ChessBuffer.length); const banStatusText = computed(() => { @@ -502,8 +502,8 @@ function formatActLog(type: number, content = ''): [string, string] {
- - + + diff --git a/packages/effects/common-ui/src/ui/dashboard/analysis/analysis-overview.vue b/packages/effects/common-ui/src/ui/dashboard/analysis/analysis-overview.vue index 6a1fdef..3e88a40 100644 --- a/packages/effects/common-ui/src/ui/dashboard/analysis/analysis-overview.vue +++ b/packages/effects/common-ui/src/ui/dashboard/analysis/analysis-overview.vue @@ -35,7 +35,7 @@ withDefaults(defineProps(), { (), { {{ item.totalTitle }}