import { ArrowDown, ArrowUp, ArrowUpDown, ChevronLeft, ChevronRight, Eye, RefreshCcw, Search, X, } from 'lucide-react'; import { FormEvent, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react'; import { getAdminDatabaseTableRows, getAdminDatabaseTables, } from '../api/adminApiClient'; import type { AdminDatabaseTableRowPayload, AdminDatabaseTableRowsResponse, } from '../api/adminApiTypes'; import { AdminUserReferenceButton } from '../components/AdminUserReferenceButton'; import { handlePageError } from './pageUtils'; interface AdminDatabaseTablesPageProps { token: string; onUnauthorized: (message?: string) => void; } type SortDirection = 'asc' | 'desc'; export function AdminDatabaseTablesPage({ token, onUnauthorized, }: AdminDatabaseTablesPageProps) { const pageRef = useRef(null); const appliedQueryRef = useRef({ search: '', filters: '', limit: '100', sortColumn: '', sortDirection: 'asc' as SortDirection, }); const [tables, setTables] = useState([]); const [tableName, setTableName] = useState(() => readHashTableName()); const [search, setSearch] = useState(''); const [filters, setFilters] = useState(''); const [limit, setLimit] = useState('100'); const [result, setResult] = useState( null, ); const [detailRow, setDetailRow] = useState(null); const [errorMessage, setErrorMessage] = useState(''); const [copyMessage, setCopyMessage] = useState(''); const [sortColumn, setSortColumn] = useState(''); const [sortDirection, setSortDirection] = useState('asc'); const [isLoadingTables, setIsLoadingTables] = useState(false); const [isLoadingRows, setIsLoadingRows] = useState(false); useEffect(() => { void loadTables(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [token]); useEffect(() => { const nextTableName = readHashTableName(); if (nextTableName) { setTableName(nextTableName); } const handleHashChange = () => { const tableFromHash = readHashTableName(); if (tableFromHash) { setTableName(tableFromHash); } }; window.addEventListener('hashchange', handleHashChange); return () => window.removeEventListener('hashchange', handleHashChange); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { if (tables.length && !tableName) { setTableName(tables[0] ?? ''); } }, [tableName, tables]); useEffect(() => { if (tableName) { void refreshRows(tableName); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [tableName]); useLayoutEffect(() => { const pageElement = pageRef.current; if (!pageElement) { return; } const bottomNav = document.querySelector('.admin-bottom-nav'); const pagination = pageElement.querySelector( '.admin-database-pagination', ); const updateFixedBarHeights = () => { pageElement.style.setProperty( '--admin-bottom-nav-height', `${bottomNav?.getBoundingClientRect().height ?? 0}px`, ); pageElement.style.setProperty( '--admin-database-pagination-height', `${pagination?.getBoundingClientRect().height ?? 0}px`, ); }; updateFixedBarHeights(); window.addEventListener('resize', updateFixedBarHeights); const resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(updateFixedBarHeights) : null; if (resizeObserver) { if (bottomNav) { resizeObserver.observe(bottomNav); } if (pagination) { resizeObserver.observe(pagination); } } return () => { resizeObserver?.disconnect(); window.removeEventListener('resize', updateFixedBarHeights); }; }, [result]); const visibleColumns = useMemo(() => { const columns = result?.columns ?? []; if (columns.length) { return columns; } const firstRow = result?.rows[0]; return firstRow ? Object.keys(firstRow.cells) : []; }, [result]); const tableOptions = useMemo(() => { const optionNames = tableName && !tables.includes(tableName) ? [tableName, ...tables] : tables; return optionNames.map(getDatabaseTableHeader); }, [tableName, tables]); const selectedTableHeader = useMemo( () => getDatabaseTableHeader(tableName), [tableName], ); const resultTableHeader = useMemo( () => getDatabaseTableHeader(result?.tableName || tableName), [result?.tableName, tableName], ); const columnHeaders = useMemo( () => visibleColumns.map((column) => getDatabaseTableColumnHeader(tableName, column), ), [tableName, visibleColumns], ); const currentPage = result?.page ?? 1; const totalMatched = result?.totalMatched ?? result?.totalReturned ?? 0; const totalPages = result ? Math.max(1, Math.ceil(totalMatched / Math.max(1, result.limit))) : 1; async function loadTables() { setIsLoadingTables(true); setErrorMessage(''); try { const response = await getAdminDatabaseTables(token); setTables(response.tables); if (response.fetchErrors.length) { setErrorMessage(response.fetchErrors.join(';')); } } catch (error: unknown) { handlePageError(error, onUnauthorized, setErrorMessage); } finally { setIsLoadingTables(false); } } async function refreshRows( nextTableName = tableName, options: { search?: string; filters?: string; limit?: string; page?: number; sortColumn?: string; sortDirection?: SortDirection; } = {}, ) { const normalizedTableName = nextTableName.trim(); if (!normalizedTableName) { return; } const querySearch = options.search ?? search; const queryFilters = options.filters ?? filters; const queryLimit = options.limit ?? limit; const querySortColumn = options.sortColumn ?? sortColumn; const querySortDirection = options.sortDirection ?? sortDirection; setIsLoadingRows(true); setErrorMessage(''); try { const response = await getAdminDatabaseTableRows( token, normalizedTableName, { search: querySearch, filters: queryFilters, limit: parseLimit(queryLimit), page: options.page ?? 1, sortColumn: querySortColumn || undefined, sortDirection: querySortColumn ? querySortDirection : undefined, }, ); appliedQueryRef.current = { search: querySearch, filters: queryFilters, limit: queryLimit, sortColumn: querySortColumn, sortDirection: querySortDirection, }; setSortColumn(querySortColumn); setSortDirection(querySortDirection); setResult(response); setCopyMessage(''); } catch (error: unknown) { handlePageError(error, onUnauthorized, setErrorMessage); } finally { setIsLoadingRows(false); } } function handleSearch(event: FormEvent) { event.preventDefault(); void refreshRows(tableName, { page: 1 }); } function handleTableChange(nextTableName: string) { setSortColumn(''); setSortDirection('asc'); setTableName(nextTableName); const nextHash = `#tables?table=${encodeURIComponent(nextTableName)}`; if (window.location.hash !== nextHash) { window.location.hash = nextHash; } } function handleResetQuery() { setSearch(''); setFilters(''); setLimit('100'); setSortColumn(''); setSortDirection('asc'); void refreshRows(tableName, { search: '', filters: '', limit: '100', page: 1, sortColumn: '', sortDirection: 'asc', }); } function handlePageChange(page: number) { void refreshRows(tableName, { ...appliedQueryRef.current, page }); } function handleSortColumn(column: string) { const nextDirection = sortColumn === column && sortDirection === 'asc' ? 'desc' : 'asc'; void refreshRows(tableName, { ...appliedQueryRef.current, page: 1, sortColumn: column, sortDirection: nextDirection, }); } async function handleCopyDetailJson() { if (!detailRow) { return; } const copiedText = JSON.stringify( detailRow.raw ?? detailRow.cells, null, 2, ); try { await navigator.clipboard.writeText(copiedText); setCopyMessage('已复制 JSON'); } catch { setCopyMessage('复制失败,请手动选中后复制'); } } return (

表查询

SpacetimeDB 行数据

已选表:{selectedTableHeader.optionLabel} 显示列:{visibleColumns.length}
{errorMessage ? (
{errorMessage}
) : null} {result?.scanLimitReached ? (
当前表超过 {result.scanLimit} 条扫描与浏览上限,本次已扫描{' '} {result.scannedCount} 条;当前分页结果和匹配总数可能不完整。
) : null}

{resultTableHeader.label}

匹配 {totalMatched} 条,本页 {result?.totalReturned ?? 0} 条
{columnHeaders.map(({ column, label, description }) => { const isSorted = sortColumn === column; return ( ); })} {result?.rows.length ? ( result.rows.map((row, rowIndex) => ( setDetailRow(row)} > {visibleColumns.map((column) => { const cellValue = formatCellValue( row.cells[column], column, ); const userReference = resolveAdminDatabaseUserReference( result.tableName, column, row.cells[column], ); return ( ); })} )) ) : ( )}
详情
{cellValue.content} {userReference ? ( ) : null}
暂无数据
{result ? ( ) : null} {detailRow ? (

行详情

{copyMessage ? (
{copyMessage}
) : null}
              {JSON.stringify(detailRow.raw ?? detailRow.cells, null, 2)}
            
) : null}
); } function readHashTableName() { const hash = window.location.hash; const queryIndex = hash.indexOf('?'); if (queryIndex < 0) { return ''; } return ( new URLSearchParams(hash.slice(queryIndex + 1)).get('table')?.trim() ?? '' ); } function parseLimit(value: string) { const parsed = Number.parseInt(value.trim(), 10); return Number.isFinite(parsed) ? parsed : 100; } function getDatabaseTableHeader(tableName: string): DatabaseTableHeader { const normalizedName = tableName.trim(); if (!normalizedName) { return { description: '尚未选择数据库表。', label: '数据行', name: '', optionLabel: '-', }; } const label = getDatabaseTableLabel(normalizedName); const description = getDatabaseTableDescription(normalizedName, label); return { description: `原始表名:${normalizedName}。${description}。`, label, name: normalizedName, optionLabel: `${label}(${normalizedName})`, }; } function getDatabaseTableLabel(tableName: string) { const exactLabel = databaseTableLabelMap[tableName]; if (exactLabel) { return exactLabel; } const segments = tableName.split('_').filter(Boolean); if (segments.length) { return segments.map(formatDatabaseTableColumnSegment).join(''); } return tableName; } function getDatabaseTableDescription(tableName: string, label: string) { return ( databaseTableDescriptionMap[tableName] ?? `当前 SpacetimeDB 中的 ${label} 表` ); } function getDatabaseTableColumnHeader(tableName: string, column: string) { const normalizedColumn = column.trim(); const label = getDatabaseTableColumnLabel(normalizedColumn); const description = getDatabaseTableColumnDescription( tableName, normalizedColumn, label, ); return { column: normalizedColumn, label, description }; } function getDatabaseTableColumnLabel(column: string) { const normalizedColumn = column.trim(); const exactLabel = databaseTableColumnLabelMap[normalizedColumn]; if (exactLabel) { return exactLabel; } const segments = normalizedColumn.split('_').filter(Boolean); if (segments.length) { return segments.map(formatDatabaseTableColumnSegment).join(''); } return '字段'; } function formatDatabaseTableColumnSegment(segment: string) { return databaseTableColumnSegmentLabelMap[segment] ?? segment; } function getDatabaseTableColumnDescription( tableName: string, column: string, label: string, ) { const exactDescription = databaseTableColumnDescriptionMap[column]; const description = exactDescription ?? `当前表 ${tableName || '未知'} 中的 ${label} 字段`; return `原始字段名:${column}。${description}。点击可按此列排序。`; } function buildRowKey(row: AdminDatabaseTableRowPayload, rowIndex: number) { const firstValue = Object.values(row.cells)[0]; return `${rowIndex}-${String(firstValue ?? '')}`; } export function resolveAdminDatabaseUserReference( tableName: string, column: string, value: unknown, ) { if (typeof value !== 'string' && typeof value !== 'number') { return null; } const normalizedValue = String(value).trim(); const normalizedColumn = column.trim().toLowerCase(); const normalizedTable = tableName.trim().toLowerCase(); if (!normalizedValue || normalizedValue.toLowerCase().startsWith('admin:')) { return null; } if ( normalizedColumn === 'operator_user_id' || normalizedColumn.includes('admin_user_id') || (normalizedTable === 'profile_invite_code' && normalizedColumn === 'user_id') ) { return null; } if ( normalizedColumn === 'public_user_code' || normalizedColumn.endsWith('_public_user_code') ) { return {publicUserCode: normalizedValue}; } if ( normalizedColumn === 'user_id' || normalizedColumn.endsWith('_user_id') ) { return {userId: normalizedValue}; } return null; } function formatCellValue(value: unknown, column = ''): FormattedTableCellValue { if (value === null || typeof value === 'undefined' || value === '') { return { content: '-', fullText: '-' }; } if ( typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' ) { const text = String(value); const readableTimestamp = formatReadableTimestampValue(value, column); if (readableTimestamp) { return { content: readableTimestamp, fullText: `${readableTimestamp}(原始值:${text})`, }; } return { content: text, fullText: text }; } return { content: stringifyUnknownValue(value), fullText: stringifyPrettyUnknownValue(value), }; } function formatReadableTimestampValue( value: string | number | boolean, column: string, ) { if (typeof value === 'boolean' || !isTimestampColumn(column)) { return ''; } const timestampMs = parseTimestampMillis(value, column); if (timestampMs === null) { return ''; } const date = new Date(timestampMs); if (Number.isNaN(date.getTime())) { return ''; } return formatBeijingDateTime(date); } function isTimestampColumn(column: string) { const normalizedColumn = column.trim().toLowerCase(); return ( normalizedColumn.endsWith('_at') || normalizedColumn.endsWith('_at_ms') || normalizedColumn.endsWith('_at_micros') || normalizedColumn.endsWith('_timestamp') || normalizedColumn.endsWith('_timestamp_ms') || normalizedColumn.endsWith('_timestamp_micros') ); } function parseTimestampMillis(value: string | number, column: string) { if (typeof value === 'number') { return parseNumericTimestampMillis(value, column); } const trimmed = value.trim(); if (!trimmed) { return null; } const numericValue = Number(trimmed); if (Number.isFinite(numericValue) && /^-?\d+(\.\d+)?$/.test(trimmed)) { return parseNumericTimestampMillis(numericValue, column); } const parsed = Date.parse(trimmed); return Number.isNaN(parsed) ? null : parsed; } function parseNumericTimestampMillis(value: number, column: string) { if (!Number.isFinite(value) || value <= 0) { return null; } const normalizedColumn = column.trim().toLowerCase(); if ( normalizedColumn.endsWith('_at_ms') || normalizedColumn.endsWith('_timestamp_ms') ) { return value; } if ( normalizedColumn.endsWith('_at_micros') || normalizedColumn.endsWith('_timestamp_micros') ) { return Math.floor(value / 1_000); } if (value >= 1_000_000_000_000_000) { return Math.floor(value / 1_000); } if (value >= 1_000_000_000_000) { return value; } if (value >= 1_000_000_000) { return value * 1_000; } return null; } function formatBeijingDateTime(date: Date) { const parts = new Intl.DateTimeFormat('zh-CN', { day: '2-digit', hour: '2-digit', hourCycle: 'h23', minute: '2-digit', month: '2-digit', second: '2-digit', timeZone: 'Asia/Shanghai', year: 'numeric', }).formatToParts(date); const partMap = Object.fromEntries( parts.map((part) => [part.type, part.value]), ); return `${partMap.year}-${partMap.month}-${partMap.day} ${partMap.hour}:${partMap.minute}:${partMap.second}`; } function stringifyPrettyUnknownValue(value: unknown) { try { const serialized = JSON.stringify(value, null, 2); return serialized ?? String(value); } catch { return String(value); } } function stringifyUnknownValue(value: unknown) { try { const serialized = JSON.stringify(value); return serialized ?? String(value); } catch { return String(value); } } interface DatabaseTableHeader { name: string; label: string; optionLabel: string; description: string; } interface FormattedTableCellValue { content: string; fullText: string; } const databaseTableColumnLabelMap: Record = { id: 'ID', table_name: '表名', row_count: '行数', error_message: '错误信息', fetch_errors: '读取异常', total_returned: '返回总数', columns: '列名列表', rows: '行列表', raw: '原始值', user_id: '用户ID', referee_user_id: '被邀请人ID', invitee_user_id: '被邀请人ID', owner_user_id: '归属用户ID', profile_id: '档案ID', module_key: '模块键', event_id: '事件ID', event_key: '事件键', event_title: '事件名称', operation_id: '操作ID', code_kind: '码类型', scope_kind: '范围类型', scope_id: '范围ID', day_key: '日期键', occurred_at: '发生时间', created_at: '创建时间', updated_at: '更新时间', starts_at: '开始时间', expires_at: '到期时间', last_played_at: '最近游玩时间', published_at: '发布时间', sort_order: '排序值', reward_points: '奖励积分', threshold: '阈值', enabled: '启用状态', status: '状态', description: '说明', title: '标题', name: '名称', points: '积分', total: '总数', count: '数量', metadata_json: '元数据JSON', data_json: '数据JSON', config_json: '配置JSON', draft_json: '草稿JSON', snapshot_json: '快照JSON', payload_json: '载荷JSON', request_payload_json: '请求载荷JSON', latest_structured_payload_json: '最新结构化载荷JSON', result_payload_json: '结果载荷JSON', evidence_json: '凭证JSON', source_asset_ids_json: '源资产ID列表', available_choices_json: '可选项JSON', visible_character_ids_json: '可见角色ID列表', played_profile_ids_json: '已玩档案列表', tags_json: '标签JSON', theme_tags_json: '主题标签JSON', cover_image_src: '封面图', cover_asset_id: '封面资产ID', public_user_code: '陶泥号', public_work_code: '公开作品号', author_public_user_code: '作者陶泥号', author_display_name: '作者昵称', display_name: '显示名', avatar_url: '头像地址', phone_number_masked: '手机号掩码', phone_number_e164: '手机号E164', login_method: '登录方式', binding_status: '绑定状态', token_version: '令牌版本', wallet_balance: '钱包余额', amount_delta: '变动金额', balance_after: '变动后余额', source_type: '来源类型', source_module: '来源模块', source_entity_id: '来源实体ID', request_label: '请求标签', task_kind: '任务类型', failure_message: '失败信息', latest_text_output: '最新文本输出', world_key: '世界键', world_type: '世界类型', world_name: '世界名称', world_title: '世界标题', world_subtitle: '世界副标题', subtitle: '副标题', summary_text: '摘要文本', bottom_tab: '底部Tab', saved_at: '存档时间', visited_at: '访问时间', claimed_at: '领取时间', paid_at: '支付时间', started_at: '开始时间', completed_at: '完成时间', finished_at_ms: '完成时间毫秒', started_at_ms: '开始时间毫秒', elapsed_ms: '耗时毫秒', duration_limit_ms: '时限毫秒', play_count: '游玩次数', clear_count: '通关次数', like_id: '点赞ID', liked_at: '点赞时间', invite_code: '邀请码', inviter_user_id: '邀请人ID', inviter_reward_granted: '邀请人奖励发放状态', invitee_reward_granted: '被邀请人奖励发放状态', bound_at: '绑定时间', search: '关键词', filters: '筛选条件', limit: '条数上限', value: '值', type: '类型', kind: '类型', key: '键', url: '链接地址', path: '路径', message: '消息', content: '内容', text: '文本', source: '来源', target: '目标', asset_id: '资产ID', file_id: '文件ID', task_id: '任务ID', session_id: '会话ID', record_id: '记录ID', created_by: '创建人', updated_by: '更新人', operator_user_id: '操作人ID', total_count: '总数', max_uses: '最大使用次数', global_used_count: '全局使用次数', allowed_user_ids: '允许用户列表', metadata: '元数据', }; const databaseTableColumnDescriptionMap: Record = { id: '当前记录的唯一标识', table_name: '当前查询的表名', row_count: '当前统计到的行数', error_message: '读取表统计或行数据时返回的错误信息', fetch_errors: '读取表列表时积累的异常信息', total_returned: '本次接口实际返回的行数', columns: '当前结果集返回的字段名列表', rows: '当前结果集返回的行列表', raw: '当前行的原始值', user_id: '当前记录所属用户的标识', referee_user_id: '被邀请人的用户标识', invitee_user_id: '被邀请人的用户标识', owner_user_id: '当前记录归属用户的标识', profile_id: '当前用户档案的标识', module_key: '当前记录所属模块的业务键', event_id: '当前埋点事件的唯一标识', event_key: '埋点事件键', event_title: '埋点事件展示名称', operation_id: '后台操作记录的唯一标识', code_kind: '码类型,redeem 表示兑换码,invite 表示邀请码', scope_kind: '埋点统计范围类型', scope_id: '埋点统计范围标识', day_key: '按天聚合时使用的日期键', occurred_at: '事件实际发生时间', created_at: '记录创建时间', updated_at: '记录更新时间', starts_at: '生效开始时间', expires_at: '失效或到期时间', last_played_at: '最近一次游玩时间', published_at: '公开发布时间', sort_order: '列表或配置项使用的排序值', reward_points: '完成任务后发放的奖励积分', threshold: '触发完成条件的阈值', enabled: '是否启用', status: '当前状态', description: '字段说明', title: '展示标题', name: '名称', points: '积分或点数', metadata_json: '存放结构化附加信息的 JSON 文本', data_json: '存放业务数据的 JSON 文本', config_json: '当前记录的配置 JSON', draft_json: '当前记录的草稿 JSON', snapshot_json: '当前运行态快照 JSON', payload_json: '事件或任务携带的载荷 JSON', request_payload_json: 'AI 任务请求载荷 JSON', latest_structured_payload_json: 'AI 任务最新结构化输出 JSON', result_payload_json: '生成或编排完成后的结果载荷 JSON', evidence_json: '用户反馈提交时附带的凭证元数据 JSON', source_asset_ids_json: '当前记录引用的源资产 ID 列表', available_choices_json: '当前运行态可选项 JSON', visible_character_ids_json: '当前运行态可见角色 ID 列表', played_profile_ids_json: '当前运行态已串联游玩的档案 ID 列表', tags_json: '标签列表 JSON', theme_tags_json: '主题标签列表 JSON', cover_image_src: '封面图片地址或平台资产引用', cover_asset_id: '封面对应的平台资产 ID', public_user_code: '用户对外展示的陶泥号', public_work_code: '作品公开后对外展示的作品号', author_public_user_code: '作者对外展示的陶泥号', author_display_name: '作者展示昵称', display_name: '用户展示名称', avatar_url: '用户头像地址', phone_number_masked: '脱敏后的手机号', phone_number_e164: 'E.164 格式手机号', login_method: '账号最近或主要登录方式', binding_status: '账号绑定状态', token_version: '用于令牌吊销和刷新控制的版本号', wallet_balance: '当前钱包余额', amount_delta: '本次钱包流水的增减值', balance_after: '本次变更后的钱包余额', source_type: '当前记录的来源类型', source_module: '触发 AI 任务的来源模块', source_entity_id: '触发 AI 任务的来源实体 ID', request_label: 'AI 任务请求展示标签', task_kind: 'AI 任务类型', failure_message: '失败时记录的错误信息', latest_text_output: 'AI 任务最近一次文本输出', world_key: '世界或作品在运行态中的稳定键', world_type: '世界或作品类型', world_name: '世界名称', world_title: '世界标题', world_subtitle: '世界副标题', subtitle: '副标题', summary_text: '摘要文本', bottom_tab: '保存快照时所在的底部 Tab', saved_at: '用户保存存档的时间', visited_at: '用户访问该作品或世界的时间', claimed_at: '奖励领取时间', paid_at: '订单支付时间', started_at: '任务或流程开始时间', completed_at: '任务或流程完成时间', finished_at_ms: '运行态完成时的毫秒时间戳', started_at_ms: '运行态开始时的毫秒时间戳', elapsed_ms: '运行态已消耗毫秒数', duration_limit_ms: '运行态时间限制毫秒数', play_count: '作品累计游玩次数', clear_count: '累计通关次数', like_id: '点赞记录唯一标识', liked_at: '点赞发生时间', invite_code: '完成绑定时使用的邀请码', inviter_user_id: '邀请人的用户标识', inviter_reward_granted: '邀请人奖励是否已经发放', invitee_reward_granted: '被邀请人奖励是否已经发放', bound_at: '邀请关系绑定时间', search: '用于本地过滤的关键词', filters: '用于等值筛选的 JSON 条件', limit: '本次查询请求的条数上限', value: '当前字段值', type: '当前字段类型', kind: '当前字段种类', key: '用于稳定识别记录或业务项的键', url: '链接地址或资源地址', path: '资源路径或业务路径', message: '消息内容或错误信息', content: '主要内容', text: '文本内容', source: '来源字段', target: '目标字段', asset_id: '平台资产对象标识', file_id: '文件标识', task_id: '任务标识', session_id: '会话标识', record_id: '记录标识', created_by: '创建该记录的主体', updated_by: '最后更新该记录的主体', operator_user_id: '执行后台操作的用户标识', total_count: '累计总数', max_uses: '允许的最大使用次数', global_used_count: '当前已使用次数', allowed_user_ids: '被允许使用的用户 ID 列表', metadata: '结构化附加信息', }; const databaseTableColumnSegmentLabelMap: Record = { account: '账户', action: '动作', active: '启用', actor: '参与者', added: '新增', address: '地址', after: '后', agent: 'Agent', ai: 'AI', amount: '金额', anchor: '锚点', archive: '存档', assistant: '助手', at: '时间', asset: '资产', attach: '附加', attachment: '附件', author: '作者', available: '可用', balance: '余额', before: '前', best: '最佳', binding: '绑定', blockers: '阻断项', body: '正文', bottom: '底部', built: '构建', bucket: 'Bucket', cache: '缓存', card: '卡片', cents: '分', channel: '渠道', chapter: '章节', character: '角色', chat: '聊天', choice: '选项', claim: '领取', claimed: '领取', claimant: '领取人', cleared: '已清除', client: '客户端', code: '编码', completed: '完成', content: '内容', count: '数量', create: '创建', created: '创建', current: '当前', custom: '自定义', data: '数据', date: '日期', day: '日期', default: '默认', delta: '变动', delete: '删除', deleted: '删除', description: '说明', detail: '详情', device: '设备', difficulty: '难度', display: '展示', draft: '草稿', elapsed: '耗时', enabled: '启用', entity: '实体', entry: '记录', error: '错误', evidence: '凭证', event: '事件', expires: '到期', expire: '到期', expired: '到期', failure: '失败', file: '文件', filter: '筛选', first: '首个', flags: '标记', flow: '流程', form: '表单', game: '游戏', granted: '发放', global: '全局', grid: '网格', group: '分组', hash: '哈希', history: '历史', host: '主机', id: 'ID', image: '图片', info: '信息', initial: '初始', input: '输入', invite: '邀请', invitee: '被邀请人', inviter: '邀请人', issued: '签发', item: '物品', job: '任务', json: 'JSON', key: '键', kind: '类型', last: '最近', ledger: '流水', level: '关卡', liked: '点赞', limit: '条数上限', line: '行', link: '链接', list: '列表', login: '登录', main: '主', max: '最大', membership: '会员', message: '消息', metadata: '元数据', metrics: '指标', module: '模块', mode: '模式', name: '名称', next: '下一个', npc: 'NPC', note: '备注', number: '编号', object: '对象', observed: '观察', order: '订单', owner: '归属', pack: '包', paid: '支付', password: '密码', payment: '支付', percent: '百分比', phone: '手机', phase: '阶段', path: '路径', pending: '待处理', picture: '图片', platform: '平台', points: '积分', policy: '策略', product: '商品', profile: '档案', progress: '进度', prompt: '提示词', provider: '提供方', publication: '发布', publish: '发布', payload: '载荷', public: '公开', query: '查询', ready: '就绪', raw: '原始', record: '记录', reference: '引用', referral: '邀请关系', refresh: '刷新', register: '注册', relation: '关系', reply: '回复', request: '请求', response: '响应', reward: '奖励', row: '行', rows: '行', run: '运行', runtime: '运行态', save: '存档', saved: '存档', search: '搜索', seed: '种子', seen: '可见', sequence: '序号', session: '会话', shape: '形状', share: '分享', slot: '槽位', snapshot: '快照', sort: '排序', status: '状态', source: '来源', starts: '开始', started: '开始', state: '状态', step: '步骤', structured: '结构化', summary: '摘要', task: '任务', target: '目标', text: '文本', time: '时间', title: '标题', total: '总数', type: '类型', unique: '唯一', update: '更新', updated: '更新', url: '链接', user: '用户', value: '值', version: '版本', visible: '可见', wallet: '钱包', wechat: '微信', work: '作品', world: '世界', xp: '经验', occurred: '发生', played: '游玩', published: '发布时间', returned: '返回', rewardpoints: '奖励积分', result: '结果', read: '读取', write: '写入', totalreturned: '返回总数', }; const databaseTableLabelMap: Record = { database_migration_operator: '数据库迁移操作员', database_migration_import_chunk: '数据库迁移分片', auth_store_snapshot: '认证仓储快照', user_account: '用户账号', auth_identity: '身份绑定', refresh_session: '刷新会话', runtime_setting: '运行时设置', runtime_snapshot: '运行时快照', user_browse_history: '浏览历史', profile_dashboard_state: '个人主页状态', profile_wallet_ledger: '钱包流水', analytics_date_dimension: '日期维表', tracking_event: '埋点事件', tracking_daily_stat: '埋点日统计', profile_task_config: '个人任务配置', profile_task_progress: '个人任务进度', profile_task_reward_claim: '个人任务领奖', profile_redeem_code: '兑换码', profile_redeem_code_usage: '兑换码使用记录', profile_code_operation: '码操作记录', profile_invite_code: '邀请码', profile_referral_relation: '邀请关系', profile_played_world: '已玩世界', public_work_play_daily_stat: '公开作品日游玩统计', public_work_like: '公开作品点赞', profile_membership: '会员状态', profile_recharge_order: '充值订单', profile_feedback_submission: '反馈提交', profile_save_archive: '存档记录', story_session: '剧情会话', story_event: '剧情事件', npc_state: 'NPC 状态', inventory_slot: '背包槽位', battle_state: '战斗状态', treasure_record: '宝藏记录', quest_record: '任务记录', quest_log: '任务日志', player_progression: '玩家进度', chapter_progression: '章节进度', custom_world_profile: '自定义世界档案', custom_world_session: '自定义世界会话', custom_world_agent_session: '自定义世界 Agent 会话', custom_world_agent_message: '自定义世界 Agent 消息', custom_world_agent_operation: '自定义世界 Agent 操作', custom_world_draft_card: '自定义世界草稿卡片', custom_world_gallery_entry: '自定义世界画廊条目', puzzle_agent_session: '拼图 Agent 会话', puzzle_agent_message: '拼图 Agent 消息', puzzle_work_profile: '拼图作品档案', puzzle_event: '拼图事件', puzzle_runtime_run: '拼图运行记录', puzzle_leaderboard_entry: '拼图排行榜条目', match3d_agent_session: '抓大鹅 Agent 会话', match3d_agent_message: '抓大鹅 Agent 消息', match3d_work_profile: '抓大鹅作品档案', match3d_runtime_run: '抓大鹅运行记录', square_hole_agent_session: '方洞挑战 Agent 会话', square_hole_agent_message: '方洞挑战 Agent 消息', square_hole_work_profile: '方洞挑战作品档案', square_hole_runtime_run: '方洞挑战运行记录', visual_novel_agent_session: '视觉小说 Agent 会话', visual_novel_agent_message: '视觉小说 Agent 消息', visual_novel_work_profile: '视觉小说作品档案', visual_novel_runtime_run: '视觉小说运行记录', visual_novel_runtime_history_entry: '视觉小说历史条目', visual_novel_runtime_event: '视觉小说运行事件', big_fish_creation_session: '大鱼吃小鱼创建会话', big_fish_agent_message: '大鱼吃小鱼 Agent 消息', big_fish_asset_slot: '大鱼吃小鱼资产槽位', big_fish_event: '大鱼吃小鱼事件', big_fish_runtime_run: '大鱼吃小鱼运行记录', asset_object: '资产对象', asset_entity_binding: '资产实体绑定', asset_event: '资产事件', ai_task: 'AI 任务', ai_task_stage: 'AI 任务阶段', ai_text_chunk: 'AI 文本分片', ai_result_reference: 'AI 结果引用', ai_task_event: 'AI 任务事件', }; const databaseTableDescriptionMap: Record = { database_migration_operator: '管理数据库迁移导出、导入和增量导入权限的操作员表', database_migration_import_chunk: '大迁移 JSON 分片导入的临时表', auth_store_snapshot: '旧认证仓储的整份 JSON 快照表', user_account: '用户账号主表', auth_identity: '第三方或手机号身份绑定表', refresh_session: '刷新令牌会话表', runtime_setting: '用户运行时设置表', runtime_snapshot: '用户当前运行时快照表', user_browse_history: '用户浏览历史表', profile_dashboard_state: '个人主页聚合状态表', profile_wallet_ledger: '钱包流水账表', analytics_date_dimension: '分析日期维表', tracking_event: '埋点原始事件表', tracking_daily_stat: '埋点按自然日聚合表', profile_task_config: '个人任务配置表', profile_task_progress: '个人任务进度表', profile_task_reward_claim: '个人任务领奖记录表', profile_redeem_code: '运营兑换码表', profile_redeem_code_usage: '兑换码使用记录表', profile_code_operation: '兑换码/邀请码后台操作记录表', profile_invite_code: '用户邀请中心邀请码表', profile_referral_relation: '邀请关系记录表', profile_played_world: '用户已玩世界记录表', public_work_play_daily_stat: '公开作品日游玩统计表', public_work_like: '公开作品点赞表', profile_membership: '会员状态表', profile_recharge_order: '充值订单表', profile_feedback_submission: '反馈提交记录表', profile_save_archive: '用户存档记录表', story_session: '剧情会话表', story_event: '剧情事件表', npc_state: 'NPC 状态表', inventory_slot: '背包槽位表', battle_state: '战斗状态表', treasure_record: '宝藏记录表', quest_record: '任务记录表', quest_log: '任务日志表', player_progression: '玩家进度表', chapter_progression: '章节进度表', custom_world_profile: '自定义世界档案表', custom_world_session: '自定义世界会话表', custom_world_agent_session: '自定义世界 Agent 会话表', custom_world_agent_message: '自定义世界 Agent 消息表', custom_world_agent_operation: '自定义世界 Agent 操作表', custom_world_draft_card: '自定义世界草稿卡片表', custom_world_gallery_entry: '自定义世界画廊条目表', puzzle_agent_session: '拼图 Agent 会话表', puzzle_agent_message: '拼图 Agent 消息表', puzzle_work_profile: '拼图作品档案表', puzzle_event: '拼图事件表', puzzle_runtime_run: '拼图运行记录表', puzzle_leaderboard_entry: '拼图排行榜条目表', match3d_agent_session: '抓大鹅 Agent 会话表', match3d_agent_message: '抓大鹅 Agent 消息表', match3d_work_profile: '抓大鹅作品档案表', match3d_runtime_run: '抓大鹅运行记录表', square_hole_agent_session: '方洞挑战 Agent 会话表', square_hole_agent_message: '方洞挑战 Agent 消息表', square_hole_work_profile: '方洞挑战作品档案表', square_hole_runtime_run: '方洞挑战运行记录表', visual_novel_agent_session: '视觉小说 Agent 会话表', visual_novel_agent_message: '视觉小说 Agent 消息表', visual_novel_work_profile: '视觉小说作品档案表', visual_novel_runtime_run: '视觉小说运行记录表', visual_novel_runtime_history_entry: '视觉小说历史条目表', visual_novel_runtime_event: '视觉小说运行事件表', big_fish_creation_session: '大鱼吃小鱼创建会话表', big_fish_agent_message: '大鱼吃小鱼 Agent 消息表', big_fish_asset_slot: '大鱼吃小鱼资产槽位表', big_fish_event: '大鱼吃小鱼事件表', big_fish_runtime_run: '大鱼吃小鱼运行记录表', asset_object: '资产对象表', asset_entity_binding: '资产实体绑定表', asset_event: '资产事件表', ai_task: 'AI 任务表', ai_task_stage: 'AI 任务阶段表', ai_text_chunk: 'AI 文本分片表', ai_result_reference: 'AI 结果引用表', ai_task_event: 'AI 任务事件表', };