pi-custom-provider-model 0.1.1
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/LICENSE +21 -0
- package/README.md +175 -0
- package/docs/design.md +54 -0
- package/package.json +53 -0
- package/src/discovery.ts +124 -0
- package/src/index.ts +63 -0
- package/src/limits.ts +75 -0
- package/src/probes.ts +150 -0
- package/src/server.ts +109 -0
- package/src/service.ts +205 -0
- package/src/standalone.ts +15 -0
- package/src/storage.ts +117 -0
- package/src/types.ts +112 -0
- package/web/app.js +589 -0
- package/web/index.html +73 -0
- package/web/style.css +293 -0
package/web/index.html
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<meta name="color-scheme" content="light dark">
|
|
7
|
+
<title>Pi · Provider Manager</title>
|
|
8
|
+
<link rel="stylesheet" href="/style.css">
|
|
9
|
+
<script type="module" src="/app.js"></script>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<header class="topbar">
|
|
13
|
+
<a class="brand" href="/" aria-label="Pi Provider Manager"><span class="pi-mark">π</span><span>Provider Manager<small>FOR PI CODING AGENT</small></span></a>
|
|
14
|
+
<div class="top-actions"><span class="local-badge"><i></i><span data-i18n="local">Local workspace</span></span><select id="language" aria-label="Language"><option value="en">English</option><option value="id">Indonesia</option></select></div>
|
|
15
|
+
</header>
|
|
16
|
+
<div class="layout">
|
|
17
|
+
<aside>
|
|
18
|
+
<div class="sidebar-heading"><h2 data-i18n="providers">Providers</h2><span id="provider-count" class="count">0</span></div>
|
|
19
|
+
<button id="new-provider" class="primary wide" data-i18n="addProvider">+ Add provider</button>
|
|
20
|
+
<nav id="provider-list" aria-label="Providers"></nav>
|
|
21
|
+
<footer class="sidebar-footer"><span class="eyebrow" data-i18n="configuration">CONFIGURATION</span><code id="config-path">…</code><span id="version"></span><a href="https://pi.dev/docs/latest" target="_blank" rel="noreferrer" data-i18n="docs">Official Pi documentation ↗</a></footer>
|
|
22
|
+
</aside>
|
|
23
|
+
<main>
|
|
24
|
+
<div class="page-heading"><div><span class="eyebrow" data-i18n="connections">CONNECTIONS</span><h1 id="editor-title">New provider</h1><p data-i18n="subtitle">Your endpoint. Your models. Native Pi configuration.</p></div><button id="refresh-state" class="quiet" data-i18n="refresh">Reload configuration</button></div>
|
|
25
|
+
<div id="notice" role="status" aria-live="polite" hidden></div>
|
|
26
|
+
<form id="provider-form">
|
|
27
|
+
<section class="card">
|
|
28
|
+
<div class="section-heading"><div><span class="step">01</span><h2 data-i18n="connection">Connection</h2></div><span class="tag" data-i18n="native">Pi native</span></div>
|
|
29
|
+
<div id="readonly-note" class="info" hidden data-i18n="readonly">This provider is managed by Pi or another integration. Create a custom provider to edit here.</div>
|
|
30
|
+
<fieldset id="connection-fields">
|
|
31
|
+
<div class="grid two">
|
|
32
|
+
<label><span data-i18n="providerId">Provider ID</span><input id="provider-id" required maxlength="80" placeholder="my-gateway" autocomplete="off" spellcheck="false"><small data-i18n="idHelp">A unique name used by Pi, e.g. my-gateway.</small></label>
|
|
33
|
+
<label><span data-i18n="protocol">API protocol</span><select id="api"><option value="openai-completions">OpenAI · Chat Completions</option><option value="openai-responses">OpenAI · Responses</option><option value="anthropic-messages">Anthropic · Messages</option></select><small data-i18n="protocolHelp">Select the protocol your endpoint supports.</small></label>
|
|
34
|
+
</div>
|
|
35
|
+
<label class="spaced"><span>Base URL</span><input id="base-url" type="url" required placeholder="https://api.example.com/v1" autocomplete="off" spellcheck="false"><small id="url-help"></small></label>
|
|
36
|
+
<div class="endpoint-preview"><span data-i18n="requestPreview">REQUEST PREVIEW</span><div><b>GET</b><code id="models-url">…</code></div><div><b>POST</b><code id="chat-url">…</code></div></div>
|
|
37
|
+
<div class="grid auth-grid spaced">
|
|
38
|
+
<label><span>API key</span><input id="api-key" type="password" placeholder="sk-…" autocomplete="new-password" spellcheck="false"><small id="key-help"></small></label>
|
|
39
|
+
<div class="auth-status"><span class="eyebrow" data-i18n="credentialSource">CREDENTIAL SOURCE</span><span id="auth-source">not configured</span><button id="remove-key" class="text-danger" type="button" hidden data-i18n="removeKey">Remove saved key</button></div>
|
|
40
|
+
</div>
|
|
41
|
+
<label class="checkbox-line spaced"><input id="auth-header" type="checkbox"><span data-i18n="bearer">Also send an explicit Authorization: Bearer header (Pi authHeader)</span></label>
|
|
42
|
+
<label class="spaced"><span data-i18n="providerSystemRole">Default system prompt role — this provider</span><select id="provider-system-role"><option value="" data-i18n="providerRoleAuto">Pi automatic detection</option><option value="system" data-i18n="roleSystem">system — compatible gateways</option><option value="developer" data-i18n="roleDeveloper">developer — when reasoning is enabled</option></select><small data-i18n="providerRoleHelp">For OpenAI-compatible APIs. Applies to existing and newly added models that follow the provider setting. Model-specific overrides take priority.</small></label>
|
|
43
|
+
<p id="advanced-note" class="hint" hidden data-i18n="advanced">Existing advanced fields and custom headers are preserved. Model-level overrides can change the effective request.</p>
|
|
44
|
+
</fieldset>
|
|
45
|
+
<div class="action-row"><button id="test-connection" type="button" data-i18n="testConnection">Test connection</button><button id="fetch-models" type="button" class="primary" data-i18n="fetchModels">Fetch models ↗</button><span class="hint" data-i18n="testHint">Checks the model-list endpoint. No generation request.</span></div>
|
|
46
|
+
<div id="diagnostics" role="status" class="diagnostics" hidden></div>
|
|
47
|
+
</section>
|
|
48
|
+
<section class="card">
|
|
49
|
+
<div class="section-heading"><div><span class="step">02</span><h2 data-i18n="models">Models</h2><span id="model-count" class="count">0</span></div><div class="action-row"><button id="fill-limits" type="button" class="quiet" data-i18n="fillLimits">Fill missing limits</button><button id="add-model" type="button" class="quiet" data-i18n="addModel">+ Manual model</button></div></div>
|
|
50
|
+
<p class="hint" data-i18n="modelHint">Select models to save. A listed ID does not guarantee chat, vision or tool support.</p>
|
|
51
|
+
<p class="hint" data-i18n="limitsHelp">Context/output defaults come from endpoint metadata, then exact, unambiguous matches in Pi's bundled catalog. Gateway limits can differ. Existing values are kept; save the provider to persist filled defaults.</p>
|
|
52
|
+
<p class="hint" data-i18n="probeHelp">Test chat sends one short request. Check capabilities runs chat, a two-turn test tool, reasoning and a generated image (up to 5 requests). Uses API tokens: up to 256 output tokens per request, 2,048 for reasoning. Results appear below each model.</p>
|
|
53
|
+
<div class="model-toolbar"><input id="model-search" type="search" placeholder="Search model IDs…" aria-label="Search models"><label class="checkbox-line"><input id="select-all" type="checkbox"><span data-i18n="selectAll">Select visible</span></label><span id="selected-count" class="hint">0 selected</span></div>
|
|
54
|
+
<div class="table-scroll"><table><thead><tr><th class="check-col"></th><th data-i18n="modelId">Model ID</th><th data-i18n="tokenLimits">Token limits</th><th data-i18n="input">Input</th><th data-i18n="actions">Actions</th></tr></thead><tbody id="models-body"></tbody></table></div>
|
|
55
|
+
<div id="empty-models" class="empty"><span class="empty-icon">◇</span><h3 data-i18n="noModels">Connect your first model</h3><p data-i18n="noModelsHint">Fetch models from your endpoint, or add a model ID manually.</p></div>
|
|
56
|
+
</section>
|
|
57
|
+
<div class="save-bar"><div><strong id="save-summary"></strong><small data-i18n="saveHelp">Models → models.json · Key → auth.json</small></div><div class="action-row"><button id="delete-provider" type="button" class="text-danger" hidden data-i18n="deleteProvider">Delete provider</button><button id="save-provider" type="submit" class="primary" data-i18n="save">Save provider</button></div></div>
|
|
58
|
+
</form>
|
|
59
|
+
<p class="footnote" data-i18n="footnote">Configuration is saved locally. Open /model in Pi to select a saved model. “Set default” applies to new Pi launches.</p>
|
|
60
|
+
</main>
|
|
61
|
+
</div>
|
|
62
|
+
<dialog id="model-dialog"><form id="model-form"><div class="section-heading"><h2 data-i18n="editModel">Model settings</h2><button id="close-dialog" type="button" class="quiet" aria-label="Close">×</button></div>
|
|
63
|
+
<label><span data-i18n="modelId">Model ID</span><input id="edit-model-id" required maxlength="300" spellcheck="false"></label>
|
|
64
|
+
<label class="spaced"><span data-i18n="displayName">Display name (optional)</span><input id="edit-model-name"></label>
|
|
65
|
+
<div class="grid two spaced"><label><span data-i18n="context">Context window</span><input id="edit-context" type="number" min="1" step="1" placeholder="Pi default: 128000" aria-describedby="edit-context-source"><small id="edit-context-source"></small></label><label><span data-i18n="maxOutput">Max output tokens</span><input id="edit-max" type="number" min="1" step="1" placeholder="Pi default: 16384" aria-describedby="edit-max-source"><small id="edit-max-source"></small></label></div>
|
|
66
|
+
<button id="use-model-limits" type="button" class="quiet spaced" data-i18n="useLimits">Use detected limits</button>
|
|
67
|
+
<p id="limit-lookup-status" class="hint" role="status"></p>
|
|
68
|
+
<div class="grid two spaced"><label><span data-i18n="vision">Vision input</span><select id="edit-vision"><option value="" data-i18n="defaultText">Pi default (text)</option><option value="no" data-i18n="textOnly">Text only</option><option value="yes" data-i18n="textImage">Text + image</option></select></label><label><span data-i18n="reasoning">Reasoning support</span><select id="edit-reasoning"><option value="" data-i18n="defaultOff">Pi default (off)</option><option value="no" data-i18n="off">Off</option><option value="yes" data-i18n="on">On</option></select></label></div>
|
|
69
|
+
<label class="spaced"><span data-i18n="systemRole">System prompt role (OpenAI)</span><select id="edit-system-role"><option value="" data-i18n="roleAuto">Follow Pi / provider settings</option><option value="system" data-i18n="roleSystem">system — compatible gateways</option><option value="developer" data-i18n="roleDeveloper">developer — when reasoning is enabled</option></select><small data-i18n="roleHelp">For gateways that reject the developer role with HTTP 400/422, choose system. Saved as compat.supportsDeveloperRole; reasoning remains enabled.</small></label>
|
|
70
|
+
<p class="hint" data-i18n="metadataHelp">Leave unknown values at Pi defaults. Enabling a capability here does not add it to the upstream model.</p><button type="submit" class="primary wide" data-i18n="applyModel">Apply model settings</button>
|
|
71
|
+
</form></dialog>
|
|
72
|
+
</body>
|
|
73
|
+
</html>
|
package/web/style.css
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
3
|
+
font-size: 100%;
|
|
4
|
+
font-synthesis: none;
|
|
5
|
+
color: #202c2b;
|
|
6
|
+
background: #f5f7f7;
|
|
7
|
+
--surface: #fff;
|
|
8
|
+
--line: #e1e7e5;
|
|
9
|
+
--muted: #667975;
|
|
10
|
+
--accent: #147b67;
|
|
11
|
+
--soft: #eaf4f0;
|
|
12
|
+
--danger: #b34949;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
* { box-sizing: border-box; }
|
|
16
|
+
body { margin: 0; font-size: 1rem; line-height: 1.6; }
|
|
17
|
+
button, input, select { font: inherit; }
|
|
18
|
+
small { font-size: inherit; }
|
|
19
|
+
button, a, input, select { outline-offset: 4px; }
|
|
20
|
+
|
|
21
|
+
button {
|
|
22
|
+
cursor: pointer;
|
|
23
|
+
border: 1px solid var(--line);
|
|
24
|
+
background: var(--surface);
|
|
25
|
+
color: inherit;
|
|
26
|
+
border-radius: 8px;
|
|
27
|
+
padding: 10px 16px;
|
|
28
|
+
min-height: 44px;
|
|
29
|
+
line-height: 1.4;
|
|
30
|
+
font-weight: 550;
|
|
31
|
+
transition: background .12s;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
button:hover { background: var(--soft); }
|
|
35
|
+
button:disabled { opacity: .45; cursor: not-allowed; }
|
|
36
|
+
.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
37
|
+
.primary:hover { background: #0d6655; }
|
|
38
|
+
.quiet { background: transparent; }
|
|
39
|
+
.wide { width: 100%; }
|
|
40
|
+
.text-danger { border: 0; background: transparent; color: var(--danger); padding: 8px; }
|
|
41
|
+
|
|
42
|
+
.topbar {
|
|
43
|
+
min-height: 96px;
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
justify-content: space-between;
|
|
47
|
+
gap: 24px;
|
|
48
|
+
padding: 18px 32px;
|
|
49
|
+
border-bottom: 1px solid var(--line);
|
|
50
|
+
background: var(--surface);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.brand {
|
|
54
|
+
text-decoration: none;
|
|
55
|
+
color: inherit;
|
|
56
|
+
display: flex;
|
|
57
|
+
gap: 14px;
|
|
58
|
+
align-items: center;
|
|
59
|
+
font-size: 1.125rem;
|
|
60
|
+
font-weight: 650;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.brand small {
|
|
64
|
+
display: block;
|
|
65
|
+
font-size: 1rem;
|
|
66
|
+
letter-spacing: .04em;
|
|
67
|
+
color: var(--muted);
|
|
68
|
+
font-weight: 400;
|
|
69
|
+
margin-top: 4px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.pi-mark { font-family: Georgia, serif; font-size: 2.5rem; line-height: 1; color: var(--accent); }
|
|
73
|
+
.top-actions { display: flex; align-items: center; gap: 24px; }
|
|
74
|
+
.local-badge { display: flex; align-items: center; gap: 9px; color: var(--muted); }
|
|
75
|
+
.local-badge i { width: 8px; height: 8px; flex-shrink: 0; background: #27836c; border-radius: 50%; }
|
|
76
|
+
.top-actions select { width: auto; }
|
|
77
|
+
.layout { display: grid; grid-template-columns: 288px minmax(0, 1fr); min-height: calc(100vh - 96px); }
|
|
78
|
+
|
|
79
|
+
aside {
|
|
80
|
+
padding: 28px 20px;
|
|
81
|
+
border-right: 1px solid var(--line);
|
|
82
|
+
display: flex;
|
|
83
|
+
flex-direction: column;
|
|
84
|
+
min-width: 0;
|
|
85
|
+
background: var(--surface);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.sidebar-heading { display: flex; justify-content: space-between; align-items: center; margin-bottom: 18px; }
|
|
89
|
+
h2 { margin: 0; font-size: 1.125rem; font-weight: 650; }
|
|
90
|
+
.count { border-radius: 6px; background: var(--soft); color: var(--accent); padding: 3px 9px; }
|
|
91
|
+
nav { margin-top: 22px; display: grid; gap: 8px; }
|
|
92
|
+
nav button { text-align: left; border-color: transparent; background: transparent; min-width: 0; padding: 14px; }
|
|
93
|
+
nav button.active { background: var(--soft); color: var(--accent); border-color: #c9e0d8; }
|
|
94
|
+
nav button strong { display: block; overflow-wrap: anywhere; }
|
|
95
|
+
nav button small { display: block; color: var(--muted); margin-top: 6px; overflow-wrap: anywhere; line-height: 1.6; }
|
|
96
|
+
|
|
97
|
+
.sidebar-footer {
|
|
98
|
+
margin-top: auto;
|
|
99
|
+
padding-top: 48px;
|
|
100
|
+
display: flex;
|
|
101
|
+
flex-direction: column;
|
|
102
|
+
gap: 16px;
|
|
103
|
+
color: var(--muted);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.sidebar-footer code { overflow-wrap: anywhere; }
|
|
107
|
+
.sidebar-footer a { color: var(--accent); text-decoration: none; }
|
|
108
|
+
main { padding: 38px clamp(24px, 3vw, 56px) 32px; max-width: 1500px; width: 100%; min-width: 0; margin: auto; }
|
|
109
|
+
.eyebrow { letter-spacing: .06em; color: var(--muted); font-weight: 600; }
|
|
110
|
+
.page-heading { display: flex; justify-content: space-between; gap: 24px; align-items: center; margin-bottom: 28px; }
|
|
111
|
+
.page-heading > div { min-width: 0; }
|
|
112
|
+
.page-heading button { flex-shrink: 0; }
|
|
113
|
+
h1 { font-size: 2rem; line-height: 1.3; letter-spacing: -.025em; font-weight: 650; margin: 10px 0; overflow-wrap: anywhere; }
|
|
114
|
+
.page-heading p { color: var(--muted); margin: 0; }
|
|
115
|
+
|
|
116
|
+
.card {
|
|
117
|
+
background: var(--surface);
|
|
118
|
+
border: 1px solid var(--line);
|
|
119
|
+
border-radius: 12px;
|
|
120
|
+
padding: 28px;
|
|
121
|
+
margin-bottom: 24px;
|
|
122
|
+
box-shadow: 0 2px 4px #10282203;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.section-heading, .section-heading > div { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
|
126
|
+
.section-heading { justify-content: space-between; margin-bottom: 24px; }
|
|
127
|
+
.step { color: var(--accent); background: var(--soft); border-radius: 5px; padding: 5px 9px; font-weight: 600; }
|
|
128
|
+
.tag { border: 1px solid var(--line); padding: 4px 10px; border-radius: 5px; color: var(--muted); }
|
|
129
|
+
fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }
|
|
130
|
+
.grid { display: grid; gap: 24px; }
|
|
131
|
+
.two { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
|
|
132
|
+
label { display: flex; flex-direction: column; gap: 9px; font-weight: 550; min-width: 0; }
|
|
133
|
+
|
|
134
|
+
input, select {
|
|
135
|
+
border: 1px solid #d7e0dc;
|
|
136
|
+
border-radius: 7px;
|
|
137
|
+
background: var(--surface);
|
|
138
|
+
color: inherit;
|
|
139
|
+
padding: 12px;
|
|
140
|
+
width: 100%;
|
|
141
|
+
min-width: 0;
|
|
142
|
+
min-height: 46px;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
input:focus, select:focus { outline: 2px solid #81bda7; outline-offset: 1px; }
|
|
146
|
+
input:disabled { background: #f2f5f4; color: var(--muted); }
|
|
147
|
+
label small, .hint { color: var(--muted); font-weight: 400; line-height: 1.65; }
|
|
148
|
+
.spaced { margin-top: 24px; }
|
|
149
|
+
|
|
150
|
+
.endpoint-preview {
|
|
151
|
+
margin-top: 18px;
|
|
152
|
+
padding: 18px;
|
|
153
|
+
background: #f7f9f8;
|
|
154
|
+
border: 1px solid #edf0ee;
|
|
155
|
+
border-radius: 7px;
|
|
156
|
+
overflow-wrap: anywhere;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.endpoint-preview > span { display: block; color: var(--muted); letter-spacing: .04em; margin-bottom: 14px; }
|
|
160
|
+
.endpoint-preview div { display: grid; grid-template-columns: 58px minmax(0, 1fr); gap: 12px; }
|
|
161
|
+
.endpoint-preview div + div { margin-top: 12px; }
|
|
162
|
+
.endpoint-preview b { color: var(--accent); }
|
|
163
|
+
code { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 1rem; }
|
|
164
|
+
.auth-grid { grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); }
|
|
165
|
+
.auth-status { display: flex; flex-direction: column; gap: 12px; border-left: 1px solid var(--line); padding: 2px 0 0 24px; align-items: flex-start; overflow-wrap: anywhere; }
|
|
166
|
+
.auth-status button { padding: 4px 0; }
|
|
167
|
+
.checkbox-line { flex-direction: row; align-items: flex-start; gap: 12px; font-weight: 400; line-height: 1.6; }
|
|
168
|
+
.checkbox-line input, input[type=checkbox] { width: 20px; height: 20px; min-height: 20px; accent-color: var(--accent); flex-shrink: 0; margin-top: 3px; }
|
|
169
|
+
.action-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
|
170
|
+
.card > .action-row { padding-top: 24px; margin-top: 24px; border-top: 1px solid var(--line); }
|
|
171
|
+
.card > .action-row > .hint { flex-basis: 100%; }
|
|
172
|
+
.model-toolbar { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; margin: 24px 0; }
|
|
173
|
+
.model-toolbar > input { flex: 1 1 240px; max-width: 360px; }
|
|
174
|
+
.model-toolbar label { white-space: nowrap; }
|
|
175
|
+
.model-toolbar > .hint { margin-left: auto; white-space: nowrap; }
|
|
176
|
+
.table-scroll { overflow-x: auto; }
|
|
177
|
+
table { border-collapse: collapse; width: 100%; text-align: left; }
|
|
178
|
+
th { padding: 14px 12px; font-weight: 600; color: var(--muted); border-bottom: 1px solid var(--line); }
|
|
179
|
+
td { padding: 18px 12px; border-bottom: 1px solid var(--line); vertical-align: top; }
|
|
180
|
+
td:nth-child(2) { min-width: 180px; overflow-wrap: anywhere; }
|
|
181
|
+
td small { display: block; color: var(--muted); margin-top: 6px; }
|
|
182
|
+
.model-limits { overflow-wrap: anywhere; }
|
|
183
|
+
.model-limit + .model-limit { margin-top: 16px; }
|
|
184
|
+
.model-limit strong { font-weight: 550; }
|
|
185
|
+
.check-col { width: 44px; }
|
|
186
|
+
td .action-row { gap: 6px; min-width: 150px; }
|
|
187
|
+
td button { padding: 9px 10px; }
|
|
188
|
+
.default-badge { color: var(--accent); padding: 8px 4px; }
|
|
189
|
+
.model-row > td:last-child { width: 38%; }
|
|
190
|
+
.model-results > td { padding: 0 0 24px; }
|
|
191
|
+
.probe-panel { border: 1px solid #c9e0d8; background: #f7faf8; border-radius: 10px; padding: 24px; overflow-wrap: anywhere; }
|
|
192
|
+
.probe-header { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px; margin-bottom: 20px; }
|
|
193
|
+
.probe-header h3 { font-size: 1.125rem; margin: 0 0 6px; }
|
|
194
|
+
.probe-progress { color: var(--muted); }
|
|
195
|
+
.probe-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
|
|
196
|
+
.probe-check { padding: 18px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); min-width: 0; }
|
|
197
|
+
.probe-check-heading { display: flex; align-items: flex-start; justify-content: space-between; flex-wrap: wrap; gap: 10px; }
|
|
198
|
+
.probe-check p { margin: 16px 0 0; }
|
|
199
|
+
.probe-badge { display: inline-block; border-radius: 6px; background: #edf1ef; color: #52655e; padding: 4px 10px; }
|
|
200
|
+
.probe-badge.supported { background: #e4f4eb; color: #21634e; }
|
|
201
|
+
.probe-badge.inconclusive { background: #fff6df; color: #765718; }
|
|
202
|
+
.probe-badge.failed { background: #fff1ef; color: #9f3e3e; }
|
|
203
|
+
.probe-badge.running { background: #e7f0ff; color: #2d5594; }
|
|
204
|
+
.probe-meta { color: var(--muted); }
|
|
205
|
+
.probe-url { display: block; margin-top: 12px; overflow-wrap: anywhere; }
|
|
206
|
+
.probe-applied { color: var(--accent); }
|
|
207
|
+
.probe-summary { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
|
|
208
|
+
.probe-summary .probe-badge { padding: 3px 8px; }
|
|
209
|
+
.empty { text-align: center; padding: 40px 16px; color: var(--muted); }
|
|
210
|
+
.empty-icon { font-size: 2.5rem; color: #6da48f; }
|
|
211
|
+
.empty h3 { color: #445f54; font-size: 1.125rem; font-weight: 550; }
|
|
212
|
+
.save-bar { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 8px 0 0; flex-wrap: wrap; }
|
|
213
|
+
.save-bar strong { font-weight: 600; }
|
|
214
|
+
.save-bar small { display: block; color: var(--muted); margin-top: 8px; }
|
|
215
|
+
.footnote { color: var(--muted); line-height: 1.7; margin-top: 24px; }
|
|
216
|
+
|
|
217
|
+
.diagnostics, #notice, .info {
|
|
218
|
+
padding: 16px 18px;
|
|
219
|
+
border-radius: 8px;
|
|
220
|
+
line-height: 1.7;
|
|
221
|
+
background: var(--soft);
|
|
222
|
+
color: #21634e;
|
|
223
|
+
margin-top: 20px;
|
|
224
|
+
overflow-wrap: anywhere;
|
|
225
|
+
white-space: pre-wrap;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.diagnostics.error, #notice.error { background: #fff1ef; color: #9f3e3e; }
|
|
229
|
+
#notice, .info { margin: 0 0 24px; }
|
|
230
|
+
.diagnostics.warning, #notice.warning { background: #fff6df; color: #816522; }
|
|
231
|
+
|
|
232
|
+
dialog {
|
|
233
|
+
border: 1px solid var(--line);
|
|
234
|
+
border-radius: 14px;
|
|
235
|
+
padding: 28px;
|
|
236
|
+
width: min(640px, 94vw);
|
|
237
|
+
background: var(--surface);
|
|
238
|
+
color: inherit;
|
|
239
|
+
box-shadow: 0 30px 80px #0003;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
dialog::backdrop { background: #172e2766; backdrop-filter: blur(3px); }
|
|
243
|
+
dialog .section-heading { margin-bottom: 24px; }
|
|
244
|
+
dialog .section-heading button { font-size: 1.5rem; padding: 0 10px; }
|
|
245
|
+
dialog .hint { margin: 24px 0; }
|
|
246
|
+
[hidden] { display: none !important; }
|
|
247
|
+
|
|
248
|
+
@media (max-width: 1100px) {
|
|
249
|
+
.layout { grid-template-columns: 248px minmax(0, 1fr); }
|
|
250
|
+
main { padding: 28px 24px; }
|
|
251
|
+
.page-heading { align-items: flex-start; flex-direction: column; }
|
|
252
|
+
.card { padding: 24px; }
|
|
253
|
+
.two, .auth-grid { grid-template-columns: minmax(0, 1fr); }
|
|
254
|
+
.auth-status { border-left: 0; padding-left: 0; }
|
|
255
|
+
.probe-grid { grid-template-columns: minmax(0, 1fr); }
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
@media (max-width: 760px) {
|
|
259
|
+
.topbar { padding: 20px 18px; flex-wrap: wrap; gap: 18px; }
|
|
260
|
+
.top-actions { gap: 16px; flex-wrap: wrap; }
|
|
261
|
+
.layout { display: block; }
|
|
262
|
+
aside { padding: 20px 18px; border-right: 0; border-bottom: 1px solid var(--line); }
|
|
263
|
+
.sidebar-heading { margin-bottom: 16px; }
|
|
264
|
+
.sidebar-footer { display: none; }
|
|
265
|
+
nav { display: flex; overflow-x: auto; margin-top: 16px; }
|
|
266
|
+
nav button { flex: 0 0 240px; }
|
|
267
|
+
.layout aside > .wide { width: auto; }
|
|
268
|
+
main { padding: 28px 16px; }
|
|
269
|
+
.card { padding: 24px 16px; }
|
|
270
|
+
.save-bar { align-items: flex-start; flex-direction: column; }
|
|
271
|
+
.save-bar > .action-row { width: 100%; }
|
|
272
|
+
h1 { font-size: 1.75rem; }
|
|
273
|
+
.endpoint-preview { padding: 16px 12px; }
|
|
274
|
+
.endpoint-preview div { grid-template-columns: minmax(0, 1fr); gap: 4px; }
|
|
275
|
+
.model-toolbar > input { max-width: none; flex-basis: 100%; }
|
|
276
|
+
.section-heading { gap: 16px; }
|
|
277
|
+
dialog { padding: 24px 18px; }
|
|
278
|
+
.table-scroll { overflow: visible; }
|
|
279
|
+
table, tbody { display: block; }
|
|
280
|
+
thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
|
|
281
|
+
.model-row { display: grid; grid-template-columns: 32px minmax(0, 1fr); padding: 20px 0; border-top: 1px solid var(--line); gap: 14px 8px; }
|
|
282
|
+
.model-row > td { display: block; border: 0; padding: 0; min-width: 0; }
|
|
283
|
+
.model-row > td:nth-child(n+3) { grid-column: 2; }
|
|
284
|
+
.model-row > td:last-child { width: auto; }
|
|
285
|
+
.model-row > td[data-label]::before { content: attr(data-label) ": "; font-weight: 600; }
|
|
286
|
+
.model-results, .model-results > td { display: block; width: 100%; }
|
|
287
|
+
.probe-panel { padding: 18px 12px; }
|
|
288
|
+
.probe-check { padding: 16px 12px; }
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
@media (prefers-reduced-motion: reduce) {
|
|
292
|
+
* { transition: none !important; }
|
|
293
|
+
}
|