관리자 AI 제공자 연결 기능 추가 (claude·codex·agy·openrouter 토큰 붙여넣기 연결)
Some checks failed
API contract / OpenAPI type drift (push) Failing after 12m57s
Some checks failed
API contract / OpenAPI type drift (push) Failing after 12m57s
관리자 /admin/ai 화면에서 provider 토큰(OAuth 액세스 토큰 또는 API 키)을 발급받아 붙여넣으면 DB에 암호화 저장되고, shared-secret으로 보호되는 게이트웨이 내부 엔드포인트로 push되어 실행 중인 엔진 컨테이너에 즉시 적용된다. NAS처럼 게이트웨이가 컨테이너로 도는 환경에서 CLI·로컬 PC 의존 없이 claude_api·openai·openrouter를 연결할 수 있다. - 게이트웨이: openrouter 어댑터 신설(chat/completions), OAuth 토큰이면 Anthropic Bearer 헤더, /internal/provider-credentials GET/POST와 boot_id 기반 재동기화 - API: app.admin_provider_credential 테이블(idempotent DDL), stdlib HMAC-CTR+MAC 암호화 서비스(PROVIDER_CREDENTIAL_SECRET, 폴백 SESSION_SECRET), /admin/providers CRUD·검증 라우트 - 웹: 제공자 연결 패널(저장·검증·해제, 토큰 힌트만 표시), 엔진 선택에 OpenRouter 추가, api.gen.ts 재생성 - compose: api 서비스에 PROVIDER_CREDENTIAL_SECRET 전달(로컬·NAS)
This commit is contained in:
parent
6831a79ffb
commit
4b45d33142
22 changed files with 2088 additions and 13 deletions
|
|
@ -828,3 +828,139 @@
|
|||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 제공자 연결 (ProviderConnectionsPanel) ───────────────────── */
|
||||
.aic-providers .aic-provider__list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.aic-provider {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg-surface-2);
|
||||
}
|
||||
|
||||
.aic-provider.is-connected {
|
||||
border-color: color-mix(in srgb, var(--ok-solid, #2e7d32) 38%, var(--border-subtle));
|
||||
}
|
||||
|
||||
.aic-provider__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.aic-provider__head b {
|
||||
color: var(--text-strong);
|
||||
font-size: var(--fs-sm);
|
||||
}
|
||||
|
||||
.aic-provider__badge {
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--border-subtle);
|
||||
color: var(--text-muted);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.aic-provider__badge.is-on {
|
||||
border-color: transparent;
|
||||
background: color-mix(in srgb, var(--ok-solid, #2e7d32) 16%, transparent);
|
||||
color: var(--ok-text, #2e7d32);
|
||||
}
|
||||
|
||||
.aic-provider__desc,
|
||||
.aic-provider__guide {
|
||||
margin: 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.aic-provider__guide {
|
||||
color: var(--text-body);
|
||||
}
|
||||
|
||||
.aic-provider__link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
color: var(--text-strong);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.aic-provider__controls {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aic-provider__kind {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.aic-provider__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aic-provider__meta {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
margin: 0;
|
||||
padding-top: 8px;
|
||||
border-top: 1px dashed var(--border-subtle);
|
||||
}
|
||||
|
||||
.aic-provider__meta div {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aic-provider__meta dt {
|
||||
flex: 0 0 32px;
|
||||
color: var(--text-muted);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.aic-provider__meta dd {
|
||||
margin: 0;
|
||||
color: var(--text-body);
|
||||
font-size: 11px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.aic-provider__verify {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.aic-provider__verify.is-ok {
|
||||
color: var(--ok-text, #2e7d32);
|
||||
}
|
||||
|
||||
.aic-provider__verify.is-fail {
|
||||
color: var(--crit-text, #b3261e);
|
||||
}
|
||||
|
||||
.aic-provider__flash {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 9px 12px;
|
||||
border: 1px solid color-mix(in srgb, var(--ok-solid, #2e7d32) 34%, var(--border-subtle));
|
||||
border-radius: var(--radius);
|
||||
color: var(--ok-text, #2e7d32);
|
||||
font-size: var(--fs-sm);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue