chore(frontend): introduce prettier + eslint and prune unused code
Backend CI / Backend (push) Has been cancelled
Frontend CI / Frontend (push) Failing after 1m39s

- Add Prettier 3 with prettier-plugin-organize-imports (sorts/removes unused imports)
- Add ESLint 10 flat config with typescript-eslint + eslint-plugin-vue + eslint-config-prettier
- Add root scripts: format, format:check, lint, lint:fix
- Reformat 257 files across admin-web, ops-web, desktop-client, packages
- Remove unused locals/exports flagged by --noUnusedLocals/--noUnusedParameters
- Fix duplicate localTabLabel key, surrogate-pair regex u-flag, NBSP literal in regex
- Skip server/ (Go) and apps/browser-extension/ (deprecated per ADR)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-01 20:39:09 +08:00
parent 21c7892ee4
commit 162abdc97c
258 changed files with 42261 additions and 37556 deletions
+89 -77
View File
@@ -5,111 +5,111 @@ import {
QuestionCircleOutlined,
ReloadOutlined,
ThunderboltOutlined,
} from "@ant-design/icons-vue";
import type { AIPointUsageLog } from "@geo/shared-types";
import { useQuery } from "@tanstack/vue-query";
import type { TableColumnsType } from "ant-design-vue";
import { computed, ref } from "vue";
import { useI18n } from "vue-i18n";
} from '@ant-design/icons-vue'
import type { AIPointUsageLog } from '@geo/shared-types'
import { useQuery } from '@tanstack/vue-query'
import type { TableColumnsType } from 'ant-design-vue'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { workspaceApi } from "@/lib/api";
import { formatDateTime } from "@/lib/display";
import { workspaceApi } from '@/lib/api'
import { formatDateTime } from '@/lib/display'
const { t } = useI18n();
const page = ref(1);
const pageSize = ref(10);
const { t } = useI18n()
const page = ref(1)
const pageSize = ref(10)
const quotaQuery = useQuery({
queryKey: ["workspace", "quota-summary", "ai-points"],
queryKey: ['workspace', 'quota-summary', 'ai-points'],
queryFn: () => workspaceApi.quotaSummary(),
});
})
const usageQuery = useQuery({
queryKey: computed(() => ["workspace", "ai-point-usage", page.value, pageSize.value]),
queryKey: computed(() => ['workspace', 'ai-point-usage', page.value, pageSize.value]),
queryFn: () => workspaceApi.aiPointUsage({ page: page.value, page_size: pageSize.value }),
});
})
const quotaSummary = computed(() => quotaQuery.data.value);
const usageRows = computed(() => usageQuery.data.value?.items ?? []);
const usageTotal = computed(() => usageQuery.data.value?.total ?? 0);
const quotaSummary = computed(() => quotaQuery.data.value)
const usageRows = computed(() => usageQuery.data.value?.items ?? [])
const usageTotal = computed(() => usageQuery.data.value?.total ?? 0)
const aiPointUsedPercent = computed(() => {
const total = quotaSummary.value?.ai_points_total ?? 0;
const used = quotaSummary.value?.ai_points_used ?? 0;
const total = quotaSummary.value?.ai_points_total ?? 0
const used = quotaSummary.value?.ai_points_used ?? 0
if (total <= 0) {
return 0;
return 0
}
return Math.min(100, Math.round((used / total) * 100));
});
const loading = computed(() => quotaQuery.isPending.value || usageQuery.isPending.value);
const refreshing = computed(() => quotaQuery.isFetching.value || usageQuery.isFetching.value);
return Math.min(100, Math.round((used / total) * 100))
})
const loading = computed(() => quotaQuery.isPending.value || usageQuery.isPending.value)
const refreshing = computed(() => quotaQuery.isFetching.value || usageQuery.isFetching.value)
const columns = computed<TableColumnsType<AIPointUsageLog>>(() => [
{
title: t("aiPoints.table.usageType"),
dataIndex: "usage_type",
key: "usage_type",
title: t('aiPoints.table.usageType'),
dataIndex: 'usage_type',
key: 'usage_type',
},
{
title: t("aiPoints.table.points"),
dataIndex: "points",
key: "points",
title: t('aiPoints.table.points'),
dataIndex: 'points',
key: 'points',
width: 110,
align: "right",
align: 'right',
},
{
title: t("aiPoints.table.requestChars"),
dataIndex: "request_chars",
key: "request_chars",
title: t('aiPoints.table.requestChars'),
dataIndex: 'request_chars',
key: 'request_chars',
width: 150,
},
{
title: t("common.status"),
dataIndex: "status",
key: "status",
title: t('common.status'),
dataIndex: 'status',
key: 'status',
width: 118,
},
{
title: t("common.createdAt"),
dataIndex: "created_at",
key: "created_at",
title: t('common.createdAt'),
dataIndex: 'created_at',
key: 'created_at',
width: 172,
},
]);
])
function refresh(): void {
void quotaQuery.refetch();
void usageQuery.refetch();
void quotaQuery.refetch()
void usageQuery.refetch()
}
function handleTableChange(nextPage: number, nextPageSize: number): void {
page.value = nextPage;
pageSize.value = nextPageSize;
page.value = nextPage
pageSize.value = nextPageSize
}
function getAIPointUsageTypeLabel(usageType: string): string {
const key = `shell.aiUsageTypes.${usageType}`;
const label = t(key);
return label === key ? usageType : label;
const key = `shell.aiUsageTypes.${usageType}`
const label = t(key)
return label === key ? usageType : label
}
function getAIPointStatusMeta(status: string): { label: string; color: string } {
const key = `shell.aiUsageStatus.${status}`;
const label = t(key);
const normalizedLabel = label === key ? status : label;
const key = `shell.aiUsageStatus.${status}`
const label = t(key)
const normalizedLabel = label === key ? status : label
switch (status) {
case "completed":
return { label: normalizedLabel, color: "success" };
case "pending":
return { label: normalizedLabel, color: "processing" };
case "refunded":
return { label: normalizedLabel, color: "default" };
case 'completed':
return { label: normalizedLabel, color: 'success' }
case 'pending':
return { label: normalizedLabel, color: 'processing' }
case 'refunded':
return { label: normalizedLabel, color: 'default' }
default:
return { label: normalizedLabel, color: "error" };
return { label: normalizedLabel, color: 'error' }
}
}
function formatAIPointDelta(item: AIPointUsageLog): string {
return item.status === "refunded" ? `+${item.points}` : `-${item.points}`;
return item.status === 'refunded' ? `+${item.points}` : `-${item.points}`
}
</script>
@@ -118,23 +118,23 @@ function formatAIPointDelta(item: AIPointUsageLog): string {
<section class="ai-points-view__top-card">
<div class="ai-points-view__header">
<div class="ai-points-view__header-title">
<h2>{{ t("route.aiPoints.title") }}</h2>
<p>{{ t("route.aiPoints.description") }}</p>
<h2>{{ t('route.aiPoints.title') }}</h2>
<p>{{ t('route.aiPoints.description') }}</p>
</div>
<div class="ai-points-view__header-actions">
<a-button :loading="refreshing" @click="refresh">
<template #icon><ReloadOutlined /></template>
{{ t("common.refresh") }}
{{ t('common.refresh') }}
</a-button>
</div>
</div>
<a-divider style="margin: 0; border-color: #f0f0f0;" />
<a-divider style="margin: 0; border-color: #f0f0f0" />
<div class="ai-points-view__summary">
<div class="ai-points-balance">
<span class="summary-label">{{ t("aiPoints.balance") }}</span>
<strong>{{ quotaSummary?.ai_points_balance ?? "--" }}</strong>
<span class="summary-label">{{ t('aiPoints.balance') }}</span>
<strong>{{ quotaSummary?.ai_points_balance ?? '--' }}</strong>
<a-progress :percent="aiPointUsedPercent" :show-info="false" stroke-color="#1677ff" />
</div>
@@ -143,8 +143,11 @@ function formatAIPointDelta(item: AIPointUsageLog): string {
<ThunderboltOutlined />
</div>
<div class="summary-metric-content">
<span>{{ t("common.used") }}</span>
<strong>{{ quotaSummary?.ai_points_used ?? "--" }} / {{ quotaSummary?.ai_points_total ?? "--" }}</strong>
<span>{{ t('common.used') }}</span>
<strong>
{{ quotaSummary?.ai_points_used ?? '--' }} /
{{ quotaSummary?.ai_points_total ?? '--' }}
</strong>
</div>
</div>
@@ -153,8 +156,8 @@ function formatAIPointDelta(item: AIPointUsageLog): string {
<CalculatorOutlined />
</div>
<div class="summary-metric-content">
<span>{{ t("aiPoints.baseChars") }}</span>
<strong>{{ quotaSummary?.ai_point_base_chars ?? "--" }}</strong>
<span>{{ t('aiPoints.baseChars') }}</span>
<strong>{{ quotaSummary?.ai_point_base_chars ?? '--' }}</strong>
</div>
</div>
@@ -163,7 +166,7 @@ function formatAIPointDelta(item: AIPointUsageLog): string {
<FieldTimeOutlined />
</div>
<div class="summary-metric-content">
<span>{{ t("shell.resetAt") }}</span>
<span>{{ t('shell.resetAt') }}</span>
<strong>{{ formatDateTime(quotaSummary?.ai_points_reset_at) }}</strong>
</div>
</div>
@@ -173,8 +176,8 @@ function formatAIPointDelta(item: AIPointUsageLog): string {
<section class="ai-points-view__table-card">
<div class="ai-points-view__table-head">
<div>
<p class="eyebrow">{{ t("aiPoints.ledgerTitle") }}</p>
<h3>{{ t("aiPoints.ledgerTotal", { total: usageTotal }) }}</h3>
<p class="eyebrow">{{ t('aiPoints.ledgerTitle') }}</p>
<h3>{{ t('aiPoints.ledgerTotal', { total: usageTotal }) }}</h3>
</div>
</div>
@@ -198,24 +201,33 @@ function formatAIPointDelta(item: AIPointUsageLog): string {
<div class="usage-type-cell">
<strong>{{ getAIPointUsageTypeLabel(record.usage_type) }}</strong>
<span v-if="record.model">{{ record.model }}</span>
<span v-else-if="record.error_message" class="usage-error">{{ record.error_message }}</span>
<span v-else-if="record.error_message" class="usage-error">
{{ record.error_message }}
</span>
</div>
</template>
<template v-else-if="column.key === 'points'">
<span class="points-delta" :class="{ 'points-delta--refund': record.status === 'refunded' }">
<span
class="points-delta"
:class="{ 'points-delta--refund': record.status === 'refunded' }"
>
{{ formatAIPointDelta(record) }}
</span>
</template>
<template v-else-if="column.key === 'request_chars'">
<span class="usage-rule">
{{ t("shell.aiUsageRule", { chars: record.request_chars, base: record.base_chars }) }}
{{ t('shell.aiUsageRule', { chars: record.request_chars, base: record.base_chars }) }}
</span>
</template>
<template v-else-if="column.key === 'status'">
<a-tag class="status-tag" :color="getAIPointStatusMeta(record.status).color" :bordered="false">
<a-tag
class="status-tag"
:color="getAIPointStatusMeta(record.status).color"
:bordered="false"
>
{{ getAIPointStatusMeta(record.status).label }}
<a-tooltip v-if="record.status === 'pending'" :title="t('shell.aiUsagePendingHelp')">
<QuestionCircleOutlined class="status-help-icon" />