M0+M1: 基建 + 用户/密钥/核心代理

后端 (Go/Gin/GORM):
- 配置(viper+env)、SQLite/Postgres 迁移、argon2id、AES-GCM 渠道密钥、JWT+refresh cookie
- 用户注册/登录/刷新/登出、API Key CRUD(仅存哈希、明文一次展示)
- 代理网关: /v1/chat/completions、/v1/responses、/v1/models 直通 OpenAI 渠道
  非流式+流式(SSE 零缓冲转发), 用量捕获(chat 末块/responses completed 嵌套),
  OpenAI 错误格式(401/402/404/502), 余额检查
- 异步批量记账 + 余额流水 + 日聚合, admin 用户/余额/配置 API
- 单测: crypto/jwt/apikey/流式 usage 提取

前端 (Vue3+TS+Vite+Tailwind v4):
- taste-skill 设计 tokens: 深色仪表盘, 石墨+信号铜色, Outfit+JetBrains Mono
- Landing/登录/注册, 控制台(仪表盘图表/密钥管理/用量明细)
- 基础组件 Button/Input/Badge/Modal, ECharts 用量图

部署: docker-compose(nginx+api+postgres), 双 Dockerfile, nginx SSE 反代
联调: scripts/mockupstream 本地 mock 上游, 端到端验证通过
This commit is contained in:
Sakurasan
2026-08-15 13:10:47 +08:00
commit 360c6b33a6
75 changed files with 7044 additions and 0 deletions
+92
View File
@@ -0,0 +1,92 @@
<script setup lang="ts">
import { useAuthStore } from '../stores/auth'
import { computed } from 'vue'
const auth = useAuthStore()
const primaryAction = computed(() => (auth.isAuthed ? '/console/dashboard' : '/register'))
const protocols = [
{ name: 'POST /v1/chat/completions', desc: 'OpenAI Chat Completions · 流式 + 工具调用' },
{ name: 'POST /v1/responses', desc: 'OpenAI Responses API · 新一代生态' },
{ name: 'POST /v1/messages', desc: 'Anthropic Messages · Claude 原生格式' },
{ name: 'GET /v1/models', desc: 'OpenAI 风格模型列表' },
]
</script>
<template>
<div class="min-h-[100dvh] bg-ink-950 text-paper-100">
<!-- 顶部导航 -->
<header class="sticky top-0 z-40 border-b border-ink-800 bg-ink-950/90 backdrop-blur">
<div class="mx-auto flex h-16 max-w-6xl items-center justify-between px-6">
<div class="flex items-center gap-2.5">
<span class="flex h-7 w-7 items-center justify-center rounded-md bg-signal-400">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M2 8h3l2-5 3 10 2-5h2" stroke="#0c0d0f" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
<span class="text-[15px] font-semibold tracking-tight">openteam</span>
<span class="ml-1 rounded border border-ink-700 px-1.5 py-px font-mono text-[10px] text-paper-500">relay</span>
</div>
<nav class="flex items-center gap-2">
<a v-if="!auth.isAuthed" href="/login" class="rounded-md px-3 py-2 text-sm text-paper-500 transition-colors hover:text-paper-100">登录</a>
<a :href="primaryAction" class="rounded-md bg-signal-400 px-4 py-2 text-sm font-medium text-ink-950 transition-colors hover:bg-signal-300">开始使用</a>
</nav>
</div>
</header>
<main>
<!-- Hero:左对齐,信号路径示意 -->
<section class="mx-auto grid max-w-6xl grid-cols-1 items-center gap-14 px-6 pt-16 pb-20 lg:grid-cols-[1.1fr_0.9fr]">
<div>
<p class="font-mono text-xs uppercase tracking-[0.2em] text-signal-400">self-hosted llm relay</p>
<h1 class="mt-4 text-4xl leading-[1.05] font-bold tracking-tight md:text-5xl">
一个 Key,<br />接入全部模型
</h1>
<p class="mt-5 max-w-[52ch] text-base leading-relaxed text-paper-500">
自托管 LLM API 中转网关。统一 OpenAI 与 Anthropic 协议入口,背后对接任意上游渠道,用量计费一目了然。
</p>
<div class="mt-8 flex items-center gap-3">
<a href="/register" class="inline-flex h-10 items-center rounded-md bg-signal-400 px-5 text-sm font-medium text-ink-950 transition-all hover:bg-signal-300 active:scale-[0.98]">立即开始</a>
<a href="#protocols" class="inline-flex h-10 items-center rounded-md border border-ink-600 px-5 text-sm text-paper-300 transition-colors hover:border-signal-400 hover:text-signal-300">查看端点</a>
</div>
</div>
<!-- 信号路径:client → gateway → upstream -->
<div class="rounded-lg border border-ink-700 bg-ink-900 p-5 font-mono text-[13px]">
<div class="flex items-center gap-3 pb-4">
<span class="h-1.5 w-1.5 animate-pulse rounded-full bg-mint-400" />
<span class="text-xs text-paper-500">request path · live</span>
</div>
<div class="space-y-2">
<div class="rounded-md border border-ink-700 bg-ink-850 px-3 py-2 text-paper-300">client <span class="text-signal-400">──▶</span> <span class="text-paper-500">/v1/chat/completions</span></div>
<div class="rounded-md border border-ink-700 bg-ink-850 px-3 py-2 text-paper-300">gateway <span class="text-signal-400">──▶</span> <span class="text-paper-500">auth · quota · route</span></div>
<div class="rounded-md border border-ink-700 bg-ink-850 px-3 py-2 text-paper-300">upstream <span class="text-mint-400">◀──</span> <span class="text-paper-500">openai / anthropic</span></div>
<div class="rounded-md border border-ink-700 bg-ink-850 px-3 py-2 text-paper-300">billing <span class="text-mint-400">──▶</span> <span class="num text-paper-500">usage · cost · ledger</span></div>
</div>
</div>
</section>
<!-- 协议端点 -->
<section id="protocols" class="border-t border-ink-800 bg-ink-900/50">
<div class="mx-auto max-w-6xl px-6 py-16">
<h2 class="text-xl font-semibold tracking-tight">三套协议,一个入口</h2>
<p class="mt-2 max-w-[60ch] text-sm leading-relaxed text-paper-500">
OpenAI 与 Anthropic 生态的 SDK 与客户端无需改动,直接指向本网关。协议间自动互转。
</p>
<div class="mt-8 grid grid-cols-1 gap-3 md:grid-cols-2">
<div v-for="p in protocols" :key="p.name" class="rounded-lg border border-ink-700 bg-ink-900 p-4">
<code class="font-mono text-[13px] text-signal-300">{{ p.name }}</code>
<p class="mt-1.5 text-[13px] text-paper-500">{{ p.desc }}</p>
</div>
</div>
</div>
</section>
<!-- 页脚 -->
<footer class="border-t border-ink-800">
<div class="mx-auto flex max-w-6xl items-center justify-between px-6 py-6 text-xs text-paper-600">
<span>openteam · 自托管 LLM 中转网关</span>
<span class="font-mono">v0.1 · M1</span>
</div>
</footer>
</main>
</div>
</template>