aidevops 3.31.74 → 3.31.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/VERSION +1 -1
- package/aidevops.sh +1 -1
- package/package.json +1 -1
- package/packages/gui-api/src/status-adapter.ts +1 -1
- package/packages/gui-shared/src/contracts.ts +1 -1
- package/packages/gui-shared/src/fixtures.ts +11 -0
- package/packages/gui-web/src/AiProvidersSurface.tsx +297 -0
- package/packages/gui-web/src/StatusSurfaces.tsx +6 -64
- package/packages/gui-web/src/styles.css +213 -4
- package/setup.sh +1 -1
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ The result: an AI operations platform that manages projects across every busines
|
|
|
60
60
|
[](https://github.com/marcusquinn)
|
|
61
61
|
|
|
62
62
|
<!-- Release & Version Info -->
|
|
63
|
-
[](https://github.com/marcusquinn/aidevops/releases)
|
|
64
64
|
[](https://www.npmjs.com/package/aidevops)
|
|
65
65
|
[](https://github.com/marcusquinn/homebrew-tap)
|
|
66
66
|
[](https://github.com/marcusquinn/aidevops)
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.31.
|
|
1
|
+
3.31.75
|
package/aidevops.sh
CHANGED
package/package.json
CHANGED
|
@@ -191,7 +191,7 @@ export function readVaultStatus(
|
|
|
191
191
|
return envelope;
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
const AI_PROVIDER_IDS: GuiAiProviderId[] = ["anthropic", "openai", "cursor", "google"];
|
|
194
|
+
const AI_PROVIDER_IDS: GuiAiProviderId[] = ["anthropic", "openai", "cursor", "google", "zai"];
|
|
195
195
|
const SETUP_TARGET_DEFINITIONS: Array<Pick<GuiSetupTargetSummary, "label" | "path_ref" | "purpose">> = [
|
|
196
196
|
{
|
|
197
197
|
label: "Deployed agents",
|
|
@@ -446,7 +446,7 @@ export interface GuiConversationThread {
|
|
|
446
446
|
read_states: GuiConversationReadState[];
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
-
export type GuiAiProviderId = "anthropic" | "openai" | "cursor" | "google";
|
|
449
|
+
export type GuiAiProviderId = "anthropic" | "openai" | "cursor" | "google" | "zai";
|
|
450
450
|
|
|
451
451
|
export interface GuiOAuthPoolAccountSummary {
|
|
452
452
|
email_ref: string;
|
|
@@ -254,6 +254,17 @@ export const statusFixture: GuiStatusData = {
|
|
|
254
254
|
pending_token: false,
|
|
255
255
|
accounts: [],
|
|
256
256
|
},
|
|
257
|
+
{
|
|
258
|
+
provider: "zai",
|
|
259
|
+
configured: false,
|
|
260
|
+
total: 0,
|
|
261
|
+
available: 0,
|
|
262
|
+
active_or_idle: 0,
|
|
263
|
+
rate_limited: 0,
|
|
264
|
+
auth_errors: 0,
|
|
265
|
+
pending_token: false,
|
|
266
|
+
accounts: [],
|
|
267
|
+
},
|
|
257
268
|
],
|
|
258
269
|
},
|
|
259
270
|
setup_targets: [
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { IconType } from "react-icons";
|
|
3
|
+
import { FiAlertTriangle, FiCheckCircle, FiClock, FiCommand, FiGlobe, FiInfo, FiKey, FiPlusCircle, FiRefreshCw, FiRepeat, FiShield, FiTerminal, FiThumbsUp, FiTool } from "react-icons/fi";
|
|
4
|
+
import type { GuiOAuthProviderSummary, GuiStatusData } from "../../gui-shared/src";
|
|
5
|
+
import { text } from "./app-model";
|
|
6
|
+
|
|
7
|
+
type AiProviderGroupId = "recommended-oauth" | "oauth-pool";
|
|
8
|
+
|
|
9
|
+
interface AiProviderCatalogEntry {
|
|
10
|
+
id: GuiOAuthProviderSummary["provider"];
|
|
11
|
+
displayName: string;
|
|
12
|
+
group: AiProviderGroupId;
|
|
13
|
+
authKind: string;
|
|
14
|
+
description: string;
|
|
15
|
+
connectionHint: string;
|
|
16
|
+
recommended: boolean;
|
|
17
|
+
recommendation?: string;
|
|
18
|
+
opencodeProviders: string[];
|
|
19
|
+
capabilities: string[];
|
|
20
|
+
Icon: IconType;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const AI_PROVIDER_GROUPS: Array<{ detail: string; id: AiProviderGroupId; label: string }> = [
|
|
24
|
+
{ id: "recommended-oauth", label: "Recommended OAuth pools", detail: "First-class providers to connect for aidevops model rotation and worker resilience." },
|
|
25
|
+
{ id: "oauth-pool", label: "OAuth pool compatible", detail: "Additional OpenCode providers that can be represented by the local aidevops OAuth pool." },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const AI_PROVIDER_CATALOG: AiProviderCatalogEntry[] = [
|
|
29
|
+
{
|
|
30
|
+
id: "openai",
|
|
31
|
+
displayName: "OpenAI",
|
|
32
|
+
group: "recommended-oauth",
|
|
33
|
+
authKind: "OAuth pool",
|
|
34
|
+
description: "Primary coding and reasoning models through OpenCode's OpenAI provider and the aidevops OAuth pool.",
|
|
35
|
+
connectionHint: "Use OpenCode auth login, choose OpenAI Pool, then add each account email separately.",
|
|
36
|
+
recommended: true,
|
|
37
|
+
recommendation: "Recommended for strong coding coverage and current OAuth rotation support.",
|
|
38
|
+
opencodeProviders: ["openai", "openai-pool", "openrouter/openai"],
|
|
39
|
+
capabilities: ["coding", "reasoning", "pool rotation"],
|
|
40
|
+
Icon: FiCommand,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "cursor",
|
|
44
|
+
displayName: "Cursor",
|
|
45
|
+
group: "recommended-oauth",
|
|
46
|
+
authKind: "OAuth proxy",
|
|
47
|
+
description: "Cursor account pool routed through the aidevops Cursor proxy for OpenCode-compatible model access.",
|
|
48
|
+
connectionHint: "Use OpenCode auth login, choose Cursor Pool, then keep every account as a separate pool entry.",
|
|
49
|
+
recommended: true,
|
|
50
|
+
recommendation: "Recommended when Cursor subscriptions provide useful fallback capacity.",
|
|
51
|
+
opencodeProviders: ["cursor", "cursor-pool"],
|
|
52
|
+
capabilities: ["subscription reuse", "proxy", "fallback capacity"],
|
|
53
|
+
Icon: FiTerminal,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "zai",
|
|
57
|
+
displayName: "Z.ai",
|
|
58
|
+
group: "recommended-oauth",
|
|
59
|
+
authKind: "OAuth pool",
|
|
60
|
+
description: "GLM/Z.ai provider family surfaced by OpenCode as zai, z-ai, and zhipuai model prefixes.",
|
|
61
|
+
connectionHint: "Track Z.ai as a pool target now; audited OAuth connection routes should be added before enabling writes.",
|
|
62
|
+
recommended: true,
|
|
63
|
+
recommendation: "Recommended for GLM coding and low-friction non-US provider diversity.",
|
|
64
|
+
opencodeProviders: ["zai", "zai-coding-plan", "zhipuai", "openrouter/z-ai"],
|
|
65
|
+
capabilities: ["GLM models", "provider diversity", "planned OAuth"],
|
|
66
|
+
Icon: FiTool,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "anthropic",
|
|
70
|
+
displayName: "Anthropic",
|
|
71
|
+
group: "oauth-pool",
|
|
72
|
+
authKind: "OAuth pool",
|
|
73
|
+
description: "Claude provider pool for compatible Anthropic and Claude Code sessions.",
|
|
74
|
+
connectionHint: "Use OpenCode auth login, choose Anthropic Pool, then rotate accounts with the local OAuth pool helper.",
|
|
75
|
+
recommended: false,
|
|
76
|
+
opencodeProviders: ["anthropic", "openrouter/anthropic", "poe/anthropic"],
|
|
77
|
+
capabilities: ["Claude models", "OAuth", "account rotation"],
|
|
78
|
+
Icon: FiShield,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "google",
|
|
82
|
+
displayName: "Google Gemini",
|
|
83
|
+
group: "oauth-pool",
|
|
84
|
+
authKind: "OAuth pool",
|
|
85
|
+
description: "Gemini and Vertex-compatible OAuth accounts represented as metadata-only pool entries.",
|
|
86
|
+
connectionHint: "Use OpenCode auth login, choose Google Pool, and keep Workspace or AI subscription accounts separate.",
|
|
87
|
+
recommended: false,
|
|
88
|
+
opencodeProviders: ["google", "gemini", "openrouter/google", "vercel/google"],
|
|
89
|
+
capabilities: ["Gemini", "Vertex", "multimodal"],
|
|
90
|
+
Icon: FiGlobe,
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
export function AiProvidersSurface({ status }: { status: GuiStatusData }) {
|
|
95
|
+
const [activeGroup, setActiveGroup] = useState<AiProviderGroupId | "all">("all");
|
|
96
|
+
const [query, setQuery] = useState("");
|
|
97
|
+
const providersById = new Map(status.oauth_pool.providers.map((provider) => [provider.provider, provider]));
|
|
98
|
+
const catalogCards = AI_PROVIDER_CATALOG.map((entry) => ({ entry, provider: providersById.get(entry.id) ?? emptyProviderSummary(entry.id) }));
|
|
99
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
100
|
+
const visibleCards = catalogCards.filter(({ entry }) => {
|
|
101
|
+
const matchesGroup = activeGroup === "all" || entry.group === activeGroup;
|
|
102
|
+
const searchable = [entry.displayName, entry.id, entry.authKind, entry.description, entry.connectionHint, ...entry.opencodeProviders, ...entry.capabilities].join(" ").toLowerCase();
|
|
103
|
+
|
|
104
|
+
return matchesGroup && (normalizedQuery.length === 0 || searchable.includes(normalizedQuery));
|
|
105
|
+
});
|
|
106
|
+
const totalAccounts = catalogCards.reduce((sum, card) => sum + card.provider.total, 0);
|
|
107
|
+
const availableAccounts = catalogCards.reduce((sum, card) => sum + card.provider.available, 0);
|
|
108
|
+
const recommendedConnected = catalogCards.filter((card) => card.entry.recommended && card.provider.configured).length;
|
|
109
|
+
const needsAttention = catalogCards.filter((card) => card.provider.auth_errors > 0 || card.provider.rate_limited > 0 || card.provider.pending_token).length;
|
|
110
|
+
const summaryCards = [
|
|
111
|
+
{ label: "accounts", value: String(totalAccounts), detail: "metadata-only pool entries" },
|
|
112
|
+
{ label: "available", value: String(availableAccounts), detail: "ready after cooldown checks" },
|
|
113
|
+
{ label: "recommended", value: `${recommendedConnected}/3`, detail: "OpenAI, Cursor, and Z.ai" },
|
|
114
|
+
{ label: "attention", value: String(needsAttention), detail: "auth errors, rate limits, pending tokens" },
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<section className="surface-page ai-providers-surface" aria-label={text.aiProviders}>
|
|
119
|
+
<div className="hero-panel ai-provider-hero">
|
|
120
|
+
<div className="section-heading split-heading">
|
|
121
|
+
<div>
|
|
122
|
+
<p className="eyebrow">{status.oauth_pool.path_ref}</p>
|
|
123
|
+
<h2>{text.aiProviders}</h2>
|
|
124
|
+
<p>{text.aiProvidersIntro} Manage provider connections as OAuth pool metadata, with one card per OpenCode provider family and one row per account.</p>
|
|
125
|
+
</div>
|
|
126
|
+
<span className="count-pill">{status.oauth_pool.value_policy}</span>
|
|
127
|
+
</div>
|
|
128
|
+
<div className="ai-provider-summary-grid">
|
|
129
|
+
{summaryCards.map((card) => (
|
|
130
|
+
<article className="ai-provider-summary-card" key={card.label}>
|
|
131
|
+
<span>{card.label}</span>
|
|
132
|
+
<strong>{card.value}</strong>
|
|
133
|
+
<small>{card.detail}</small>
|
|
134
|
+
</article>
|
|
135
|
+
))}
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
<div className="notice compact-notice" role="note">
|
|
139
|
+
Data-leaves-device warning: provider accounts can process prompts outside this machine. Use `data_classification` and `runtime_policy` metadata to require Local AI or explicit provider approval for confidential work.
|
|
140
|
+
</div>
|
|
141
|
+
<section className="panel ai-provider-catalog-panel" aria-label="AI provider catalog">
|
|
142
|
+
<div className="provider-toolbar">
|
|
143
|
+
<label className="provider-search">
|
|
144
|
+
<FiInfo aria-hidden="true" />
|
|
145
|
+
<span className="sr-only">Search AI providers</span>
|
|
146
|
+
<input aria-label="Search AI providers" onChange={(event) => setQuery(event.target.value)} placeholder="Search providers, model prefixes, or capabilities" type="search" value={query} />
|
|
147
|
+
</label>
|
|
148
|
+
<div aria-label="Provider group filters" className="provider-filter-chips" role="toolbar">
|
|
149
|
+
<button className={activeGroup === "all" ? "active" : ""} onClick={() => setActiveGroup("all")} type="button">All <span>{catalogCards.length}</span></button>
|
|
150
|
+
{AI_PROVIDER_GROUPS.map((group) => (
|
|
151
|
+
<button className={activeGroup === group.id ? "active" : ""} key={group.id} onClick={() => setActiveGroup(group.id)} type="button">
|
|
152
|
+
{group.label} <span>{catalogCards.filter((card) => card.entry.group === group.id).length}</span>
|
|
153
|
+
</button>
|
|
154
|
+
))}
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
{visibleCards.length === 0 ? (
|
|
158
|
+
<p className="empty-state">No providers match this filter.</p>
|
|
159
|
+
) : (
|
|
160
|
+
AI_PROVIDER_GROUPS.map((group) => {
|
|
161
|
+
const groupCards = visibleCards.filter((card) => card.entry.group === group.id);
|
|
162
|
+
if (groupCards.length === 0) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<section className="provider-section" key={group.id} aria-label={group.label}>
|
|
168
|
+
<div className="section-heading split-heading provider-section-heading">
|
|
169
|
+
<div>
|
|
170
|
+
<p className="eyebrow">{groupCards.length} provider{groupCards.length === 1 ? "" : "s"}</p>
|
|
171
|
+
<h3>{group.label}</h3>
|
|
172
|
+
<p>{group.detail}</p>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
<div className="provider-grid">
|
|
176
|
+
{groupCards.map((card) => <ProviderCard entry={card.entry} key={card.entry.id} provider={card.provider} />)}
|
|
177
|
+
</div>
|
|
178
|
+
</section>
|
|
179
|
+
);
|
|
180
|
+
})
|
|
181
|
+
)}
|
|
182
|
+
</section>
|
|
183
|
+
</section>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function ProviderCard({ entry, provider }: { entry: AiProviderCatalogEntry; provider: GuiOAuthProviderSummary }) {
|
|
188
|
+
const Icon = entry.Icon;
|
|
189
|
+
const connectLabel = provider.configured ? "Add another account" : "Connect account";
|
|
190
|
+
const connectionTitle = `${entry.connectionHint} Browser write routes are read-only until audited provider actions are implemented.`;
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<article className={entry.recommended ? "provider-card provider-card-recommended" : "provider-card"}>
|
|
194
|
+
<header className="provider-card-header">
|
|
195
|
+
<div className="provider-identity">
|
|
196
|
+
<span className={`provider-avatar provider-avatar-${entry.id}`} aria-hidden="true"><Icon /></span>
|
|
197
|
+
<div>
|
|
198
|
+
<p className="eyebrow">{entry.authKind}</p>
|
|
199
|
+
<div className="provider-title-row">
|
|
200
|
+
<h3>{entry.displayName}</h3>
|
|
201
|
+
{entry.recommended ? <span className="recommendation-badge" title={entry.recommendation}><FiThumbsUp aria-hidden="true" />Recommended</span> : null}
|
|
202
|
+
</div>
|
|
203
|
+
<p>{entry.description}</p>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
<div className="provider-count-stack">
|
|
207
|
+
<strong>{provider.available}/{provider.total}</strong>
|
|
208
|
+
<ProviderStatusBadge provider={provider} />
|
|
209
|
+
</div>
|
|
210
|
+
</header>
|
|
211
|
+
<ul aria-label={`${entry.displayName} OpenCode provider prefixes`} className="provider-chip-row">
|
|
212
|
+
{entry.opencodeProviders.map((prefix) => <li className="provider-chip" key={prefix}>{prefix}</li>)}
|
|
213
|
+
</ul>
|
|
214
|
+
<p className="provider-connection-hint">{entry.connectionHint}</p>
|
|
215
|
+
<div className="repo-card-meta provider-metrics">
|
|
216
|
+
<ProviderDetail label="active/idle" value={String(provider.active_or_idle)} />
|
|
217
|
+
<ProviderDetail label="rate limited" value={String(provider.rate_limited)} />
|
|
218
|
+
<ProviderDetail label="auth errors" value={String(provider.auth_errors)} />
|
|
219
|
+
<ProviderDetail label="pending token" value={provider.pending_token ? "yes" : "no"} />
|
|
220
|
+
</div>
|
|
221
|
+
<ul aria-label={`${entry.displayName} capabilities`} className="provider-chip-row provider-capability-row">
|
|
222
|
+
{entry.capabilities.map((capability) => <li className="provider-chip" key={capability}>{capability}</li>)}
|
|
223
|
+
</ul>
|
|
224
|
+
<fieldset className="provider-actions">
|
|
225
|
+
<legend className="sr-only">{entry.displayName} planned management controls</legend>
|
|
226
|
+
<button disabled title={connectionTitle} type="button"><FiPlusCircle aria-hidden="true" />{connectLabel}</button>
|
|
227
|
+
<button disabled title="Read-only preview: rotation uses oauth-pool-helper.sh rotate after audited write routes land." type="button"><FiRepeat aria-hidden="true" />Rotate</button>
|
|
228
|
+
<button disabled title="Read-only preview: health checks use oauth-pool-helper.sh check after audited write routes land." type="button"><FiRefreshCw aria-hidden="true" />Check</button>
|
|
229
|
+
<button disabled title="Read-only preview: cooldown resets use oauth-pool-helper.sh reset-cooldowns after audited write routes land." type="button"><FiClock aria-hidden="true" />Cooldowns</button>
|
|
230
|
+
</fieldset>
|
|
231
|
+
{provider.accounts.length === 0 ? (
|
|
232
|
+
<p className="empty-state compact-empty">No accounts in this pool yet. Multiple accounts are supported by adding a separate OAuth entry per email.</p>
|
|
233
|
+
) : (
|
|
234
|
+
<ul className="provider-account-list">
|
|
235
|
+
{provider.accounts.map((account) => (
|
|
236
|
+
<li key={`${provider.provider}:${account.email_ref}`}>
|
|
237
|
+
<strong>{account.email_ref}</strong>
|
|
238
|
+
<span className="provider-account-status">{account.status}</span>
|
|
239
|
+
<small>priority {account.priority ?? "default"}</small>
|
|
240
|
+
<small>last used {account.last_used}</small>
|
|
241
|
+
<small>expires {account.expires_at}</small>
|
|
242
|
+
{account.cooldown_until ? <small>cooldown {account.cooldown_until}</small> : null}
|
|
243
|
+
<button disabled title="Read-only preview: account management requires audited provider write routes." type="button"><FiKey aria-hidden="true" />Manage</button>
|
|
244
|
+
</li>
|
|
245
|
+
))}
|
|
246
|
+
</ul>
|
|
247
|
+
)}
|
|
248
|
+
</article>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function ProviderStatusBadge({ provider }: { provider: GuiOAuthProviderSummary }) {
|
|
253
|
+
const status = providerStatus(provider);
|
|
254
|
+
const Icon = status.Icon;
|
|
255
|
+
|
|
256
|
+
return <span className={`status-pill provider-health ${status.className}`}><Icon aria-hidden="true" />{status.label}</span>;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function providerStatus(provider: GuiOAuthProviderSummary): { Icon: IconType; className: string; label: string } {
|
|
260
|
+
if (provider.auth_errors > 0) {
|
|
261
|
+
return { Icon: FiAlertTriangle, className: "error", label: "auth attention" };
|
|
262
|
+
}
|
|
263
|
+
if (provider.rate_limited > 0) {
|
|
264
|
+
return { Icon: FiClock, className: "warn", label: "rate limited" };
|
|
265
|
+
}
|
|
266
|
+
if (provider.pending_token) {
|
|
267
|
+
return { Icon: FiKey, className: "warn", label: "pending token" };
|
|
268
|
+
}
|
|
269
|
+
if (provider.configured) {
|
|
270
|
+
return { Icon: FiCheckCircle, className: "ready", label: "configured" };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return { Icon: FiInfo, className: "neutral", label: "not connected" };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function emptyProviderSummary(provider: GuiOAuthProviderSummary["provider"]): GuiOAuthProviderSummary {
|
|
277
|
+
return {
|
|
278
|
+
provider,
|
|
279
|
+
configured: false,
|
|
280
|
+
total: 0,
|
|
281
|
+
available: 0,
|
|
282
|
+
active_or_idle: 0,
|
|
283
|
+
rate_limited: 0,
|
|
284
|
+
auth_errors: 0,
|
|
285
|
+
pending_token: false,
|
|
286
|
+
accounts: [],
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function ProviderDetail({ label, value }: { label: string; value: string }) {
|
|
291
|
+
return (
|
|
292
|
+
<span>
|
|
293
|
+
<small>{label}</small>
|
|
294
|
+
<strong>{value}</strong>
|
|
295
|
+
</span>
|
|
296
|
+
);
|
|
297
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/* jshint esversion: 11 */
|
|
2
2
|
import { useState } from "react";
|
|
3
|
-
import type { GuiAiAppSummary, GuiLocalRepoSetupSummary,
|
|
3
|
+
import type { GuiAiAppSummary, GuiLocalRepoSetupSummary, GuiSetupTargetSummary, GuiStatusData, GuiVaultCollectionSummary, GuiVaultStatusData } from "../../gui-shared/src";
|
|
4
4
|
import { plannedHomes, text } from "./app-model";
|
|
5
5
|
import { FileExplorerSurface } from "./FileExplorerSurface";
|
|
6
6
|
import { PathActions } from "./PathActions";
|
|
7
7
|
import { VaultPadlock } from "./VaultBadges";
|
|
8
8
|
|
|
9
|
+
export { AiProvidersSurface } from "./AiProvidersSurface";
|
|
10
|
+
|
|
9
11
|
export function OverviewSurface({ status }: { status: GuiStatusData }) {
|
|
10
12
|
const metrics = [
|
|
11
13
|
{ label: text.setup, value: status.update.restart_required ? "restart" : "current", detail: status.update.installed_version },
|
|
@@ -150,9 +152,9 @@ export function VaultSurface({ status }: { status: GuiStatusData }) {
|
|
|
150
152
|
</div>
|
|
151
153
|
{vaultCollection ? <VaultPadlock collection={vaultCollection} vault={vault} /> : null}
|
|
152
154
|
</div>
|
|
153
|
-
<
|
|
154
|
-
{readiness.map((item) => <
|
|
155
|
-
</
|
|
155
|
+
<ul aria-label="Vault readiness" className="vault-readiness-strip">
|
|
156
|
+
{readiness.map((item) => <li key={item.label}><strong>{item.label}</strong>{item.value}</li>)}
|
|
157
|
+
</ul>
|
|
156
158
|
</div>
|
|
157
159
|
{vault.locked ? (
|
|
158
160
|
<div className="notice compact-notice" role="note">
|
|
@@ -445,66 +447,6 @@ function AiAppCard({ app }: { app: GuiAiAppSummary }) {
|
|
|
445
447
|
);
|
|
446
448
|
}
|
|
447
449
|
|
|
448
|
-
export function AiProvidersSurface({ status }: { status: GuiStatusData }) {
|
|
449
|
-
return (
|
|
450
|
-
<section className="panel ai-providers-panel" aria-label={text.aiProviders}>
|
|
451
|
-
<div className="section-heading split-heading">
|
|
452
|
-
<div>
|
|
453
|
-
<p className="eyebrow">{status.oauth_pool.path_ref}</p>
|
|
454
|
-
<h2>{text.aiProviders}</h2>
|
|
455
|
-
<p>{text.aiProvidersIntro}</p>
|
|
456
|
-
</div>
|
|
457
|
-
<span className="count-pill">{status.oauth_pool.value_policy}</span>
|
|
458
|
-
</div>
|
|
459
|
-
<div className="notice compact-notice" role="note">
|
|
460
|
-
Data-leaves-device warning: provider accounts can process prompts outside this machine. Use `data_classification` and `runtime_policy` metadata to require Local AI or explicit provider approval for confidential work.
|
|
461
|
-
</div>
|
|
462
|
-
<div className="provider-grid">
|
|
463
|
-
{status.oauth_pool.providers.map((provider) => <ProviderCard key={provider.provider} provider={provider} />)}
|
|
464
|
-
</div>
|
|
465
|
-
</section>
|
|
466
|
-
);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
function ProviderCard({ provider }: { provider: GuiOAuthProviderSummary }) {
|
|
470
|
-
return (
|
|
471
|
-
<article className="provider-card">
|
|
472
|
-
<header>
|
|
473
|
-
<div>
|
|
474
|
-
<p className="eyebrow">{provider.configured ? "configured" : "not configured"}</p>
|
|
475
|
-
<h3>{provider.provider}</h3>
|
|
476
|
-
</div>
|
|
477
|
-
<strong>{provider.available}/{provider.total}</strong>
|
|
478
|
-
</header>
|
|
479
|
-
<div className="repo-card-meta provider-metrics">
|
|
480
|
-
<Detail label="active/idle" value={String(provider.active_or_idle)} />
|
|
481
|
-
<Detail label="rate limited" value={String(provider.rate_limited)} />
|
|
482
|
-
<Detail label="auth errors" value={String(provider.auth_errors)} />
|
|
483
|
-
<Detail label="pending token" value={provider.pending_token ? "yes" : "no"} />
|
|
484
|
-
</div>
|
|
485
|
-
<fieldset className="provider-actions" aria-label={`${provider.provider} planned management controls`}>
|
|
486
|
-
{['Add account', 'Rotate', 'Check', 'Reset cooldowns'].map((action) => <button disabled key={action} type="button">{action}</button>)}
|
|
487
|
-
</fieldset>
|
|
488
|
-
{provider.accounts.length === 0 ? (
|
|
489
|
-
<p className="empty-state compact-empty">No accounts in this pool.</p>
|
|
490
|
-
) : (
|
|
491
|
-
<ul className="provider-account-list">
|
|
492
|
-
{provider.accounts.map((account) => (
|
|
493
|
-
<li key={`${provider.provider}:${account.email_ref}`}>
|
|
494
|
-
<strong>{account.email_ref}</strong>
|
|
495
|
-
<span>{account.status}</span>
|
|
496
|
-
<small>priority {account.priority ?? "default"}</small>
|
|
497
|
-
<small>expires {account.expires_at}</small>
|
|
498
|
-
{account.cooldown_until ? <small>cooldown {account.cooldown_until}</small> : null}
|
|
499
|
-
<button disabled type="button">Manage</button>
|
|
500
|
-
</li>
|
|
501
|
-
))}
|
|
502
|
-
</ul>
|
|
503
|
-
)}
|
|
504
|
-
</article>
|
|
505
|
-
);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
450
|
function Detail({ label, value }: { label: string; value: string }) {
|
|
509
451
|
return (
|
|
510
452
|
<span>
|
|
@@ -1930,9 +1930,12 @@ code {
|
|
|
1930
1930
|
display: flex;
|
|
1931
1931
|
flex-wrap: wrap;
|
|
1932
1932
|
gap: 8px;
|
|
1933
|
+
list-style: none;
|
|
1934
|
+
margin: 0;
|
|
1935
|
+
padding: 0;
|
|
1933
1936
|
}
|
|
1934
1937
|
|
|
1935
|
-
.vault-readiness-strip
|
|
1938
|
+
.vault-readiness-strip li {
|
|
1936
1939
|
background: var(--surface-elevated);
|
|
1937
1940
|
border: 1px solid var(--border);
|
|
1938
1941
|
border-radius: 999px;
|
|
@@ -3561,6 +3564,7 @@ button.managed-toggle:focus-visible {
|
|
|
3561
3564
|
|
|
3562
3565
|
.repo-card-grid,
|
|
3563
3566
|
.provider-grid,
|
|
3567
|
+
.ai-provider-summary-grid,
|
|
3564
3568
|
.setup-card-grid,
|
|
3565
3569
|
.ai-app-grid,
|
|
3566
3570
|
.vault-card-grid,
|
|
@@ -3572,6 +3576,7 @@ button.managed-toggle:focus-visible {
|
|
|
3572
3576
|
|
|
3573
3577
|
.repo-setup-card,
|
|
3574
3578
|
.provider-card,
|
|
3579
|
+
.ai-provider-summary-card,
|
|
3575
3580
|
.setup-target-card,
|
|
3576
3581
|
.ai-app-card,
|
|
3577
3582
|
.vault-feature-card,
|
|
@@ -3587,6 +3592,7 @@ button.managed-toggle:focus-visible {
|
|
|
3587
3592
|
|
|
3588
3593
|
.repo-setup-card header,
|
|
3589
3594
|
.provider-card header,
|
|
3595
|
+
.provider-card-header,
|
|
3590
3596
|
.setup-target-card header,
|
|
3591
3597
|
.ai-app-card header,
|
|
3592
3598
|
.repo-card-footer {
|
|
@@ -4065,12 +4071,211 @@ button.managed-toggle:focus-visible {
|
|
|
4065
4071
|
transform: translateX(18px);
|
|
4066
4072
|
}
|
|
4067
4073
|
|
|
4068
|
-
.provider-
|
|
4074
|
+
.ai-provider-summary-grid {
|
|
4075
|
+
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
4076
|
+
}
|
|
4077
|
+
|
|
4078
|
+
.ai-provider-summary-card span {
|
|
4079
|
+
color: var(--text-muted);
|
|
4080
|
+
font-size: 0.74rem;
|
|
4081
|
+
font-weight: 900;
|
|
4082
|
+
text-transform: uppercase;
|
|
4083
|
+
}
|
|
4084
|
+
|
|
4085
|
+
.ai-provider-summary-card strong {
|
|
4086
|
+
color: var(--accent);
|
|
4087
|
+
display: block;
|
|
4088
|
+
font-size: 1.55rem;
|
|
4089
|
+
letter-spacing: -0.05em;
|
|
4090
|
+
}
|
|
4091
|
+
|
|
4092
|
+
.ai-provider-summary-card small,
|
|
4093
|
+
.provider-card p,
|
|
4094
|
+
.provider-connection-hint {
|
|
4095
|
+
color: var(--text-secondary);
|
|
4096
|
+
}
|
|
4097
|
+
|
|
4098
|
+
.ai-provider-catalog-panel,
|
|
4099
|
+
.provider-section {
|
|
4100
|
+
display: grid;
|
|
4101
|
+
gap: 16px;
|
|
4102
|
+
}
|
|
4103
|
+
|
|
4104
|
+
.provider-toolbar {
|
|
4105
|
+
align-items: center;
|
|
4106
|
+
display: grid;
|
|
4107
|
+
gap: 10px;
|
|
4108
|
+
grid-template-columns: minmax(260px, 1fr) auto;
|
|
4109
|
+
}
|
|
4110
|
+
|
|
4111
|
+
.provider-search {
|
|
4112
|
+
align-items: center;
|
|
4113
|
+
background: var(--surface-elevated);
|
|
4114
|
+
border: 1px solid var(--border);
|
|
4115
|
+
border-radius: 999px;
|
|
4116
|
+
color: var(--text-muted);
|
|
4117
|
+
display: flex;
|
|
4118
|
+
gap: 8px;
|
|
4119
|
+
min-width: 0;
|
|
4120
|
+
padding: 8px 12px;
|
|
4121
|
+
}
|
|
4122
|
+
|
|
4123
|
+
.provider-search input {
|
|
4124
|
+
background: transparent;
|
|
4125
|
+
border: 0;
|
|
4126
|
+
color: var(--text-primary);
|
|
4127
|
+
min-width: 0;
|
|
4128
|
+
outline: 0;
|
|
4129
|
+
width: 100%;
|
|
4130
|
+
}
|
|
4131
|
+
|
|
4132
|
+
.provider-search:focus-within {
|
|
4133
|
+
border-color: var(--border-accent);
|
|
4134
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 16%, transparent);
|
|
4135
|
+
}
|
|
4136
|
+
|
|
4137
|
+
.provider-filter-chips,
|
|
4138
|
+
.provider-chip-row {
|
|
4139
|
+
display: flex;
|
|
4140
|
+
flex-wrap: wrap;
|
|
4141
|
+
gap: 7px;
|
|
4142
|
+
list-style: none;
|
|
4143
|
+
margin: 0;
|
|
4144
|
+
padding: 0;
|
|
4145
|
+
}
|
|
4146
|
+
|
|
4147
|
+
.provider-filter-chips button,
|
|
4148
|
+
.provider-chip,
|
|
4149
|
+
.recommendation-badge,
|
|
4150
|
+
.provider-health {
|
|
4151
|
+
align-items: center;
|
|
4152
|
+
border-radius: 999px;
|
|
4153
|
+
display: inline-flex;
|
|
4154
|
+
gap: 5px;
|
|
4155
|
+
white-space: nowrap;
|
|
4156
|
+
}
|
|
4157
|
+
|
|
4158
|
+
.provider-filter-chips button,
|
|
4159
|
+
.provider-chip {
|
|
4160
|
+
background: var(--surface-elevated);
|
|
4161
|
+
border: 1px solid var(--border);
|
|
4162
|
+
color: var(--text-secondary);
|
|
4163
|
+
font-size: 0.76rem;
|
|
4164
|
+
font-weight: 800;
|
|
4165
|
+
padding: 6px 9px;
|
|
4166
|
+
}
|
|
4167
|
+
|
|
4168
|
+
.provider-filter-chips button.active {
|
|
4169
|
+
border-color: var(--border-accent);
|
|
4170
|
+
color: var(--accent);
|
|
4171
|
+
}
|
|
4172
|
+
|
|
4173
|
+
.provider-filter-chips span {
|
|
4174
|
+
color: var(--text-muted);
|
|
4175
|
+
}
|
|
4176
|
+
|
|
4177
|
+
.provider-section-heading h3 {
|
|
4178
|
+
margin: 0 0 4px;
|
|
4179
|
+
}
|
|
4180
|
+
|
|
4181
|
+
.provider-card-recommended {
|
|
4182
|
+
border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
|
|
4183
|
+
}
|
|
4184
|
+
|
|
4185
|
+
.provider-identity {
|
|
4186
|
+
align-items: flex-start;
|
|
4187
|
+
display: flex;
|
|
4188
|
+
gap: 12px;
|
|
4189
|
+
min-width: 0;
|
|
4190
|
+
}
|
|
4191
|
+
|
|
4192
|
+
.provider-avatar {
|
|
4193
|
+
align-items: center;
|
|
4194
|
+
background: color-mix(in srgb, var(--accent) 14%, transparent);
|
|
4195
|
+
border: 1px solid color-mix(in srgb, var(--accent) 36%, transparent);
|
|
4196
|
+
border-radius: 14px;
|
|
4197
|
+
color: var(--accent);
|
|
4198
|
+
display: inline-flex;
|
|
4199
|
+
flex: 0 0 auto;
|
|
4200
|
+
font-size: 1.2rem;
|
|
4201
|
+
height: 42px;
|
|
4202
|
+
justify-content: center;
|
|
4203
|
+
width: 42px;
|
|
4204
|
+
}
|
|
4205
|
+
|
|
4206
|
+
.provider-avatar-openai,
|
|
4207
|
+
.provider-avatar-zai {
|
|
4208
|
+
background: color-mix(in srgb, var(--success-text) 12%, transparent);
|
|
4209
|
+
border-color: color-mix(in srgb, var(--success-text) 34%, transparent);
|
|
4210
|
+
color: var(--success-text);
|
|
4211
|
+
}
|
|
4212
|
+
|
|
4213
|
+
.provider-avatar-cursor {
|
|
4214
|
+
background: color-mix(in srgb, var(--text-primary) 10%, transparent);
|
|
4215
|
+
color: var(--text-primary);
|
|
4216
|
+
}
|
|
4217
|
+
|
|
4218
|
+
.provider-title-row {
|
|
4219
|
+
align-items: center;
|
|
4220
|
+
display: flex;
|
|
4221
|
+
flex-wrap: wrap;
|
|
4222
|
+
gap: 8px;
|
|
4223
|
+
}
|
|
4224
|
+
|
|
4225
|
+
.provider-title-row h3 {
|
|
4226
|
+
margin: 0;
|
|
4227
|
+
}
|
|
4228
|
+
|
|
4229
|
+
.recommendation-badge {
|
|
4230
|
+
background: color-mix(in srgb, var(--success-text) 14%, transparent);
|
|
4231
|
+
border: 1px solid color-mix(in srgb, var(--success-text) 38%, transparent);
|
|
4232
|
+
color: var(--success-text);
|
|
4233
|
+
font-size: 0.7rem;
|
|
4234
|
+
font-weight: 900;
|
|
4235
|
+
padding: 4px 7px;
|
|
4236
|
+
text-transform: uppercase;
|
|
4237
|
+
}
|
|
4238
|
+
|
|
4239
|
+
.provider-count-stack {
|
|
4240
|
+
align-items: flex-end;
|
|
4241
|
+
display: grid;
|
|
4242
|
+
gap: 6px;
|
|
4243
|
+
justify-items: end;
|
|
4244
|
+
}
|
|
4245
|
+
|
|
4246
|
+
.provider-count-stack > strong {
|
|
4069
4247
|
color: var(--accent);
|
|
4070
4248
|
font-size: 1.8rem;
|
|
4071
4249
|
letter-spacing: -0.06em;
|
|
4072
4250
|
}
|
|
4073
4251
|
|
|
4252
|
+
.provider-health.ready {
|
|
4253
|
+
background: color-mix(in srgb, var(--success-text) 14%, transparent);
|
|
4254
|
+
border-color: color-mix(in srgb, var(--success-text) 38%, transparent);
|
|
4255
|
+
color: var(--success-text);
|
|
4256
|
+
}
|
|
4257
|
+
|
|
4258
|
+
.provider-health.error {
|
|
4259
|
+
background: color-mix(in srgb, var(--notification-error-fg) 14%, transparent);
|
|
4260
|
+
border-color: color-mix(in srgb, var(--notification-error-fg) 38%, transparent);
|
|
4261
|
+
color: var(--notification-error-fg);
|
|
4262
|
+
}
|
|
4263
|
+
|
|
4264
|
+
.provider-health.neutral {
|
|
4265
|
+
background: var(--surface-elevated);
|
|
4266
|
+
border-color: var(--border);
|
|
4267
|
+
color: var(--text-muted);
|
|
4268
|
+
}
|
|
4269
|
+
|
|
4270
|
+
.provider-connection-hint {
|
|
4271
|
+
background: var(--surface-elevated);
|
|
4272
|
+
border: 1px solid var(--border);
|
|
4273
|
+
border-radius: 12px;
|
|
4274
|
+
font-size: 0.84rem;
|
|
4275
|
+
margin: 0;
|
|
4276
|
+
padding: 10px;
|
|
4277
|
+
}
|
|
4278
|
+
|
|
4074
4279
|
.provider-actions {
|
|
4075
4280
|
border: 0;
|
|
4076
4281
|
display: flex;
|
|
@@ -4082,10 +4287,13 @@ button.managed-toggle:focus-visible {
|
|
|
4082
4287
|
|
|
4083
4288
|
.provider-actions button,
|
|
4084
4289
|
.provider-account-list button {
|
|
4290
|
+
align-items: center;
|
|
4085
4291
|
background: var(--surface-elevated);
|
|
4086
4292
|
border: 1px solid var(--border);
|
|
4087
4293
|
border-radius: 999px;
|
|
4088
4294
|
color: var(--text-muted);
|
|
4295
|
+
display: inline-flex;
|
|
4296
|
+
gap: 5px;
|
|
4089
4297
|
padding: 7px 10px;
|
|
4090
4298
|
}
|
|
4091
4299
|
|
|
@@ -4103,7 +4311,7 @@ button.managed-toggle:focus-visible {
|
|
|
4103
4311
|
border-radius: 12px;
|
|
4104
4312
|
display: grid;
|
|
4105
4313
|
gap: 6px;
|
|
4106
|
-
grid-template-columns: minmax(0, 1.5fr) auto auto auto auto;
|
|
4314
|
+
grid-template-columns: minmax(0, 1.5fr) auto auto auto auto auto;
|
|
4107
4315
|
padding: 10px;
|
|
4108
4316
|
}
|
|
4109
4317
|
|
|
@@ -4126,7 +4334,8 @@ button.managed-toggle:focus-visible {
|
|
|
4126
4334
|
}
|
|
4127
4335
|
|
|
4128
4336
|
.managed-app-summary,
|
|
4129
|
-
.managed-app-toolbar
|
|
4337
|
+
.managed-app-toolbar,
|
|
4338
|
+
.provider-toolbar {
|
|
4130
4339
|
grid-template-columns: 1fr;
|
|
4131
4340
|
}
|
|
4132
4341
|
|
package/setup.sh
CHANGED
|
@@ -12,7 +12,7 @@ shopt -s inherit_errexit 2>/dev/null || true
|
|
|
12
12
|
# AI Assistant Server Access Framework Setup Script
|
|
13
13
|
# Helps developers set up the framework for their infrastructure
|
|
14
14
|
#
|
|
15
|
-
# Version: 3.31.
|
|
15
|
+
# Version: 3.31.75
|
|
16
16
|
#
|
|
17
17
|
# Quick Install:
|
|
18
18
|
# npm install -g aidevops && aidevops update (recommended)
|