feat(admin): persist provider drafts in crm
This commit is contained in:
@@ -8,11 +8,22 @@
|
||||
|
||||
- `tksea-portal/index.html`
|
||||
- `https://sub.tksea.top/portal/` 的静态页面源码
|
||||
- `tksea-portal/admin/index.html`
|
||||
- `https://sub.tksea.top/portal/admin/` 的管理首页
|
||||
- 统一收纳“新增模型 / 供应商目录”和“导入供应商帐号”入口
|
||||
- `tksea-portal/admin/providers.html`
|
||||
- `https://sub.tksea.top/portal/admin/providers.html`
|
||||
- 用现有 CRM API 做 pack/provider 浏览、preview-import、import,以及 provider manifest 草稿生成
|
||||
- 当前也可直接调用服务端 `provider_drafts` API,把 manifest 草稿持久化到 CRM SQLite,并支持更新 / 删除
|
||||
- `tksea-portal/admin/batch-import.html`
|
||||
- `https://sub.tksea.top/portal/admin/batch-import.html`
|
||||
- 结构化入口地址,当前跳转到 legacy `admin-batch-import.html`
|
||||
- `tksea-portal/admin-batch-import.html`
|
||||
- `https://sub.tksea.top/portal/admin-batch-import.html` 的最小管理页
|
||||
- 直接消费 `POST /api/batch-import/runs` 与 `GET /api/batch-import/runs/*`
|
||||
- `tksea-portal/nginx.sub.tksea.top.conf.example`
|
||||
- `sub.tksea.top` 上 portal 路由与代理示例
|
||||
- 当前同时包含 `/portal-proxy/` 宿主用户态代理与 `/portal-admin-api/` CRM 管理态代理
|
||||
|
||||
它和 `scripts/` 的边界如下:
|
||||
|
||||
|
||||
@@ -42,6 +42,33 @@
|
||||
margin: 0 auto;
|
||||
padding: 36px 20px 64px;
|
||||
}
|
||||
.topnav {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.topnav a {
|
||||
text-decoration: none;
|
||||
padding: 10px 14px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--line);
|
||||
background: rgba(255,255,255,0.78);
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
transition: transform 120ms ease, background 120ms ease, color 120ms ease;
|
||||
}
|
||||
.topnav a:hover {
|
||||
transform: translateY(-1px);
|
||||
background: #fff;
|
||||
color: var(--ink);
|
||||
}
|
||||
.topnav a.is-current {
|
||||
background: var(--ink);
|
||||
color: #fff;
|
||||
border-color: var(--ink);
|
||||
}
|
||||
.hero {
|
||||
display: grid;
|
||||
grid-template-columns: 1.3fr 0.7fr;
|
||||
@@ -343,6 +370,13 @@
|
||||
</head>
|
||||
<body>
|
||||
<main class="shell">
|
||||
<nav class="topnav" aria-label="Admin Navigation">
|
||||
<a href="/portal/admin/">管理首页</a>
|
||||
<a href="/portal/admin/providers.html">新增模型 / 供应商目录</a>
|
||||
<a href="/portal/admin/batch-import.html" class="is-current">导入供应商帐号</a>
|
||||
<a href="/portal/" target="_blank" rel="noreferrer">用户 Portal</a>
|
||||
</nav>
|
||||
|
||||
<section class="hero">
|
||||
<article class="hero-card">
|
||||
<div class="eyebrow">Batch Import Admin</div>
|
||||
@@ -350,7 +384,7 @@
|
||||
<p class="hero-copy">
|
||||
这个页面只做三件事:发起 batch import、查看 run 摘要、拉取 item 级复用结果。
|
||||
后端仍然以现有 `POST /api/batch-import/runs` 与 `GET /api/batch-import/runs/*` 为准,
|
||||
页面不引入额外协议。
|
||||
页面不引入额外协议。默认通过同域 `portal-admin-api` 访问 CRM。
|
||||
</p>
|
||||
<ul class="hero-points">
|
||||
<li>直接展示 `matched_account_state`</li>
|
||||
@@ -384,7 +418,7 @@
|
||||
|
||||
<div class="field-grid two">
|
||||
<label>API Base
|
||||
<input id="api-base" type="text" placeholder="https://crm.example.com">
|
||||
<input id="api-base" type="text" placeholder="https://sub.tksea.top/portal-admin-api">
|
||||
</label>
|
||||
<label>Host ID
|
||||
<input id="host-id" type="text" placeholder="remote43-current-host">
|
||||
@@ -573,7 +607,7 @@ https://api.other.com/v1|sk-example-2|deepseek-chat,gpt-5.4</textarea>
|
||||
}
|
||||
|
||||
function defaultApiBase() {
|
||||
return window.location.origin;
|
||||
return `${window.location.origin}/portal-admin-api`;
|
||||
}
|
||||
|
||||
function saveConfig() {
|
||||
|
||||
39
deploy/tksea-portal/admin/batch-import.html
Normal file
39
deploy/tksea-portal/admin/batch-import.html
Normal file
@@ -0,0 +1,39 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Batch Import Admin Redirect</title>
|
||||
<meta http-equiv="refresh" content="0; url=/portal/admin-batch-import.html">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-family: "IBM Plex Sans", "Noto Sans SC", "PingFang SC", sans-serif;
|
||||
background: linear-gradient(180deg, #f8f3ea 0%, #efe5d7 100%);
|
||||
color: #1f1a16;
|
||||
}
|
||||
.card {
|
||||
width: min(34rem, calc(100vw - 2rem));
|
||||
padding: 28px;
|
||||
border-radius: 24px;
|
||||
border: 1px solid rgba(31, 26, 22, 0.12);
|
||||
background: rgba(255,252,246,0.92);
|
||||
box-shadow: 0 20px 60px rgba(46, 37, 28, 0.08);
|
||||
}
|
||||
a {
|
||||
color: #0c6cc9;
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="card">
|
||||
<h1>正在跳转到导入供应商帐号页面</h1>
|
||||
<p>如果浏览器没有自动跳转,请手动打开:</p>
|
||||
<p><a href="/portal/admin-batch-import.html">/portal/admin-batch-import.html</a></p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
356
deploy/tksea-portal/admin/index.html
Normal file
356
deploy/tksea-portal/admin/index.html
Normal file
@@ -0,0 +1,356 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Admin Portal</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f4efe6;
|
||||
--panel: rgba(255, 252, 246, 0.92);
|
||||
--ink: #1f1a16;
|
||||
--muted: #665c53;
|
||||
--line: rgba(31, 26, 22, 0.12);
|
||||
--accent: #0c6cc9;
|
||||
--accent-soft: rgba(12, 108, 201, 0.12);
|
||||
--success: #136f46;
|
||||
--success-soft: rgba(19, 111, 70, 0.1);
|
||||
--warn: #9f6417;
|
||||
--warn-soft: rgba(159, 100, 23, 0.12);
|
||||
--shadow: 0 24px 70px rgba(46, 37, 28, 0.1);
|
||||
--radius: 24px;
|
||||
--radius-sm: 16px;
|
||||
--font-sans: "IBM Plex Sans", "Noto Sans SC", "PingFang SC", sans-serif;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
color: var(--ink);
|
||||
font-family: var(--font-sans);
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(12, 108, 201, 0.18), transparent 24rem),
|
||||
radial-gradient(circle at bottom right, rgba(19, 111, 70, 0.12), transparent 28rem),
|
||||
linear-gradient(180deg, #f8f3ea 0%, #f1e9dd 100%);
|
||||
}
|
||||
a { color: inherit; }
|
||||
.shell {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 34px 20px 64px;
|
||||
}
|
||||
.topnav {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.topnav a {
|
||||
text-decoration: none;
|
||||
padding: 10px 14px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--line);
|
||||
background: rgba(255,255,255,0.74);
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.topnav a.is-current {
|
||||
background: var(--ink);
|
||||
border-color: var(--ink);
|
||||
color: #fff;
|
||||
}
|
||||
.hero {
|
||||
display: grid;
|
||||
grid-template-columns: 1.2fr 0.8fr;
|
||||
gap: 18px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.card {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.hero-card {
|
||||
padding: 30px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.hero-card::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: -4rem;
|
||||
bottom: -4rem;
|
||||
width: 16rem;
|
||||
height: 16rem;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(135deg, rgba(12, 108, 201, 0.2), rgba(19, 111, 70, 0.06));
|
||||
filter: blur(10px);
|
||||
}
|
||||
.eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
background: var(--accent-soft);
|
||||
color: var(--accent);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
h1 {
|
||||
margin: 18px 0 10px;
|
||||
font-size: clamp(34px, 5vw, 50px);
|
||||
line-height: 1;
|
||||
letter-spacing: -0.05em;
|
||||
}
|
||||
.hero-copy {
|
||||
max-width: 56rem;
|
||||
color: var(--muted);
|
||||
line-height: 1.75;
|
||||
font-size: 16px;
|
||||
}
|
||||
.hero-points {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
padding: 0;
|
||||
margin: 18px 0 0;
|
||||
list-style: none;
|
||||
}
|
||||
.hero-points li {
|
||||
padding: 9px 12px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--line);
|
||||
background: rgba(255,255,255,0.74);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.stack {
|
||||
padding: 24px;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
align-content: start;
|
||||
}
|
||||
.metric {
|
||||
border-radius: 20px;
|
||||
padding: 16px;
|
||||
border: 1px solid var(--line);
|
||||
background: #fff;
|
||||
}
|
||||
.metric-label {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.metric-value {
|
||||
margin-top: 8px;
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 18px;
|
||||
}
|
||||
.panel {
|
||||
padding: 24px;
|
||||
}
|
||||
.panel h2 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 24px;
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
.panel p {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
line-height: 1.7;
|
||||
font-size: 14px;
|
||||
}
|
||||
.cta-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
.cta {
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 10rem;
|
||||
padding: 12px 18px;
|
||||
border-radius: 999px;
|
||||
font-weight: 800;
|
||||
border: 1px solid var(--line);
|
||||
transition: transform 120ms ease, background 120ms ease, color 120ms ease;
|
||||
}
|
||||
.cta:hover { transform: translateY(-1px); }
|
||||
.cta.primary { background: var(--ink); color: #fff; border-color: var(--ink); }
|
||||
.cta.secondary { background: var(--accent-soft); color: var(--accent); }
|
||||
.list {
|
||||
margin: 18px 0 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
.list li {
|
||||
padding: 14px 16px;
|
||||
border-radius: 18px;
|
||||
border: 1px solid var(--line);
|
||||
background: rgba(255,255,255,0.8);
|
||||
}
|
||||
.list strong {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-size: 15px;
|
||||
}
|
||||
.status-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
.status-card {
|
||||
padding: 16px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid var(--line);
|
||||
background: #fff;
|
||||
}
|
||||
.status-card strong {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
font-size: 24px;
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
.status-available {
|
||||
background: linear-gradient(180deg, #fff 0%, rgba(19, 111, 70, 0.08) 100%);
|
||||
}
|
||||
.status-caution {
|
||||
background: linear-gradient(180deg, #fff 0%, rgba(159, 100, 23, 0.08) 100%);
|
||||
}
|
||||
.status-note {
|
||||
background: linear-gradient(180deg, #fff 0%, rgba(12, 108, 201, 0.08) 100%);
|
||||
}
|
||||
code {
|
||||
font-family: "IBM Plex Mono", "JetBrains Mono", monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
@media (max-width: 980px) {
|
||||
.hero, .grid, .status-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="shell">
|
||||
<nav class="topnav" aria-label="Admin Navigation">
|
||||
<a href="/portal/admin/" class="is-current">管理首页</a>
|
||||
<a href="/portal/admin/providers.html">新增模型 / 供应商目录</a>
|
||||
<a href="/portal/admin/batch-import.html">导入供应商帐号</a>
|
||||
<a href="/portal/" target="_blank" rel="noreferrer">用户 Portal</a>
|
||||
</nav>
|
||||
|
||||
<section class="hero">
|
||||
<article class="card hero-card">
|
||||
<div class="eyebrow">Admin Portal</div>
|
||||
<h1>把新增模型与导入帐号收进同一套入口</h1>
|
||||
<p class="hero-copy">
|
||||
这个入口不再把“新增模型供应商”和“导入供应商帐号”拆散在不同地址。当前版本统一从
|
||||
<code>/portal/admin/</code> 进入:一边看 pack/provider 目录、做 preview/import,一边继续保留
|
||||
item 级 <code>reused / reactivated / replaced</code> 的 batch-import 结果面板。
|
||||
</p>
|
||||
<ul class="hero-points">
|
||||
<li>默认同域走 <code>/portal-admin-api/</code></li>
|
||||
<li>静态页与 CRM API 解耦</li>
|
||||
<li>保留旧地址兼容,不打断现有操作</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<aside class="card stack">
|
||||
<div class="metric">
|
||||
<div class="metric-label">统一入口</div>
|
||||
<div class="metric-value">/portal/admin/</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-label">Provider 目录</div>
|
||||
<div class="metric-value">/providers</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-label">Batch Import</div>
|
||||
<div class="metric-value">/batch-import</div>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<section class="grid">
|
||||
<article class="card panel">
|
||||
<h2>新增模型 / 供应商目录</h2>
|
||||
<p>
|
||||
这页负责浏览已安装 pack、选择 provider、调用 <code>preview-import</code> /
|
||||
<code>import</code>,同时提供 provider manifest 草稿生成器。当前版本不直接在浏览器里写入 pack 仓库,
|
||||
但已经把“新增模型”的准备动作收进同一条操作链。
|
||||
</p>
|
||||
<div class="cta-row">
|
||||
<a class="cta primary" href="/portal/admin/providers.html">打开供应商页</a>
|
||||
<a class="cta secondary" href="/portal/admin/providers.html#manifest-draft">跳到 manifest 草稿</a>
|
||||
</div>
|
||||
<ul class="list">
|
||||
<li>
|
||||
<strong>适用动作</strong>
|
||||
查看 pack 与 provider、输入 keys 做 preview/import、生成 provider 草稿 JSON。
|
||||
</li>
|
||||
<li>
|
||||
<strong>默认 API Base</strong>
|
||||
<code>https://sub.tksea.top/portal-admin-api</code>
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<article class="card panel">
|
||||
<h2>导入供应商帐号</h2>
|
||||
<p>
|
||||
这页继续负责 live batch-import:创建 run、拉取 run summary、查看 item 级别的
|
||||
<code>matched_account_state</code> 与 <code>account_resolution</code>。
|
||||
</p>
|
||||
<div class="cta-row">
|
||||
<a class="cta primary" href="/portal/admin/batch-import.html">打开导入页</a>
|
||||
<a class="cta secondary" href="/portal/admin-batch-import.html">旧地址兼容入口</a>
|
||||
</div>
|
||||
<ul class="list">
|
||||
<li>
|
||||
<strong>适用动作</strong>
|
||||
批量导入第三方 key,验证 <code>reused / created / reactivated / replaced</code>。
|
||||
</li>
|
||||
<li>
|
||||
<strong>默认 API Base</strong>
|
||||
<code>https://sub.tksea.top/portal-admin-api</code>
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="status-grid">
|
||||
<article class="status-card status-available">
|
||||
<div class="metric-label">可立即使用</div>
|
||||
<strong>Provider 浏览 + 导入</strong>
|
||||
<p>依赖现有 <code>/api/packs</code>、<code>/api/providers/*</code>、<code>/api/batch-import/*</code> 即可完成。</p>
|
||||
</article>
|
||||
<article class="status-card status-note">
|
||||
<div class="metric-label">当前边界</div>
|
||||
<strong>浏览器不直接写 pack 仓库</strong>
|
||||
<p>新增 provider 模板的最终落盘仍通过仓库提交完成,页面当前先覆盖目录、草稿与导入操作。</p>
|
||||
</article>
|
||||
<article class="status-card status-caution">
|
||||
<div class="metric-label">安全前提</div>
|
||||
<strong>仍需 Admin Token</strong>
|
||||
<p>CRM 的 API 权限仍由 Bearer token 控制,同域反代只解决浏览器可达性,不降低鉴权门槛。</p>
|
||||
</article>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
1177
deploy/tksea-portal/admin/providers.html
Normal file
1177
deploy/tksea-portal/admin/providers.html
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,12 +4,17 @@
|
||||
# - /portal/ 是新的通用多模型接入中心地址
|
||||
# - /kimi-portal 与 /kimi-portal/ 保留 302 跳转,避免旧链接失效
|
||||
# - /portal-proxy/ 是页面调用宿主用户态 API 的同域代理
|
||||
# - /portal-admin-api/ 是页面调用 CRM 管理 API 的同域代理
|
||||
# - /kimi/ 与 /kimi-v1/ 继续保留,兼容旧的 Kimi 专用客户端配置
|
||||
|
||||
location = /portal {
|
||||
return 302 /portal/;
|
||||
}
|
||||
|
||||
location = /portal/admin {
|
||||
return 302 /portal/admin/;
|
||||
}
|
||||
|
||||
location = /kimi-portal {
|
||||
return 302 /portal/;
|
||||
}
|
||||
@@ -30,6 +35,15 @@ location /portal-proxy/ {
|
||||
proxy_http_version 1.1;
|
||||
}
|
||||
|
||||
location /portal-admin-api/ {
|
||||
proxy_pass http://127.0.0.1:18173/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_http_version 1.1;
|
||||
}
|
||||
|
||||
location /kimi-portal/ {
|
||||
return 302 /portal/;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user