Add CodeGraph config; fix analytics, chat, UI
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-28 14:39:09 +08:00
parent fc9455cfce
commit e575bac340
8 changed files with 297 additions and 36 deletions

16
.codegraph/.gitignore vendored Normal file
View File

@ -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

143
.codegraph/config.json Normal file
View File

@ -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
}

2
.gitignore vendored
View File

@ -50,3 +50,5 @@ vite.config.ts.*
*.sw?
.history
node-compile-cache/*
.github/*
.agents/*

View File

@ -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',

View File

@ -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<AnalysisOverviewItem[]>([
{
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<AnalysisOverviewItem[]>([
},
{
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;
}
}

View File

@ -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) {
</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 class="kb-markdown" v-html="renderMarkdown(m.text)"></div>
</div>
<details v-if="m.sources && m.sources.length" class="kb-sources">
<summary>

View File

@ -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] {
<Tabs.TabPane key="1" tab="玩家信息">
<div class="player-detail-hero">
<UserHeader :avatar="info.Face" :ban="info.Ban">
<template #nick_name> nick_name: {{ info.Name || 'N/A' }} </template>
<template #user_name> user_name: {{ info.Mac }} </template>
<template #nick_name> user_name: {{ info.Name || 'N/A' }} </template>
<template #user_name> Mac: {{ info.Mac }} </template>
<template #uid> uid: {{ info.Uid }} </template>
<template #level>{{ info.Level }}</template>
<template #star>{{ info.Star }}</template>

View File

@ -35,7 +35,7 @@ withDefaults(defineProps<Props>(), {
<CardContent class="flex items-center justify-between">
<VbenCountToAnimator
:end-val="item.value"
:start-val="1"
:start-val="0"
class="text-xl"
prefix=""
:decimals=item.decimals
@ -46,7 +46,7 @@ withDefaults(defineProps<Props>(), {
<span>{{ item.totalTitle }}</span>
<VbenCountToAnimator
:end-val="item.totalValue"
:start-val="1"
:start-val="0"
prefix=""
:decimals=item.decimals
/>