summaryrefslogtreecommitdiff
path: root/frontend/src/pages/UsagePage.svelte
blob: 00b5917a1fc64abbaef034cc4c2df18182a5b424 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
<script lang="ts">
  import Page from '../components/Page.svelte';
  import Status from '../components/Status.svelte';
  import { getAIUsage } from '../lib/client';
  import { extractStatusMessage } from '../lib/state';
  import { fmtDateTime, fmtNum } from '../lib/format';
  import type { AIUsage } from '../lib/types';
  import * as Card from '$lib/components/ui/card';
  import { Badge } from '$lib/components/ui/badge';
  import { Separator } from '$lib/components/ui/separator';
  import * as Table from '$lib/components/ui/table';

  let usage = $state<AIUsage | null>(null);
  let busy = $state(true);
  let error = $state<string | null>(null);

  $effect(() => {
    void load();
  });

  async function load() {
    busy = true;
    error = null;
    try {
      usage = await getAIUsage();
    } catch (e) {
      error = extractStatusMessage(e);
    } finally {
      busy = false;
    }
  }
</script>

<Page title="AI 用量" subtitle="内部 POC 阶段的用量计量与限额参考。不含报价或计费">
  <div class="flex flex-col gap-4 md:gap-6">
    <Status busy={busy} error={error} busyText="正在载入用量…" empty={null} />
    {#if usage}
      <Card.Root size="sm" class="p-4">
        <Card.Content>
          <div class="flex flex-wrap gap-x-3 gap-y-1">
            <div>
              <div class="text-xs text-muted-foreground">模型请求</div>
              <div class="text-2xl font-bold tabular-nums text-foreground">{usage.totals?.requests ?? usage.items.length}</div>
            </div>
            <Separator orientation="vertical" class="!h-8 self-center" />
            <div>
              <div class="text-xs text-muted-foreground">输入 tokens</div>
              <div class="text-2xl font-bold tabular-nums text-foreground">{usage.totals?.input_tokens ?? '—'}</div>
            </div>
            <Separator orientation="vertical" class="!h-8 self-center" />
            <div>
              <div class="text-xs text-muted-foreground">输出 tokens</div>
              <div class="text-2xl font-bold tabular-nums text-foreground">{usage.totals?.output_tokens ?? '—'}</div>
            </div>
          </div>
          {#if usage.internal_poc}
            <p class="text-sm text-muted-foreground mt-1">内部 POC:请在当前配额内使用,不对实际价格负责;接受模型输出的决策需重新人工复核。</p>
          {/if}
        </Card.Content>
      </Card.Root>

      {#if usage.items.length > 0}
        <Card.Root size="sm" class="p-4">
          <Table.Root>
            <Table.Header>
              <Table.Row>
                <Table.Head>时间</Table.Head>
                <Table.Head>模型</Table.Head>
                <Table.Head>输入</Table.Head>
                <Table.Head>输出</Table.Head>
                <Table.Head>状态</Table.Head>
              </Table.Row>
            </Table.Header>
            <Table.Body>
              {#each usage.items as it (it.id)}
                <Table.Row>
                  <Table.Cell>{fmtDateTime(it.created_at)}</Table.Cell>
                  <Table.Cell>{it.model}</Table.Cell>
                  <Table.Cell class="tabular-nums">{it.input_tokens ?? '—'}</Table.Cell>
                  <Table.Cell class="tabular-nums">{it.output_tokens ?? '—'}</Table.Cell>
                  <Table.Cell>
                    <Badge variant={it.status === 'succeeded' ? 'secondary' : 'outline'}>{it.status ?? '—'}</Badge>
                  </Table.Cell>
                </Table.Row>
              {/each}
            </Table.Body>
          </Table.Root>
        </Card.Root>
      {:else}
        <Status busy={false} empty="尚未使用 AI 建议" emptyHint="在项目「策略」标签发起一次 AI 修改,记录会出现在这里" />
      {/if}
    {/if}
  </div>
</Page>