M4(文风): style-auditor 双轨(提取指纹/漂移第四审)+ jobs 长任务框架(zombie reaper) + 回炉 refine + GET /style read-back。 M5(生成+扩展): worldbuilder/character-gen(入库 continuity 409 gate + partition_writes 白名单 + schema→JSONB 形变); 网关多 provider 回退链/熔断/能力降级(Anthropic/Gemini 适配器);Skill registry + 表权限沙箱 + 规则; 前端 角色生成器/世界观/Codex/规则页/技能库/⌘K 命令面板。 K1(Kimi Code 订阅接入): OAuth device-flow(kimi-code)+ 静态 Console key(kimi-code-key)两路径; coding 端点 KimiCLI 伪造头(实测 UA allow-list 门禁,缺则 403)+ JSON 模式结构化(thinking ⊥ tool_choice)。 本地联调修复: CORS 中间件;assemble 注入 premise+「写第N章」指令(修空 prompt 400); GET /outline·/draft read-back + 大纲/工作台/审稿页重载;写页 client/server 常量边界 + notFound 健壮化; 字数 toLocaleString locale 水合;审稿页终稿从已存草稿 seed(修 accept 422)。 门禁: backend ruff/mypy(157)/alembic 无漂移/pytest 451 · frontend lint/tsc/vitest/build。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
128 lines
4.3 KiB
TypeScript
128 lines
4.3 KiB
TypeScript
"use client";
|
||
|
||
import { authOpenUrl, formatExpiresAt } from "@/lib/settings/kimiOauth";
|
||
import { useKimiOauth } from "@/lib/settings/useKimiOauth";
|
||
|
||
interface KimiCodeOauthProps {
|
||
initialConnected: boolean;
|
||
initialExpiresAt: string | null;
|
||
}
|
||
|
||
// Kimi Code(OAuth 订阅 plan)连接区(K1.4)。
|
||
// 与 API-key 凭据行分离:无 key 输入,连接经 device flow(显示 user_code + 开授权页 + 轮询 job)。
|
||
export function KimiCodeOauth({
|
||
initialConnected,
|
||
initialExpiresAt,
|
||
}: KimiCodeOauthProps) {
|
||
const oauth = useKimiOauth({ initialConnected, initialExpiresAt });
|
||
const { phase, device, expiresAt, busy, error } = oauth;
|
||
const expiresLabel = formatExpiresAt(expiresAt);
|
||
|
||
return (
|
||
<section className="mb-10">
|
||
<h2 className="mb-1 font-serif text-xl text-ink">Kimi Code(OAuth)</h2>
|
||
<p className="mb-3 text-sm text-ink-soft">
|
||
订阅 plan 经 OAuth 设备授权连接(无需 API Key)。连接后可在上方档位路由里选用
|
||
<span className="font-mono"> kimi-code · kimi-for-coding</span>。
|
||
</p>
|
||
|
||
<div className="rounded border border-line bg-panel p-4">
|
||
<div className="flex flex-wrap items-center gap-3">
|
||
<span
|
||
className={`h-2 w-2 rounded-full ${
|
||
phase === "connected" ? "bg-pass" : "bg-line"
|
||
}`}
|
||
aria-hidden="true"
|
||
/>
|
||
<span className="text-sm text-ink">
|
||
{phase === "connected" ? "已连接" : "未连接"}
|
||
</span>
|
||
{phase === "connected" && expiresLabel ? (
|
||
<span className="font-mono text-xs text-ink-soft">
|
||
凭据有效至 {expiresLabel}(到期自动刷新)
|
||
</span>
|
||
) : null}
|
||
|
||
<div className="ml-auto flex gap-2">
|
||
{phase === "connected" ? (
|
||
<button
|
||
type="button"
|
||
onClick={() => void oauth.disconnect()}
|
||
disabled={busy}
|
||
className="rounded border border-line px-3 py-1.5 text-sm text-ink disabled:opacity-40"
|
||
>
|
||
{busy ? "处理中…" : "断开"}
|
||
</button>
|
||
) : (
|
||
<button
|
||
type="button"
|
||
onClick={() => void oauth.connect()}
|
||
disabled={busy}
|
||
className="rounded bg-cinnabar px-3 py-1.5 text-sm text-panel disabled:opacity-40"
|
||
>
|
||
{busy
|
||
? "等待授权…"
|
||
: phase === "error"
|
||
? "重新连接 Kimi Code(OAuth)"
|
||
: "连接 Kimi Code(OAuth)"}
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{phase === "awaiting" && device ? (
|
||
<DeviceInstructions
|
||
userCode={device.userCode}
|
||
openUrl={authOpenUrl(device)}
|
||
/>
|
||
) : null}
|
||
|
||
{phase === "error" && error ? (
|
||
<p className="mt-3 text-sm text-conflict" role="alert">
|
||
{error}
|
||
</p>
|
||
) : null}
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
interface DeviceInstructionsProps {
|
||
userCode: string;
|
||
openUrl: string;
|
||
}
|
||
|
||
// 设备授权指引:突出展示 user_code(可读、可选、可聚焦)+ 打开授权页按钮。
|
||
function DeviceInstructions({ userCode, openUrl }: DeviceInstructionsProps) {
|
||
const openAuth = (): void => {
|
||
if (typeof window !== "undefined") {
|
||
window.open(openUrl, "_blank", "noopener,noreferrer");
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="motion-safe:transition-opacity mt-4 rounded border border-dashed border-line bg-bg p-4">
|
||
<p className="mb-2 text-sm text-ink-soft">
|
||
请在浏览器中打开授权页,并确认页面显示的设备码与下方一致:
|
||
</p>
|
||
<output
|
||
aria-label="设备授权码"
|
||
tabIndex={0}
|
||
className="mb-3 block select-all rounded bg-panel px-4 py-3 text-center font-mono text-2xl tracking-[0.3em] text-ink"
|
||
>
|
||
{userCode}
|
||
</output>
|
||
<button
|
||
type="button"
|
||
onClick={openAuth}
|
||
className="rounded border border-cinnabar px-3 py-1.5 text-sm text-cinnabar"
|
||
>
|
||
在浏览器中打开授权页
|
||
</button>
|
||
<p className="mt-2 text-xs text-ink-soft">
|
||
授权完成后本页会自动检测连接状态(设备码过期后请重新连接)。
|
||
</p>
|
||
</div>
|
||
);
|
||
}
|