aimeat 2.4.0 → 2.6.0
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/dist/.env.example +33 -0
- package/dist/build-stamp.json +13 -0
- package/dist/locales/en.json +186 -20
- package/dist/locales/fi.json +186 -20
- package/dist/public/components/VoiceRecorder.js +116 -0
- package/dist/public/connection-done.html +53 -0
- package/dist/public/css/components/voice-recorder.css +66 -0
- package/dist/public/css/views/inbox.css +41 -0
- package/dist/public/css/views/profile.css +171 -95
- package/dist/public/css/views/transparency.css +5 -0
- package/dist/public/js/services/audio-recorder.js +171 -0
- package/dist/public/js/services/messages.js +21 -2
- package/dist/public/spa.html +3 -0
- package/dist/public/views/landing.js +4 -0
- package/dist/public/views/profile/access-tab/connections.js +264 -0
- package/dist/public/views/profile/access-tab.js +2 -0
- package/dist/public/views/profile/ai-transparency-card.js +22 -17
- package/dist/public/views/profile/inbox-tab/components.js +60 -10
- package/dist/public/views/profile/inbox-tab/helpers.js +33 -0
- package/dist/public/views/profile/inbox-tab/panels.js +6 -1
- package/dist/public/views/profile/inbox-tab/use-voice.js +50 -0
- package/dist/public/views/profile/inbox-tab/voice-parts.js +111 -0
- package/dist/public/views/profile/inbox-tab.js +18 -24
- package/dist/public/views/profile/openrouter/budget-panel.js +224 -0
- package/dist/public/views/profile/openrouter/model-picker.js +150 -0
- package/dist/public/views/profile/openrouter/pricing.js +139 -0
- package/dist/public/views/profile/openrouter-settings.js +425 -488
- package/dist/public/views/transparency.js +50 -0
- package/dist/scripts/build-sdk-libs.d.ts.map +1 -1
- package/dist/scripts/build-sdk-libs.js +1 -0
- package/dist/scripts/build-sdk-libs.js.map +1 -1
- package/dist/scripts/check-ai-disclosure.d.ts.map +1 -1
- package/dist/scripts/check-ai-disclosure.js +182 -0
- package/dist/scripts/check-ai-disclosure.js.map +1 -1
- package/dist/src/cli/connect/ai-provenance-carry.d.ts +247 -0
- package/dist/src/cli/connect/ai-provenance-carry.d.ts.map +1 -0
- package/dist/src/cli/connect/ai-provenance-carry.js +334 -0
- package/dist/src/cli/connect/ai-provenance-carry.js.map +1 -0
- package/dist/src/cli/connect/api-client.d.ts +11 -0
- package/dist/src/cli/connect/api-client.d.ts.map +1 -1
- package/dist/src/cli/connect/api-client.js +2 -0
- package/dist/src/cli/connect/api-client.js.map +1 -1
- package/dist/src/cli/connect/mcp/local-server.d.ts.map +1 -1
- package/dist/src/cli/connect/mcp/local-server.js +11 -0
- package/dist/src/cli/connect/mcp/local-server.js.map +1 -1
- package/dist/src/cli/connect/mcp/tools/agent-messages.d.ts +2 -0
- package/dist/src/cli/connect/mcp/tools/agent-messages.d.ts.map +1 -1
- package/dist/src/cli/connect/mcp/tools/agent-messages.js +5 -2
- package/dist/src/cli/connect/mcp/tools/agent-messages.js.map +1 -1
- package/dist/src/cli/connect/mcp/tools/agent-tasks.d.ts +2 -0
- package/dist/src/cli/connect/mcp/tools/agent-tasks.d.ts.map +1 -1
- package/dist/src/cli/connect/mcp/tools/agent-tasks.js +5 -2
- package/dist/src/cli/connect/mcp/tools/agent-tasks.js.map +1 -1
- package/dist/src/cli/connect/mcp/tools/apps.d.ts +3 -0
- package/dist/src/cli/connect/mcp/tools/apps.d.ts.map +1 -1
- package/dist/src/cli/connect/mcp/tools/apps.js +13 -5
- package/dist/src/cli/connect/mcp/tools/apps.js.map +1 -1
- package/dist/src/cli/connect/mcp/tools/boards.d.ts +3 -0
- package/dist/src/cli/connect/mcp/tools/boards.d.ts.map +1 -1
- package/dist/src/cli/connect/mcp/tools/boards.js +5 -2
- package/dist/src/cli/connect/mcp/tools/boards.js.map +1 -1
- package/dist/src/cli/connect/mcp/tools/core.d.ts +5 -0
- package/dist/src/cli/connect/mcp/tools/core.d.ts.map +1 -1
- package/dist/src/cli/connect/mcp/tools/core.js +25 -5
- package/dist/src/cli/connect/mcp/tools/core.js.map +1 -1
- package/dist/src/cli/connect/mcp/tools/dm-messages.d.ts +2 -0
- package/dist/src/cli/connect/mcp/tools/dm-messages.d.ts.map +1 -1
- package/dist/src/cli/connect/mcp/tools/dm-messages.js +11 -6
- package/dist/src/cli/connect/mcp/tools/dm-messages.js.map +1 -1
- package/dist/src/cli/connect/mcp/tools/exchange.d.ts +2 -0
- package/dist/src/cli/connect/mcp/tools/exchange.d.ts.map +1 -1
- package/dist/src/cli/connect/mcp/tools/exchange.js +8 -2
- package/dist/src/cli/connect/mcp/tools/exchange.js.map +1 -1
- package/dist/src/cli/connect/mcp/tools/knowledge.d.ts +3 -0
- package/dist/src/cli/connect/mcp/tools/knowledge.d.ts.map +1 -1
- package/dist/src/cli/connect/mcp/tools/knowledge.js +7 -3
- package/dist/src/cli/connect/mcp/tools/knowledge.js.map +1 -1
- package/dist/src/cli/connect/mcp/tools/organisms.d.ts +2 -0
- package/dist/src/cli/connect/mcp/tools/organisms.d.ts.map +1 -1
- package/dist/src/cli/connect/mcp/tools/organisms.js +7 -2
- package/dist/src/cli/connect/mcp/tools/organisms.js.map +1 -1
- package/dist/src/cli/connect/mcp/tools/workspaces.d.ts +3 -0
- package/dist/src/cli/connect/mcp/tools/workspaces.d.ts.map +1 -1
- package/dist/src/cli/connect/mcp/tools/workspaces.js +8 -2
- package/dist/src/cli/connect/mcp/tools/workspaces.js.map +1 -1
- package/dist/src/cli/connect/tool-call-defs-core.d.ts +3 -0
- package/dist/src/cli/connect/tool-call-defs-core.d.ts.map +1 -1
- package/dist/src/cli/connect/tool-call-defs-core.js +7 -0
- package/dist/src/cli/connect/tool-call-defs-core.js.map +1 -1
- package/dist/src/cli/connect/tool-call.d.ts +3 -0
- package/dist/src/cli/connect/tool-call.d.ts.map +1 -1
- package/dist/src/cli/connect/tool-call.js +11 -1
- package/dist/src/cli/connect/tool-call.js.map +1 -1
- package/dist/src/config-types.d.ts +14 -0
- package/dist/src/config-types.d.ts.map +1 -1
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +10 -0
- package/dist/src/config.js.map +1 -1
- package/dist/src/data/public-pages.d.ts +12 -0
- package/dist/src/data/public-pages.d.ts.map +1 -1
- package/dist/src/data/public-pages.js +12 -0
- package/dist/src/data/public-pages.js.map +1 -1
- package/dist/src/generated/api-types.d.ts +969 -85
- package/dist/src/generated/api-types.d.ts.map +1 -1
- package/dist/src/mcp/ai-provenance-input.d.ts +45 -2
- package/dist/src/mcp/ai-provenance-input.d.ts.map +1 -1
- package/dist/src/mcp/ai-provenance-input.js +48 -2
- package/dist/src/mcp/ai-provenance-input.js.map +1 -1
- package/dist/src/mcp/apps.d.ts +7 -0
- package/dist/src/mcp/apps.d.ts.map +1 -1
- package/dist/src/mcp/apps.js +20 -2
- package/dist/src/mcp/apps.js.map +1 -1
- package/dist/src/models/ai-provenance-schemas.d.ts +7 -1
- package/dist/src/models/ai-provenance-schemas.d.ts.map +1 -1
- package/dist/src/models/ai-provenance-schemas.js +11 -1
- package/dist/src/models/ai-provenance-schemas.js.map +1 -1
- package/dist/src/models/connection-schemas.d.ts +203 -0
- package/dist/src/models/connection-schemas.d.ts.map +1 -0
- package/dist/src/models/connection-schemas.js +33 -0
- package/dist/src/models/connection-schemas.js.map +1 -0
- package/dist/src/models/message-schemas.d.ts +40 -0
- package/dist/src/models/message-schemas.d.ts.map +1 -1
- package/dist/src/models/message-schemas.js +23 -0
- package/dist/src/models/message-schemas.js.map +1 -1
- package/dist/src/routes/ai-provenance.d.ts +6 -0
- package/dist/src/routes/ai-provenance.d.ts.map +1 -1
- package/dist/src/routes/ai-provenance.js +34 -1
- package/dist/src/routes/ai-provenance.js.map +1 -1
- package/dist/src/routes/ai-transparency.d.ts +6 -1
- package/dist/src/routes/ai-transparency.d.ts.map +1 -1
- package/dist/src/routes/ai-transparency.js +17 -3
- package/dist/src/routes/ai-transparency.js.map +1 -1
- package/dist/src/routes/ai.d.ts +5 -0
- package/dist/src/routes/ai.d.ts.map +1 -1
- package/dist/src/routes/ai.js +83 -0
- package/dist/src/routes/ai.js.map +1 -1
- package/dist/src/routes/app-grants.d.ts.map +1 -1
- package/dist/src/routes/app-grants.js +4 -0
- package/dist/src/routes/app-grants.js.map +1 -1
- package/dist/src/routes/apps/drafts.d.ts +3 -0
- package/dist/src/routes/apps/drafts.d.ts.map +1 -1
- package/dist/src/routes/apps/drafts.js +13 -0
- package/dist/src/routes/apps/drafts.js.map +1 -1
- package/dist/src/routes/apps/publish.d.ts +5 -0
- package/dist/src/routes/apps/publish.d.ts.map +1 -1
- package/dist/src/routes/apps/publish.js +21 -5
- package/dist/src/routes/apps/publish.js.map +1 -1
- package/dist/src/routes/connections.d.ts +35 -0
- package/dist/src/routes/connections.d.ts.map +1 -0
- package/dist/src/routes/connections.js +406 -0
- package/dist/src/routes/connections.js.map +1 -0
- package/dist/src/routes/libs.js +1 -1
- package/dist/src/routes/libs.js.map +1 -1
- package/dist/src/routes/messages.d.ts +5 -0
- package/dist/src/routes/messages.d.ts.map +1 -1
- package/dist/src/routes/messages.js +92 -0
- package/dist/src/routes/messages.js.map +1 -1
- package/dist/src/routes/openrouter.d.ts +6 -1
- package/dist/src/routes/openrouter.d.ts.map +1 -1
- package/dist/src/routes/openrouter.js +37 -7
- package/dist/src/routes/openrouter.js.map +1 -1
- package/dist/src/routes/upload.d.ts +5 -0
- package/dist/src/routes/upload.d.ts.map +1 -1
- package/dist/src/routes/upload.js +32 -0
- package/dist/src/routes/upload.js.map +1 -1
- package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
- package/dist/src/server-bootstrap/routes-loader.js +2 -0
- package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
- package/dist/src/services/ai-completion.d.ts +48 -0
- package/dist/src/services/ai-completion.d.ts.map +1 -1
- package/dist/src/services/ai-completion.js +112 -72
- package/dist/src/services/ai-completion.js.map +1 -1
- package/dist/src/services/ai-disclosure.d.ts +6 -0
- package/dist/src/services/ai-disclosure.d.ts.map +1 -1
- package/dist/src/services/ai-disclosure.js +20 -2
- package/dist/src/services/ai-disclosure.js.map +1 -1
- package/dist/src/services/ai-provenance-page.d.ts +59 -0
- package/dist/src/services/ai-provenance-page.d.ts.map +1 -0
- package/dist/src/services/ai-provenance-page.js +185 -0
- package/dist/src/services/ai-provenance-page.js.map +1 -0
- package/dist/src/services/ai-provenance.d.ts +6 -0
- package/dist/src/services/ai-provenance.d.ts.map +1 -1
- package/dist/src/services/ai-provenance.js +5 -0
- package/dist/src/services/ai-provenance.js.map +1 -1
- package/dist/src/services/ai-transcription.d.ts +66 -0
- package/dist/src/services/ai-transcription.d.ts.map +1 -0
- package/dist/src/services/ai-transcription.js +90 -0
- package/dist/src/services/ai-transcription.js.map +1 -0
- package/dist/src/services/app-publish.d.ts.map +1 -1
- package/dist/src/services/app-publish.js +16 -1
- package/dist/src/services/app-publish.js.map +1 -1
- package/dist/src/services/build-app-prompt.d.ts.map +1 -1
- package/dist/src/services/build-app-prompt.js +2 -1
- package/dist/src/services/build-app-prompt.js.map +1 -1
- package/dist/src/services/connections/attach.d.ts +62 -0
- package/dist/src/services/connections/attach.d.ts.map +1 -0
- package/dist/src/services/connections/attach.js +168 -0
- package/dist/src/services/connections/attach.js.map +1 -0
- package/dist/src/services/connections/credential.d.ts +39 -0
- package/dist/src/services/connections/credential.d.ts.map +1 -0
- package/dist/src/services/connections/credential.js +66 -0
- package/dist/src/services/connections/credential.js.map +1 -0
- package/dist/src/services/connections/instance.d.ts +65 -0
- package/dist/src/services/connections/instance.d.ts.map +1 -0
- package/dist/src/services/connections/instance.js +144 -0
- package/dist/src/services/connections/instance.js.map +1 -0
- package/dist/src/services/connections/oauth.d.ts +77 -0
- package/dist/src/services/connections/oauth.d.ts.map +1 -0
- package/dist/src/services/connections/oauth.js +290 -0
- package/dist/src/services/connections/oauth.js.map +1 -0
- package/dist/src/services/connections/providers.d.ts +118 -0
- package/dist/src/services/connections/providers.d.ts.map +1 -0
- package/dist/src/services/connections/providers.js +260 -0
- package/dist/src/services/connections/providers.js.map +1 -0
- package/dist/src/services/connections/publish-gate.d.ts +103 -0
- package/dist/src/services/connections/publish-gate.d.ts.map +1 -0
- package/dist/src/services/connections/publish-gate.js +0 -0
- package/dist/src/services/connections/publish-gate.js.map +1 -0
- package/dist/src/services/connections/publish.d.ts +60 -0
- package/dist/src/services/connections/publish.d.ts.map +1 -0
- package/dist/src/services/connections/publish.js +340 -0
- package/dist/src/services/connections/publish.js.map +1 -0
- package/dist/src/services/connections/refresh.d.ts +55 -0
- package/dist/src/services/connections/refresh.d.ts.map +1 -0
- package/dist/src/services/connections/refresh.js +307 -0
- package/dist/src/services/connections/refresh.js.map +1 -0
- package/dist/src/services/markdown-negotiation.d.ts +17 -0
- package/dist/src/services/markdown-negotiation.d.ts.map +1 -1
- package/dist/src/services/markdown-negotiation.js +15 -0
- package/dist/src/services/markdown-negotiation.js.map +1 -1
- package/dist/src/services/message-send.d.ts.map +1 -1
- package/dist/src/services/message-send.js +16 -2
- package/dist/src/services/message-send.js.map +1 -1
- package/dist/src/services/openrouter.d.ts +67 -1
- package/dist/src/services/openrouter.d.ts.map +1 -1
- package/dist/src/services/openrouter.js +102 -11
- package/dist/src/services/openrouter.js.map +1 -1
- package/dist/src/services/upload-token.d.ts +5 -1
- package/dist/src/services/upload-token.d.ts.map +1 -1
- package/dist/src/services/upload-token.js +13 -1
- package/dist/src/services/upload-token.js.map +1 -1
- package/dist/src/services/workflow/engine-util.d.ts +18 -0
- package/dist/src/services/workflow/engine-util.d.ts.map +1 -1
- package/dist/src/services/workflow/engine-util.js +27 -0
- package/dist/src/services/workflow/engine-util.js.map +1 -1
- package/dist/src/services/workflow/engine.d.ts.map +1 -1
- package/dist/src/services/workflow/engine.js +4 -2
- package/dist/src/services/workflow/engine.js.map +1 -1
- package/dist/src/storage/interface.d.ts +2 -1
- package/dist/src/storage/interface.d.ts.map +1 -1
- package/dist/src/storage/providers/postgres-kysely/db-types.d.ts +65 -0
- package/dist/src/storage/providers/postgres-kysely/db-types.d.ts.map +1 -1
- package/dist/src/storage/providers/postgres-kysely/index.d.ts.map +1 -1
- package/dist/src/storage/providers/postgres-kysely/index.js +2 -1
- package/dist/src/storage/providers/postgres-kysely/index.js.map +1 -1
- package/dist/src/storage/providers/postgres-kysely/methods/connections.d.ts +44 -0
- package/dist/src/storage/providers/postgres-kysely/methods/connections.d.ts.map +1 -0
- package/dist/src/storage/providers/postgres-kysely/methods/connections.js +308 -0
- package/dist/src/storage/providers/postgres-kysely/methods/connections.js.map +1 -0
- package/dist/src/storage/providers/postgres-kysely/methods/node-infra.d.ts.map +1 -1
- package/dist/src/storage/providers/postgres-kysely/methods/node-infra.js +4 -2
- package/dist/src/storage/providers/postgres-kysely/methods/node-infra.js.map +1 -1
- package/dist/src/storage/providers/postgres-kysely/migrations/0021_connections.sql +116 -0
- package/dist/src/storage/providers/postgres-kysely/migrations/0022_provider_clients.sql +36 -0
- package/dist/src/storage/providers/postgres-kysely/migrations/0023_nonce_payload.sql +13 -0
- package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/index.js +2 -1
- package/dist/src/storage/providers/sqlite/index.js.map +1 -1
- package/dist/src/storage/providers/sqlite/methods/community.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/methods/community.js +2 -1
- package/dist/src/storage/providers/sqlite/methods/community.js.map +1 -1
- package/dist/src/storage/providers/sqlite/methods/connections.d.ts +60 -0
- package/dist/src/storage/providers/sqlite/methods/connections.d.ts.map +1 -0
- package/dist/src/storage/providers/sqlite/methods/connections.js +287 -0
- package/dist/src/storage/providers/sqlite/methods/connections.js.map +1 -0
- package/dist/src/storage/providers/sqlite/schema-tables-1.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/schema-tables-1.js +4 -0
- package/dist/src/storage/providers/sqlite/schema-tables-1.js.map +1 -1
- package/dist/src/storage/providers/sqlite/schema-tables-3.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/schema-tables-3.js +106 -0
- package/dist/src/storage/providers/sqlite/schema-tables-3.js.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.js +2 -0
- package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
- package/dist/src/storage/repositories/connection.repository.d.ts +124 -0
- package/dist/src/storage/repositories/connection.repository.d.ts.map +1 -0
- package/dist/src/storage/repositories/connection.repository.js +2 -0
- package/dist/src/storage/repositories/connection.repository.js.map +1 -0
- package/dist/src/storage/types/agents-messaging.d.ts +19 -0
- package/dist/src/storage/types/agents-messaging.d.ts.map +1 -1
- package/dist/src/storage/types/agents-messaging.js.map +1 -1
- package/dist/src/storage/types/auth.d.ts +8 -1
- package/dist/src/storage/types/auth.d.ts.map +1 -1
- package/dist/src/utils/build-stamp.d.ts +56 -0
- package/dist/src/utils/build-stamp.d.ts.map +1 -0
- package/dist/src/utils/build-stamp.js +142 -0
- package/dist/src/utils/build-stamp.js.map +1 -0
- package/dist/src/utils/messaging.d.ts +14 -0
- package/dist/src/utils/messaging.d.ts.map +1 -1
- package/dist/src/utils/messaging.js +0 -0
- package/dist/src/utils/messaging.js.map +1 -1
- package/dist/static/sdk-libs/connect/index.js +199 -0
- package/dist/static/sdk-libs/connect/notes.js +47 -0
- package/dist/static/sdk-libs/connect/panel.js +238 -0
- package/dist/static/sdk-libs/data/index.js +63 -11
- package/dist/static/sdk-libs/dist/aimeat-connect.js +357 -0
- package/dist/static/sdk-libs/dist/aimeat-data.js +40 -9
- package/package.json +2 -2
|
@@ -1114,7 +1114,7 @@
|
|
|
1114
1114
|
textareas a comfortable touch height and let the prompt box scroll. */
|
|
1115
1115
|
.pf-gen-ws-pane .pf-gen-prompt-box { max-height: 220px; }
|
|
1116
1116
|
.pf-gen-ws-pane .pf-gen-result-area { min-height: 200px; }
|
|
1117
|
-
.pf-
|
|
1117
|
+
.pf-or-run-all-bar { flex-direction: column; align-items: stretch; gap: 8px; }
|
|
1118
1118
|
}
|
|
1119
1119
|
|
|
1120
1120
|
/* ══ Shared Profile Utilities (2026-03-17) ══════════════ */
|
|
@@ -1249,15 +1249,15 @@
|
|
|
1249
1249
|
.pf-gen-agent-actions { display: flex; gap: 8px; }
|
|
1250
1250
|
.pf-gen-stop-btn { margin-left: auto; }
|
|
1251
1251
|
|
|
1252
|
-
/* ── OpenRouter Settings (pf-
|
|
1253
|
-
.pf-
|
|
1252
|
+
/* ── OpenRouter Settings (pf-or-*) ──────────────── */
|
|
1253
|
+
.pf-or-wrapper {
|
|
1254
1254
|
margin-bottom: 1.25rem;
|
|
1255
1255
|
background: var(--card, #FFFFFF);
|
|
1256
1256
|
border: 1px solid var(--border, #E5E7EB);
|
|
1257
1257
|
border-radius: 8px;
|
|
1258
1258
|
overflow: hidden;
|
|
1259
1259
|
}
|
|
1260
|
-
.pf-
|
|
1260
|
+
.pf-or-toggle {
|
|
1261
1261
|
display: flex;
|
|
1262
1262
|
align-items: center;
|
|
1263
1263
|
gap: 8px;
|
|
@@ -1271,22 +1271,22 @@
|
|
|
1271
1271
|
color: var(--text, #1A1A2E);
|
|
1272
1272
|
text-align: left;
|
|
1273
1273
|
}
|
|
1274
|
-
.pf-
|
|
1275
|
-
.pf-
|
|
1276
|
-
.pf-
|
|
1274
|
+
.pf-or-toggle:hover { background: var(--bg-dim, #F3F4F6); }
|
|
1275
|
+
.pf-or-toggle-icon { font-size: 0.65em; color: var(--text-dim, #6B7280); width: 1rem; flex-shrink: 0; }
|
|
1276
|
+
.pf-or-status-dot {
|
|
1277
1277
|
width: 8px; height: 8px; border-radius: 50%;
|
|
1278
1278
|
background: var(--success, #22c55e);
|
|
1279
1279
|
box-shadow: 0 0 6px rgba(34, 197, 94, 0.4);
|
|
1280
1280
|
flex-shrink: 0;
|
|
1281
1281
|
margin-left: auto;
|
|
1282
1282
|
}
|
|
1283
|
-
.pf-
|
|
1283
|
+
.pf-or-panel {
|
|
1284
1284
|
padding: 0 14px 14px;
|
|
1285
1285
|
}
|
|
1286
|
-
.pf-
|
|
1286
|
+
.pf-or-field {
|
|
1287
1287
|
margin-bottom: 12px;
|
|
1288
1288
|
}
|
|
1289
|
-
.pf-
|
|
1289
|
+
.pf-or-label {
|
|
1290
1290
|
display: block;
|
|
1291
1291
|
font-size: 0.8em;
|
|
1292
1292
|
color: var(--text-dim, #6B7280);
|
|
@@ -1294,7 +1294,7 @@
|
|
|
1294
1294
|
margin-bottom: 4px;
|
|
1295
1295
|
font-weight: 600;
|
|
1296
1296
|
}
|
|
1297
|
-
.pf-
|
|
1297
|
+
.pf-or-input {
|
|
1298
1298
|
width: 100%;
|
|
1299
1299
|
padding: 8px 12px;
|
|
1300
1300
|
background: var(--surface, #F9FAFB);
|
|
@@ -1305,15 +1305,15 @@
|
|
|
1305
1305
|
font-family: inherit;
|
|
1306
1306
|
box-sizing: border-box;
|
|
1307
1307
|
}
|
|
1308
|
-
.pf-
|
|
1309
|
-
.pf-
|
|
1310
|
-
.pf-
|
|
1311
|
-
.pf-
|
|
1308
|
+
.pf-or-input:focus { outline: none; border-color: var(--accent, #E8564A); box-shadow: 0 0 0 3px rgba(232, 86, 74, 0.1); }
|
|
1309
|
+
.pf-or-input-sm { max-width: 100px; }
|
|
1310
|
+
.pf-or-param-row { display: flex; align-items: center; gap: 10px; }
|
|
1311
|
+
.pf-or-param-hint { font-size: 0.8em; color: var(--text-dim, #888); white-space: nowrap; }
|
|
1312
1312
|
/* Model refresh row: sits below the model <select>; let the hint/error wrap on narrow widths. */
|
|
1313
|
-
.pf-
|
|
1314
|
-
.pf-
|
|
1315
|
-
.pf-
|
|
1316
|
-
.pf-
|
|
1313
|
+
.pf-or-model-row { margin-top: 8px; flex-wrap: wrap; }
|
|
1314
|
+
.pf-or-model-row .pf-or-param-hint { white-space: normal; }
|
|
1315
|
+
.pf-or-model-error { color: var(--danger, #ef4444); }
|
|
1316
|
+
.pf-or-select {
|
|
1317
1317
|
width: 100%;
|
|
1318
1318
|
padding: 8px 12px;
|
|
1319
1319
|
background: var(--surface, #F9FAFB);
|
|
@@ -1324,9 +1324,9 @@
|
|
|
1324
1324
|
font-family: inherit;
|
|
1325
1325
|
cursor: pointer;
|
|
1326
1326
|
}
|
|
1327
|
-
.pf-
|
|
1328
|
-
.pf-
|
|
1329
|
-
.pf-
|
|
1327
|
+
.pf-or-select:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
1328
|
+
.pf-or-select:focus { outline: none; border-color: var(--accent, #E8564A); }
|
|
1329
|
+
.pf-or-checkbox {
|
|
1330
1330
|
display: flex;
|
|
1331
1331
|
align-items: center;
|
|
1332
1332
|
gap: 8px;
|
|
@@ -1334,13 +1334,13 @@
|
|
|
1334
1334
|
color: var(--text, #1A1A2E);
|
|
1335
1335
|
cursor: pointer;
|
|
1336
1336
|
}
|
|
1337
|
-
.pf-
|
|
1338
|
-
.pf-
|
|
1337
|
+
.pf-or-checkbox input[type="checkbox"] { accent-color: var(--accent, #E8564A); width: 16px; height: 16px; cursor: pointer; }
|
|
1338
|
+
.pf-or-radio-group {
|
|
1339
1339
|
display: flex;
|
|
1340
1340
|
flex-direction: column;
|
|
1341
1341
|
gap: 6px;
|
|
1342
1342
|
}
|
|
1343
|
-
.pf-
|
|
1343
|
+
.pf-or-radio-label {
|
|
1344
1344
|
display: flex;
|
|
1345
1345
|
align-items: center;
|
|
1346
1346
|
gap: 8px;
|
|
@@ -1348,34 +1348,34 @@
|
|
|
1348
1348
|
color: var(--text, #1A1A2E);
|
|
1349
1349
|
cursor: pointer;
|
|
1350
1350
|
}
|
|
1351
|
-
.pf-
|
|
1352
|
-
.pf-
|
|
1353
|
-
.pf-
|
|
1351
|
+
.pf-or-radio-label input[type="radio"] { accent-color: var(--accent, #E8564A); width: 16px; height: 16px; cursor: pointer; flex-shrink: 0; }
|
|
1352
|
+
.pf-or-loading { font-size: 0.85em; color: var(--text-dim, #6B7280); font-style: italic; }
|
|
1353
|
+
.pf-or-actions {
|
|
1354
1354
|
display: flex;
|
|
1355
1355
|
gap: 8px;
|
|
1356
1356
|
margin-top: 14px;
|
|
1357
1357
|
flex-wrap: wrap;
|
|
1358
1358
|
}
|
|
1359
|
-
.pf-
|
|
1359
|
+
.pf-or-delete {
|
|
1360
1360
|
color: var(--danger, #ef4444);
|
|
1361
1361
|
border-color: var(--danger, #ef4444);
|
|
1362
1362
|
}
|
|
1363
|
-
.pf-
|
|
1363
|
+
.pf-or-delete:hover {
|
|
1364
1364
|
background: var(--danger, #ef4444);
|
|
1365
1365
|
color: #fff;
|
|
1366
1366
|
}
|
|
1367
|
-
.pf-
|
|
1367
|
+
.pf-or-message {
|
|
1368
1368
|
padding: 8px 12px;
|
|
1369
1369
|
border-radius: 6px;
|
|
1370
1370
|
font-size: 0.85em;
|
|
1371
1371
|
margin-top: 8px;
|
|
1372
1372
|
}
|
|
1373
|
-
.pf-
|
|
1373
|
+
.pf-or-message-success {
|
|
1374
1374
|
background: rgba(34, 197, 94, 0.1);
|
|
1375
1375
|
color: var(--success, #22c55e);
|
|
1376
1376
|
border: 1px solid rgba(34, 197, 94, 0.25);
|
|
1377
1377
|
}
|
|
1378
|
-
.pf-
|
|
1378
|
+
.pf-or-message-error {
|
|
1379
1379
|
background: rgba(239, 68, 68, 0.1);
|
|
1380
1380
|
color: var(--danger, #ef4444);
|
|
1381
1381
|
border: 1px solid rgba(239, 68, 68, 0.25);
|
|
@@ -1383,57 +1383,133 @@
|
|
|
1383
1383
|
|
|
1384
1384
|
/* ── AI-apps daily-budget / spend section (generator-settings OpenRouterSettings) ──
|
|
1385
1385
|
Lifted out of ~28 inline styles + hardcoded hex; all tokenized so it flips in dark mode. */
|
|
1386
|
-
.pf-
|
|
1387
|
-
.pf-
|
|
1388
|
-
.pf-
|
|
1389
|
-
.pf-
|
|
1390
|
-
.pf-
|
|
1391
|
-
.pf-
|
|
1392
|
-
.pf-
|
|
1393
|
-
.pf-
|
|
1394
|
-
.pf-
|
|
1395
|
-
.pf-
|
|
1396
|
-
.pf-
|
|
1397
|
-
.pf-
|
|
1398
|
-
.pf-
|
|
1399
|
-
.pf-
|
|
1400
|
-
.pf-
|
|
1401
|
-
.pf-
|
|
1402
|
-
.pf-
|
|
1403
|
-
.pf-
|
|
1404
|
-
.pf-
|
|
1405
|
-
.pf-
|
|
1406
|
-
.pf-
|
|
1407
|
-
.pf-
|
|
1408
|
-
.pf-
|
|
1409
|
-
.pf-
|
|
1410
|
-
|
|
1411
|
-
/* ──
|
|
1412
|
-
|
|
1386
|
+
.pf-or-spend-section { border-top: 2px solid var(--border); padding-top: 14px; margin-top: 14px; }
|
|
1387
|
+
.pf-or-spend-desc { font-size: 12px; color: var(--text-dim); margin-bottom: 8px; }
|
|
1388
|
+
.pf-or-spend-bar-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
1389
|
+
.pf-or-spend-bar { flex: 1; min-width: 200px; background: var(--bg-surface); border-radius: 4px; height: 20px; position: relative; overflow: hidden; }
|
|
1390
|
+
.pf-or-spend-bar-fill { position: absolute; top: 0; left: 0; bottom: 0; transition: width .3s; background: var(--success); }
|
|
1391
|
+
.pf-or-spend-bar-fill.warn { background: var(--warn); }
|
|
1392
|
+
.pf-or-spend-bar-fill.crit { background: var(--danger); }
|
|
1393
|
+
.pf-or-spend-bar-label { position: relative; text-align: center; font-size: 11px; line-height: 20px; font-weight: 700; }
|
|
1394
|
+
.pf-or-spend-budget-input { width: 90px; padding: 4px 6px; font-size: 13px; }
|
|
1395
|
+
.pf-or-spend-details { margin-top: 10px; }
|
|
1396
|
+
.pf-or-spend-summary { cursor: pointer; font-size: 12px; color: var(--text-dim); }
|
|
1397
|
+
.pf-or-spend-hint { font-size: 11px; color: var(--text-muted); margin: 6px 0; }
|
|
1398
|
+
.pf-or-spend-table { width: 100%; margin-top: 4px; font-size: 12px; border-collapse: collapse; }
|
|
1399
|
+
.pf-or-spend-table th, .pf-or-spend-table td { padding: 4px; text-align: left; }
|
|
1400
|
+
.pf-or-spend-table th.num, .pf-or-spend-table td.num { text-align: right; }
|
|
1401
|
+
.pf-or-spend-table thead tr { border-bottom: 1px solid var(--border); }
|
|
1402
|
+
.pf-or-spend-cap-input { width: 74px; padding: 3px 5px; font-size: 12px; text-align: right; }
|
|
1403
|
+
.pf-or-spend-actions { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
|
|
1404
|
+
.pf-or-spend-msg { margin-top: 8px; }
|
|
1405
|
+
.pf-or-spend-chart-wrap { margin-top: 14px; }
|
|
1406
|
+
.pf-or-spend-chart-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 6px; }
|
|
1407
|
+
.pf-or-spend-chart-title { font-size: 12px; font-weight: 600; color: var(--text-dim); }
|
|
1408
|
+
.pf-or-spend-metric-toggle { display: inline-flex; gap: 4px; }
|
|
1409
|
+
.pf-or-spend-footnote { font-size: 11px; color: var(--text-muted); margin-top: 8px; }
|
|
1410
|
+
|
|
1411
|
+
/* ── AI provider settings v3: sections, model picker, STT (2026-08-01) ──
|
|
1412
|
+
The panel is four sections that each save their own fields, so the answer to "did that save?"
|
|
1413
|
+
appears where the question is asked instead of at the bottom of one long form. */
|
|
1414
|
+
.pf-or-section { padding: 14px 0; border-top: 1px solid var(--border); }
|
|
1415
|
+
.pf-or-section:first-child { border-top: none; padding-top: 4px; }
|
|
1416
|
+
.pf-or-section-title { font-size: 13px; font-weight: 700; margin: 0 0 10px; color: var(--text); }
|
|
1417
|
+
|
|
1418
|
+
.pf-or-status { margin-left: auto; font-size: 11px; color: var(--text-muted); font-weight: 500; }
|
|
1419
|
+
.pf-or-status--on { color: var(--success); }
|
|
1420
|
+
|
|
1421
|
+
/* Key row: the save button sits next to the field it saves. */
|
|
1422
|
+
.pf-or-key-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
|
1423
|
+
.pf-or-key-input { flex: 1; min-width: 220px; }
|
|
1424
|
+
.pf-or-links { display: flex; gap: 14px; margin-top: 8px; font-size: 12px; flex-wrap: wrap; }
|
|
1425
|
+
|
|
1426
|
+
.pf-or-models-head { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 10px; }
|
|
1427
|
+
.pf-or-hint { font-size: 11px; color: var(--text-muted); display: block; margin-top: 4px; }
|
|
1428
|
+
.pf-or-hint--error { color: var(--danger); }
|
|
1429
|
+
.pf-or-loading { font-size: 12px; color: var(--text-dim); padding: 8px 0; }
|
|
1430
|
+
.pf-or-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 12px; }
|
|
1431
|
+
|
|
1432
|
+
.pf-or-roles-summary,
|
|
1433
|
+
.pf-or-params-summary { cursor: pointer; font-size: 12px; color: var(--text-dim); margin-bottom: 8px; }
|
|
1434
|
+
.pf-or-params-summary { font-size: 13px; font-weight: 700; color: var(--text); }
|
|
1435
|
+
|
|
1436
|
+
.pf-or-inline-field { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 10px; }
|
|
1437
|
+
.pf-or-label--inline { margin: 0; }
|
|
1438
|
+
.pf-or-select--sm { width: auto; min-width: 150px; }
|
|
1439
|
+
|
|
1440
|
+
/* ── Model picker ──
|
|
1441
|
+
Replaces a <select> holding the provider's whole catalogue (336 options on OpenRouter, unlabelled
|
|
1442
|
+
and unsearchable). Prices are tabular so a column of them can actually be compared. */
|
|
1443
|
+
.pf-or-picker { border: 1px solid var(--border); border-radius: var(--radius, 8px); padding: 8px; background: var(--bg-surface); }
|
|
1444
|
+
.pf-or-picker--off { opacity: .55; }
|
|
1445
|
+
.pf-or-picker-head { display: flex; gap: 8px; align-items: center; justify-content: space-between; margin-bottom: 6px; }
|
|
1446
|
+
.pf-or-picked-id { font-size: 12px; word-break: break-all; }
|
|
1447
|
+
.pf-or-picked-none { font-size: 12px; color: var(--text-muted); }
|
|
1448
|
+
.pf-or-picked-warn { font-size: 11px; color: var(--warn); margin-left: 6px; }
|
|
1449
|
+
.pf-or-msearch { width: 100%; }
|
|
1450
|
+
|
|
1451
|
+
.pf-or-mlist { list-style: none; margin: 8px 0 0; padding: 0; max-height: 260px; overflow-y: auto; }
|
|
1452
|
+
.pf-or-mgroup { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); padding: 4px 2px; }
|
|
1453
|
+
.pf-or-mrow { display: flex; align-items: center; gap: 4px; border-radius: 6px; }
|
|
1454
|
+
.pf-or-mrow:hover { background: var(--bg-dim); }
|
|
1455
|
+
.pf-or-mrow--on { background: var(--bg-dim); box-shadow: inset 2px 0 0 var(--accent); }
|
|
1456
|
+
.pf-or-mpick {
|
|
1457
|
+
flex: 1; min-width: 0; text-align: left; background: none; border: none; cursor: pointer;
|
|
1458
|
+
padding: 6px 6px; color: var(--text); display: flex; flex-direction: column; gap: 2px;
|
|
1459
|
+
}
|
|
1460
|
+
.pf-or-mname { font-size: 13px; font-weight: 600; }
|
|
1461
|
+
.pf-or-mid { font-size: 11px; color: var(--text-muted); word-break: break-all; }
|
|
1462
|
+
.pf-or-mmeta { display: flex; gap: 10px; flex-wrap: wrap; font-size: 11px; color: var(--text-dim); }
|
|
1463
|
+
.pf-or-mprice { font-variant-numeric: tabular-nums; }
|
|
1464
|
+
.pf-or-mlink { padding: 0 6px; color: var(--text-dim); text-decoration: none; font-size: 13px; }
|
|
1465
|
+
.pf-or-mlink:hover { color: var(--accent); }
|
|
1466
|
+
.pf-or-mempty { font-size: 12px; color: var(--text-muted); padding: 10px 4px; }
|
|
1467
|
+
.pf-or-mall { margin-top: 6px; }
|
|
1468
|
+
.pf-or-mcustom { display: flex; gap: 8px; margin-top: 8px; flex-wrap: wrap; }
|
|
1469
|
+
.pf-or-mcustom .pf-or-input { flex: 1; min-width: 180px; }
|
|
1470
|
+
|
|
1471
|
+
/* ── Speech-to-text ──
|
|
1472
|
+
The measured result is the point of the test: an audio model's listed price carries no unit, so
|
|
1473
|
+
one real transcription says more than any number derived from the catalogue. */
|
|
1474
|
+
.pf-or-stt { border-top: 1px dashed var(--border); padding-top: 12px; margin-top: 12px; }
|
|
1475
|
+
.pf-or-stt-test { margin-top: 10px; }
|
|
1476
|
+
.pf-or-stt-test-head { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
|
1477
|
+
.pf-or-stt-result { margin-top: 8px; padding: 8px; border-radius: var(--radius, 8px); background: var(--bg-dim); border: 1px solid var(--border); }
|
|
1478
|
+
.pf-or-stt-text { font-size: 13px; white-space: pre-wrap; word-break: break-word; }
|
|
1479
|
+
.pf-or-stt-meta { font-size: 11px; color: var(--text-muted); margin-top: 6px; font-variant-numeric: tabular-nums; }
|
|
1480
|
+
|
|
1481
|
+
@media (max-width: 520px) {
|
|
1482
|
+
.pf-or-key-row { flex-direction: column; align-items: stretch; }
|
|
1483
|
+
.pf-or-key-input { min-width: 0; }
|
|
1484
|
+
.pf-or-mmeta { flex-direction: column; gap: 2px; }
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
/* ── OpenRouter Autopilot (pf-or-*) ──────────────── */
|
|
1488
|
+
.pf-or-btn-row {
|
|
1413
1489
|
display: flex;
|
|
1414
1490
|
gap: 8px;
|
|
1415
1491
|
align-items: center;
|
|
1416
1492
|
flex-wrap: wrap;
|
|
1417
1493
|
}
|
|
1418
|
-
.pf-
|
|
1494
|
+
.pf-or-run-btn {
|
|
1419
1495
|
display: inline-flex;
|
|
1420
1496
|
align-items: center;
|
|
1421
1497
|
gap: 6px;
|
|
1422
1498
|
}
|
|
1423
|
-
.pf-
|
|
1499
|
+
.pf-or-run-btn.pf-or-running {
|
|
1424
1500
|
opacity: 0.8;
|
|
1425
1501
|
cursor: wait;
|
|
1426
1502
|
}
|
|
1427
|
-
.pf-
|
|
1503
|
+
.pf-or-spinner {
|
|
1428
1504
|
display: inline-block;
|
|
1429
1505
|
width: 14px;
|
|
1430
1506
|
height: 14px;
|
|
1431
1507
|
border: 2px solid var(--border, #E5E7EB);
|
|
1432
1508
|
border-top-color: var(--accent, #E8564A);
|
|
1433
1509
|
border-radius: 50%;
|
|
1434
|
-
animation: pf-
|
|
1510
|
+
animation: pf-or-spin 0.7s linear infinite;
|
|
1435
1511
|
}
|
|
1436
|
-
@keyframes pf-
|
|
1512
|
+
@keyframes pf-or-spin {
|
|
1437
1513
|
to { transform: rotate(360deg); }
|
|
1438
1514
|
}
|
|
1439
1515
|
.pf-gen-data-viewer {
|
|
@@ -1458,7 +1534,7 @@
|
|
|
1458
1534
|
white-space: pre-wrap;
|
|
1459
1535
|
word-break: break-word;
|
|
1460
1536
|
}
|
|
1461
|
-
.pf-
|
|
1537
|
+
.pf-or-run-all-bar {
|
|
1462
1538
|
display: flex;
|
|
1463
1539
|
align-items: center;
|
|
1464
1540
|
gap: 12px;
|
|
@@ -1468,7 +1544,7 @@
|
|
|
1468
1544
|
border: 1px solid var(--border, #E5E7EB);
|
|
1469
1545
|
border-radius: 8px;
|
|
1470
1546
|
}
|
|
1471
|
-
.pf-
|
|
1547
|
+
.pf-or-run-all-status {
|
|
1472
1548
|
display: flex;
|
|
1473
1549
|
align-items: center;
|
|
1474
1550
|
gap: 8px;
|
|
@@ -2058,8 +2134,8 @@
|
|
|
2058
2134
|
.pf-gen-test-scope-compact {
|
|
2059
2135
|
display: flex; gap: 8px; align-items: center; margin: 0; font-size: 0.85em;
|
|
2060
2136
|
}
|
|
2061
|
-
.pf-gen-test-scope-compact .pf-
|
|
2062
|
-
.pf-
|
|
2137
|
+
.pf-gen-test-scope-compact .pf-or-radio-label { margin: 0; white-space: nowrap; }
|
|
2138
|
+
.pf-or-run-all-controls {
|
|
2063
2139
|
display: flex; align-items: center; gap: 12px; width: 100%;
|
|
2064
2140
|
}
|
|
2065
2141
|
|
|
@@ -3449,29 +3525,29 @@
|
|
|
3449
3525
|
.pf-adk-sev--warn { background: color-mix(in srgb, var(--warn) 18%, transparent); color: var(--warn); }
|
|
3450
3526
|
.pf-adk-sev--info { background: var(--bg-dim); color: var(--muted); }
|
|
3451
3527
|
.pf-adk-empty { color: var(--muted); font-size: 0.9rem; padding: 0.9rem; margin-bottom: 0.7rem; }
|
|
3452
|
-
|
|
3453
|
-
/* ── AI transparency card (pf-aitr-*) — TARGET-058 Phase 8 ──
|
|
3454
|
-
The owner's own view of what they published with a model in it. The one visual decision here is
|
|
3455
|
-
that the "public and unlabelled" tile changes colour only when the number is ABOVE zero: a
|
|
3456
|
-
compliance surface that is always red teaches people to stop looking at it. */
|
|
3457
|
-
.pf-aitr-head { display: flex; align-items: center; justify-content: space-between; gap: 0.8rem; width: 100%; background: none; border: 0; padding: 0; cursor: pointer; text-align: left; color: inherit; font: inherit; }
|
|
3458
|
-
.pf-aitr-chevron { color: var(--muted); font-size: 1.1rem; line-height: 1; flex-shrink: 0; }
|
|
3459
|
-
.pf-aitr-body { margin-top: 0.9rem; border-top: 1px solid var(--border); padding-top: 0.9rem; }
|
|
3460
|
-
/* auto-fit, so four tiles become two rows on a phone instead of overflowing the card. */
|
|
3461
|
-
.pf-aitr-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr)); gap: 0.6rem; }
|
|
3462
|
-
.pf-aitr-stat { display: flex; flex-direction: column; gap: 0.15rem; padding: 0.7rem 0.8rem; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-dim); min-width: 0; }
|
|
3463
|
-
.pf-aitr-stat-warn { border-color: var(--warn); }
|
|
3464
|
-
.pf-aitr-stat-warn .pf-aitr-num { color: var(--warn); }
|
|
3465
|
-
.pf-aitr-num { font-size: 1.5rem; font-weight: 700; line-height: 1.1; }
|
|
3466
|
-
.pf-aitr-lbl { font-size: 0.75rem; color: var(--muted); overflow-wrap: anywhere; }
|
|
3467
|
-
.pf-aitr-note { margin: 0.8rem 0 0; font-size: 0.82rem; color: var(--muted); }
|
|
3468
|
-
.pf-aitr-warn-line { margin: 0.9rem 0 0.4rem; font-size: 0.85rem; color: var(--fg); }
|
|
3469
|
-
.pf-aitr-error { margin: 0; font-size: 0.85rem; color: var(--danger); }
|
|
3470
|
-
.pf-aitr-muted { margin: 0.5rem 0 0; font-size: 0.8rem; color: var(--muted); }
|
|
3471
|
-
.pf-aitr-sub { margin: 1.2rem 0 0.4rem; font-size: 0.9rem; font-weight: 600; }
|
|
3472
|
-
.pf-aitr-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; }
|
|
3473
|
-
.pf-aitr-row, .pf-aitr-policy { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.3rem 0.7rem; padding: 0.55rem 0.7rem; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-dim); min-width: 0; }
|
|
3474
|
-
.pf-aitr-policy { flex-direction: column; }
|
|
3475
|
-
.pf-aitr-row-main { font-size: 0.85rem; font-weight: 600; overflow-wrap: anywhere; }
|
|
3476
|
-
.pf-aitr-row-meta { font-size: 0.78rem; color: var(--muted); overflow-wrap: anywhere; }
|
|
3477
|
-
.pf-aitr-row-link { font-size: 0.78rem; color: var(--accent); }
|
|
3528
|
+
|
|
3529
|
+
/* ── AI transparency card (pf-aitr-*) — TARGET-058 Phase 8 ──
|
|
3530
|
+
The owner's own view of what they published with a model in it. The one visual decision here is
|
|
3531
|
+
that the "public and unlabelled" tile changes colour only when the number is ABOVE zero: a
|
|
3532
|
+
compliance surface that is always red teaches people to stop looking at it. */
|
|
3533
|
+
.pf-aitr-head { display: flex; align-items: center; justify-content: space-between; gap: 0.8rem; width: 100%; background: none; border: 0; padding: 0; cursor: pointer; text-align: left; color: inherit; font: inherit; }
|
|
3534
|
+
.pf-aitr-chevron { color: var(--muted); font-size: 1.1rem; line-height: 1; flex-shrink: 0; }
|
|
3535
|
+
.pf-aitr-body { margin-top: 0.9rem; border-top: 1px solid var(--border); padding-top: 0.9rem; }
|
|
3536
|
+
/* auto-fit, so four tiles become two rows on a phone instead of overflowing the card. */
|
|
3537
|
+
.pf-aitr-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr)); gap: 0.6rem; }
|
|
3538
|
+
.pf-aitr-stat { display: flex; flex-direction: column; gap: 0.15rem; padding: 0.7rem 0.8rem; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-dim); min-width: 0; }
|
|
3539
|
+
.pf-aitr-stat-warn { border-color: var(--warn); }
|
|
3540
|
+
.pf-aitr-stat-warn .pf-aitr-num { color: var(--warn); }
|
|
3541
|
+
.pf-aitr-num { font-size: 1.5rem; font-weight: 700; line-height: 1.1; }
|
|
3542
|
+
.pf-aitr-lbl { font-size: 0.75rem; color: var(--muted); overflow-wrap: anywhere; }
|
|
3543
|
+
.pf-aitr-note { margin: 0.8rem 0 0; font-size: 0.82rem; color: var(--muted); }
|
|
3544
|
+
.pf-aitr-warn-line { margin: 0.9rem 0 0.4rem; font-size: 0.85rem; color: var(--fg); }
|
|
3545
|
+
.pf-aitr-error { margin: 0; font-size: 0.85rem; color: var(--danger); }
|
|
3546
|
+
.pf-aitr-muted { margin: 0.5rem 0 0; font-size: 0.8rem; color: var(--muted); }
|
|
3547
|
+
.pf-aitr-sub { margin: 1.2rem 0 0.4rem; font-size: 0.9rem; font-weight: 600; }
|
|
3548
|
+
.pf-aitr-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; }
|
|
3549
|
+
.pf-aitr-row, .pf-aitr-policy { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.3rem 0.7rem; padding: 0.55rem 0.7rem; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-dim); min-width: 0; }
|
|
3550
|
+
.pf-aitr-policy { flex-direction: column; }
|
|
3551
|
+
.pf-aitr-row-main { font-size: 0.85rem; font-weight: 600; overflow-wrap: anywhere; }
|
|
3552
|
+
.pf-aitr-row-meta { font-size: 0.78rem; color: var(--muted); overflow-wrap: anywhere; }
|
|
3553
|
+
.pf-aitr-row-link { font-size: 0.78rem; color: var(--accent); }
|
|
@@ -29,6 +29,11 @@
|
|
|
29
29
|
centred H1 it sat inset from the headings and looked like a caption. */
|
|
30
30
|
.tp-hero .ld-hero-sub { max-width: 62ch; margin: 0; text-align: left; }
|
|
31
31
|
|
|
32
|
+
/* "Somewhere to see one" — one sentence under the limits, pointing at something on this node that
|
|
33
|
+
actually carries a label. Quiet: it is evidence, not a call to action. */
|
|
34
|
+
.tp-seeit { margin: var(--sp-4, 16px) 0 0; max-width: 62ch; line-height: 1.6; color: var(--text-dim); font-size: var(--text-sm); }
|
|
35
|
+
.tp-seeit a { color: var(--accent); font-weight: 600; }
|
|
36
|
+
|
|
32
37
|
.tp-section { margin: var(--sp-8, 32px) 0; }
|
|
33
38
|
.tp-h2 { font-size: var(--text-xl); font-weight: 700; margin: 0 0 var(--sp-3, 12px); color: var(--text); }
|
|
34
39
|
.tp-p { margin: 0 0 var(--sp-3, 12px); max-width: 62ch; line-height: 1.6; color: var(--text); }
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file audio-recorder.js
|
|
3
|
+
* @description Microphone capture for voice messages and the transcription test. Plain JS, no
|
|
4
|
+
* framework: one function starts a recording and hands back the controls to stop or cancel it.
|
|
5
|
+
*
|
|
6
|
+
* FORMAT: whatever the browser actually produces, negotiated with isTypeSupported — webm/opus in
|
|
7
|
+
* Chrome and Firefox, mp4/AAC in Safari. Not WAV. MediaRecorder cannot produce WAV without hand
|
|
8
|
+
* muxing PCM, and the result is roughly ten times the bytes for the same speech (a minute of 16 kHz
|
|
9
|
+
* mono WAV is ~2 MB against ~180 kB of Opus) with nothing gained: every browser plays these back,
|
|
10
|
+
* and OpenRouter's transcription endpoint lists webm, ogg, m4a and aac among its accepted formats.
|
|
11
|
+
* The extra megabytes would be paid for in the storage quota, the transfer and the federation copy.
|
|
12
|
+
*
|
|
13
|
+
* The microphone track is stopped on every exit path. A forgotten track leaves the browser's
|
|
14
|
+
* recording indicator lit, which reads to a user as an application still listening to them.
|
|
15
|
+
* @structure isRecordingSupported() · pickMimeType() · startRecording() · fmtDuration()
|
|
16
|
+
* @usage const rec = await startRecording({ onLevel, maxSeconds }); const file = await rec.stop();
|
|
17
|
+
* @version-history
|
|
18
|
+
* v1.0.0 — 2026-08-01 — Initial version for inbox voice messages + the settings STT test.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** Preference order: Opus where available (smallest for speech), then whatever the browser offers. */
|
|
22
|
+
const CANDIDATES = [
|
|
23
|
+
'audio/webm;codecs=opus',
|
|
24
|
+
'audio/webm',
|
|
25
|
+
'audio/ogg;codecs=opus',
|
|
26
|
+
'audio/mp4', // Safari
|
|
27
|
+
'audio/aac',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export function isRecordingSupported() {
|
|
31
|
+
return typeof window !== 'undefined'
|
|
32
|
+
&& typeof window.MediaRecorder !== 'undefined'
|
|
33
|
+
&& !!navigator?.mediaDevices?.getUserMedia;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The first candidate this browser admits to supporting, or '' to let it choose its own default. */
|
|
37
|
+
export function pickMimeType() {
|
|
38
|
+
if (typeof window === 'undefined' || !window.MediaRecorder?.isTypeSupported) return '';
|
|
39
|
+
for (const type of CANDIDATES) {
|
|
40
|
+
try { if (window.MediaRecorder.isTypeSupported(type)) return type; } catch { /* keep looking */ } // eslint-disable-line aimeat/no-silent-catch -- a browser refusing to answer about a type IS the answer: try the next one
|
|
41
|
+
}
|
|
42
|
+
return '';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** File extension for a recorded blob, from its mime type. */
|
|
46
|
+
function extFor(mime) {
|
|
47
|
+
if (!mime) return 'webm';
|
|
48
|
+
if (mime.includes('mp4')) return 'm4a';
|
|
49
|
+
if (mime.includes('aac')) return 'aac';
|
|
50
|
+
if (mime.includes('ogg')) return 'ogg';
|
|
51
|
+
return 'webm';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Seconds → "0:07" / "12:03". */
|
|
55
|
+
export function fmtDuration(seconds) {
|
|
56
|
+
const s = Math.max(0, Math.round(Number(seconds) || 0));
|
|
57
|
+
return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Start recording from the default microphone.
|
|
62
|
+
*
|
|
63
|
+
* @param {object} [opts]
|
|
64
|
+
* @param {(level: number) => void} [opts.onLevel] 0..1 input level, ~20/s while recording
|
|
65
|
+
* @param {(seconds: number) => void} [opts.onTick] elapsed seconds, once a second
|
|
66
|
+
* @param {number} [opts.maxSeconds] hard stop; the recording is kept, not discarded
|
|
67
|
+
* @param {() => void} [opts.onAutoStop] fired when maxSeconds ended the recording
|
|
68
|
+
* @returns {Promise<{ stop: () => Promise<{file: File, durationSeconds: number}>, cancel: () => void, mimeType: string }>}
|
|
69
|
+
*/
|
|
70
|
+
export async function startRecording(opts = {}) {
|
|
71
|
+
if (!isRecordingSupported()) {
|
|
72
|
+
throw new Error('unsupported');
|
|
73
|
+
}
|
|
74
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
75
|
+
// Speech, not music: the browser's own cleanup is worth more than the fidelity it costs.
|
|
76
|
+
audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true },
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const mimeType = pickMimeType();
|
|
80
|
+
const recorder = new MediaRecorder(stream, mimeType ? { mimeType } : undefined);
|
|
81
|
+
const chunks = [];
|
|
82
|
+
const startedAt = Date.now();
|
|
83
|
+
let stopped = false;
|
|
84
|
+
|
|
85
|
+
// Level metering. Deliberately minimal: a number, not a waveform. Its whole job is to answer
|
|
86
|
+
// "is the microphone hearing me", which a silent countdown cannot.
|
|
87
|
+
let audioCtx = null;
|
|
88
|
+
let rafId = 0;
|
|
89
|
+
if (opts.onLevel && typeof window.AudioContext !== 'undefined') {
|
|
90
|
+
try {
|
|
91
|
+
audioCtx = new AudioContext();
|
|
92
|
+
const source = audioCtx.createMediaStreamSource(stream);
|
|
93
|
+
const analyser = audioCtx.createAnalyser();
|
|
94
|
+
analyser.fftSize = 512;
|
|
95
|
+
source.connect(analyser);
|
|
96
|
+
const buf = new Uint8Array(analyser.frequencyBinCount);
|
|
97
|
+
const tick = () => {
|
|
98
|
+
analyser.getByteTimeDomainData(buf);
|
|
99
|
+
let peak = 0;
|
|
100
|
+
for (let i = 0; i < buf.length; i++) peak = Math.max(peak, Math.abs(buf[i] - 128) / 128);
|
|
101
|
+
opts.onLevel(peak);
|
|
102
|
+
rafId = requestAnimationFrame(tick);
|
|
103
|
+
};
|
|
104
|
+
rafId = requestAnimationFrame(tick);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
// Metering is decoration; a browser that refuses an AudioContext still records fine.
|
|
107
|
+
audioCtx = null;
|
|
108
|
+
if (typeof console !== 'undefined') console.warn('[audio-recorder] level meter unavailable:', err?.message || err);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let tickTimer = 0;
|
|
113
|
+
if (opts.onTick) {
|
|
114
|
+
tickTimer = setInterval(() => opts.onTick(Math.floor((Date.now() - startedAt) / 1000)), 1000);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let autoStopTimer = 0;
|
|
118
|
+
if (opts.maxSeconds > 0) {
|
|
119
|
+
autoStopTimer = setTimeout(() => {
|
|
120
|
+
if (!stopped && recorder.state === 'recording') {
|
|
121
|
+
recorder.stop(); // the blob is KEPT — hitting the cap is not losing the take
|
|
122
|
+
opts.onAutoStop?.();
|
|
123
|
+
}
|
|
124
|
+
}, opts.maxSeconds * 1000);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
recorder.ondataavailable = (e) => { if (e.data && e.data.size > 0) chunks.push(e.data); };
|
|
128
|
+
|
|
129
|
+
const teardown = () => {
|
|
130
|
+
stopped = true;
|
|
131
|
+
if (tickTimer) clearInterval(tickTimer);
|
|
132
|
+
if (autoStopTimer) clearTimeout(autoStopTimer);
|
|
133
|
+
if (rafId) cancelAnimationFrame(rafId);
|
|
134
|
+
if (audioCtx) audioCtx.close().catch(() => { /* already closing */ }); // eslint-disable-line aimeat/no-silent-catch -- closing a closed context is not an error worth reporting
|
|
135
|
+
// The one line that must never be skipped: leave this out and the browser keeps showing the
|
|
136
|
+
// user that something is recording them.
|
|
137
|
+
stream.getTracks().forEach((tr) => tr.stop());
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
recorder.start();
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
mimeType: recorder.mimeType || mimeType || 'audio/webm',
|
|
144
|
+
/** Stop and resolve with the recorded File plus its measured length. */
|
|
145
|
+
stop() {
|
|
146
|
+
return new Promise((resolve, reject) => {
|
|
147
|
+
if (stopped) { reject(new Error('already stopped')); return; }
|
|
148
|
+
recorder.onstop = () => {
|
|
149
|
+
const durationSeconds = Math.max(0.1, (Date.now() - startedAt) / 1000);
|
|
150
|
+
teardown();
|
|
151
|
+
const type = recorder.mimeType || mimeType || 'audio/webm';
|
|
152
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
153
|
+
const file = new File(chunks, `voice-${stamp}.${extFor(type)}`, { type });
|
|
154
|
+
resolve({ file, durationSeconds });
|
|
155
|
+
};
|
|
156
|
+
recorder.onerror = (e) => { teardown(); reject(e?.error || new Error('recording failed')); };
|
|
157
|
+
// Already inactive (an auto-stop at maxSeconds got there first): resolve from the chunks we
|
|
158
|
+
// have instead of waiting for an onstop that will never fire.
|
|
159
|
+
if (recorder.state === 'recording') recorder.stop();
|
|
160
|
+
else recorder.onstop?.(new Event('stop'));
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
/** Throw the take away and release the microphone. */
|
|
164
|
+
cancel() {
|
|
165
|
+
if (stopped) return;
|
|
166
|
+
recorder.onstop = () => {};
|
|
167
|
+
try { if (recorder.state === 'recording') recorder.stop(); } catch { /* already stopping */ } // eslint-disable-line aimeat/no-silent-catch -- the goal is releasing the microphone below, which happens either way
|
|
168
|
+
teardown();
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
* block), plus small storage helpers used to upload and resolve message attachments.
|
|
6
6
|
* @structure send / listInbox / listConversations / getConversation / markConversationRead /
|
|
7
7
|
* markRead / deleteMessage / listRequests / acceptRequest / blockContact / listContacts /
|
|
8
|
-
* uploadAttachment / attachmentUrl
|
|
8
|
+
* uploadAttachment / attachmentUrl / transcribeAttachment
|
|
9
9
|
* @usage import * as messages from '/js/services/messages.js';
|
|
10
10
|
* @version-history
|
|
11
|
+
* v1.4.0 -- 2026-08-01 -- Voice messages: uploadAttachment carries a recording's measured
|
|
12
|
+
* `duration_seconds` into the descriptor (so a thread can show "0:14" before fetching the audio),
|
|
13
|
+
* and transcribeAttachment() turns one voice attachment into text on the caller's own copy.
|
|
11
14
|
* v1.0.0 -- 2026-06-16 -- Initial creation for user-to-user messaging (layer 5: Inbox tab).
|
|
12
15
|
* v1.0.1 -- 2026-06-19 -- JSDoc type annotations for frontend type-checking
|
|
13
16
|
* v1.1.0 -- 2026-06-23 -- send() carries the optional `interactive` payload (federated AskUserQuestion).
|
|
@@ -177,7 +180,23 @@ export async function uploadAttachment(file, kindOverride) {
|
|
|
177
180
|
const kind = kindOverride || (mime.startsWith('image/') ? 'image'
|
|
178
181
|
: mime.startsWith('audio/') ? 'audio'
|
|
179
182
|
: mime.startsWith('video/') ? 'video' : 'file');
|
|
180
|
-
|
|
183
|
+
const desc = { storage_key: key, mime, size: file.size, kind, name: file.name };
|
|
184
|
+
// A voice recording arrives with its measured length attached (composer addRecording). Carrying it
|
|
185
|
+
// in the descriptor lets the recipient's thread show "0:14" without downloading the audio first.
|
|
186
|
+
if (Number(file.durationSeconds) > 0) desc.duration_seconds = Number(file.durationSeconds);
|
|
187
|
+
return desc;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Transcribe a voice attachment on the caller's own copy of a message, with the caller's own AI key.
|
|
191
|
+
* Idempotent server-side: a second call returns the stored transcript instead of paying again. */
|
|
192
|
+
export async function transcribeAttachment(messageId, attachmentId, opts = {}) {
|
|
193
|
+
return api(`/v1/messages/${enc(messageId)}/attachments/${enc(attachmentId)}/transcribe`, {
|
|
194
|
+
method: 'POST',
|
|
195
|
+
body: JSON.stringify({ force: !!opts.force, model: opts.model, language: opts.language }),
|
|
196
|
+
// Speech-to-text is a provider round trip over an audio file; the default 30 s client timeout
|
|
197
|
+
// cuts a perfectly good transcription off at the knees.
|
|
198
|
+
timeoutMs: 180_000,
|
|
199
|
+
});
|
|
181
200
|
}
|
|
182
201
|
|
|
183
202
|
/** Resolve a presigned, no-auth download URL for one of the caller's own storage keys (for <img>). */
|
package/dist/public/spa.html
CHANGED
|
@@ -97,6 +97,7 @@
|
|
|
97
97
|
<link rel="stylesheet" href="/css/components/markdown.css">
|
|
98
98
|
<link rel="stylesheet" href="/css/components/contact-picker.css">
|
|
99
99
|
<link rel="stylesheet" href="/css/components/app-sandbox.css">
|
|
100
|
+
<link rel="stylesheet" href="/css/components/voice-recorder.css">
|
|
100
101
|
<link rel="stylesheet" href="/css/views/app-grant.css">
|
|
101
102
|
<link rel="stylesheet" href="/css/views/invite-accept.css">
|
|
102
103
|
<!-- View CSS preloaded so they are ready before any view renders (prevents FOUC) -->
|
|
@@ -148,6 +149,7 @@
|
|
|
148
149
|
"/js/recents.js": "/js/recents.js",
|
|
149
150
|
"/js/services/admin.js": "/js/services/admin.js",
|
|
150
151
|
"/js/services/agents.js": "/js/services/agents.js",
|
|
152
|
+
"/js/services/audio-recorder.js": "/js/services/audio-recorder.js",
|
|
151
153
|
"/js/services/ecosystem.js": "/js/services/ecosystem.js",
|
|
152
154
|
"/js/services/apps.js": "/js/services/apps.js",
|
|
153
155
|
"/js/services/auth.js": "/js/services/auth.js",
|
|
@@ -189,6 +191,7 @@
|
|
|
189
191
|
"/js/components/tag-cloud.js": "/js/components/tag-cloud.js",
|
|
190
192
|
"/js/components/tag-editor.js": "/js/components/tag-editor.js",
|
|
191
193
|
"/components/Modal.js": "/components/Modal.js",
|
|
194
|
+
"/components/VoiceRecorder.js": "/components/VoiceRecorder.js",
|
|
192
195
|
"/components/useViewCSS.js": "/components/useViewCSS.js",
|
|
193
196
|
"/components/Markdown.js": "/components/Markdown.js",
|
|
194
197
|
"/components/LinkPreview.js": "/components/LinkPreview.js",
|