aimeat 2.0.0 β 2.1.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/locales/en.json +35 -1
- package/dist/locales/fi.json +35 -1
- package/dist/public/components/LinkPreview.js +101 -0
- package/dist/public/components/NotificationBell.js +18 -1
- package/dist/public/css/theme.css +57 -7
- package/dist/public/css/views/admin.css +8 -1
- package/dist/public/css/views/app-grant.css +18 -0
- package/dist/public/css/views/inbox.css +46 -0
- package/dist/public/css/views/portfolio.css +46 -0
- package/dist/public/css/views/profile.css +29 -4
- package/dist/public/js/services/apps.js +19 -3
- package/dist/public/js/services/messages-ai-prompts.js +38 -0
- package/dist/public/js/services/messages.js +26 -4
- package/dist/public/js/services/notebook.js +76 -0
- package/dist/public/js/services/unfurl.js +43 -0
- package/dist/public/lib/samples/LICENSE.md +11 -0
- package/dist/public/lib/samples/bass/A1.mp3 +0 -0
- package/dist/public/lib/samples/bass/B2.mp3 +0 -0
- package/dist/public/lib/samples/bass/D2.mp3 +0 -0
- package/dist/public/lib/samples/bass/E1.mp3 +0 -0
- package/dist/public/lib/samples/bass/E3.mp3 +0 -0
- package/dist/public/lib/samples/bass/G2.mp3 +0 -0
- package/dist/public/lib/samples/epiano/C2.mp3 +0 -0
- package/dist/public/lib/samples/epiano/C3.mp3 +0 -0
- package/dist/public/lib/samples/epiano/C4.mp3 +0 -0
- package/dist/public/lib/samples/epiano/C5.mp3 +0 -0
- package/dist/public/lib/samples/epiano/C6.mp3 +0 -0
- package/dist/public/lib/samples/epiano/G2.mp3 +0 -0
- package/dist/public/lib/samples/epiano/G3.mp3 +0 -0
- package/dist/public/lib/samples/epiano/G4.mp3 +0 -0
- package/dist/public/lib/samples/epiano/G5.mp3 +0 -0
- package/dist/public/lib/samples/guitar-steel/A2.mp3 +0 -0
- package/dist/public/lib/samples/guitar-steel/A4.mp3 +0 -0
- package/dist/public/lib/samples/guitar-steel/B3.mp3 +0 -0
- package/dist/public/lib/samples/guitar-steel/D3.mp3 +0 -0
- package/dist/public/lib/samples/guitar-steel/E2.mp3 +0 -0
- package/dist/public/lib/samples/guitar-steel/E4.mp3 +0 -0
- package/dist/public/lib/samples/guitar-steel/E5.mp3 +0 -0
- package/dist/public/lib/samples/guitar-steel/G3.mp3 +0 -0
- package/dist/public/lib/samples/organ/C2.mp3 +0 -0
- package/dist/public/lib/samples/organ/C3.mp3 +0 -0
- package/dist/public/lib/samples/organ/C4.mp3 +0 -0
- package/dist/public/lib/samples/organ/C5.mp3 +0 -0
- package/dist/public/lib/samples/organ/C6.mp3 +0 -0
- package/dist/public/lib/samples/organ/G2.mp3 +0 -0
- package/dist/public/lib/samples/organ/G3.mp3 +0 -0
- package/dist/public/lib/samples/organ/G4.mp3 +0 -0
- package/dist/public/lib/samples/organ/G5.mp3 +0 -0
- package/dist/public/lib/samples/strings/C2.mp3 +0 -0
- package/dist/public/lib/samples/strings/C3.mp3 +0 -0
- package/dist/public/lib/samples/strings/C4.mp3 +0 -0
- package/dist/public/lib/samples/strings/C5.mp3 +0 -0
- package/dist/public/lib/samples/strings/C6.mp3 +0 -0
- package/dist/public/lib/samples/strings/G2.mp3 +0 -0
- package/dist/public/lib/samples/strings/G3.mp3 +0 -0
- package/dist/public/lib/samples/strings/G4.mp3 +0 -0
- package/dist/public/lib/samples/strings/G5.mp3 +0 -0
- package/dist/public/lib/samples/trumpet/A3.mp3 +0 -0
- package/dist/public/lib/samples/trumpet/A4.mp3 +0 -0
- package/dist/public/lib/samples/trumpet/A5.mp3 +0 -0
- package/dist/public/lib/samples/trumpet/C4.mp3 +0 -0
- package/dist/public/lib/samples/trumpet/C5.mp3 +0 -0
- package/dist/public/lib/samples/trumpet/E3.mp3 +0 -0
- package/dist/public/lib/samples/trumpet/E4.mp3 +0 -0
- package/dist/public/lib/samples/trumpet/E5.mp3 +0 -0
- package/dist/public/spa.html +2 -0
- package/dist/public/views/app-grant.js +24 -4
- package/dist/public/views/portfolio.js +61 -6
- package/dist/public/views/profile/apps-tab.js +18 -1
- package/dist/public/views/profile/inbox-tab/ai-actions.js +86 -0
- package/dist/public/views/profile/inbox-tab/components.js +124 -9
- package/dist/public/views/profile/inbox-tab/helpers.js +26 -0
- package/dist/public/views/profile/inbox-tab/panels.js +18 -3
- package/dist/public/views/profile/inbox-tab/use-thread-ux.js +18 -1
- package/dist/public/views/profile/inbox-tab.js +46 -49
- package/dist/public/views/profile/landing-page.cards.js +7 -0
- package/dist/public/views/profile/landing-page.js +12 -1
- package/dist/public/views/profile/skills-tab.js +1 -1
- package/dist/public/views/public-workspace-viewer.js +1 -1
- package/dist/public/views/start.js +2 -1
- package/dist/scripts/build-sdk-libs.d.ts +16 -0
- package/dist/scripts/build-sdk-libs.d.ts.map +1 -0
- package/dist/scripts/build-sdk-libs.js +112 -0
- package/dist/scripts/build-sdk-libs.js.map +1 -0
- package/dist/src/cli/connect/auth.d.ts +15 -0
- package/dist/src/cli/connect/auth.d.ts.map +1 -1
- package/dist/src/cli/connect/auth.js +25 -6
- package/dist/src/cli/connect/auth.js.map +1 -1
- package/dist/src/cli/connect/keychain.d.ts +9 -0
- package/dist/src/cli/connect/keychain.d.ts.map +1 -1
- package/dist/src/cli/connect/keychain.js +21 -8
- package/dist/src/cli/connect/keychain.js.map +1 -1
- package/dist/src/cli/connect/mcp/tools/exchange.d.ts +18 -0
- package/dist/src/cli/connect/mcp/tools/exchange.d.ts.map +1 -0
- package/dist/src/cli/connect/mcp/tools/exchange.js +170 -0
- package/dist/src/cli/connect/mcp/tools/exchange.js.map +1 -0
- package/dist/src/cli/connect/mcp/tools/index.d.ts.map +1 -1
- package/dist/src/cli/connect/mcp/tools/index.js +2 -0
- package/dist/src/cli/connect/mcp/tools/index.js.map +1 -1
- package/dist/src/cli/connect/tool-call-defs-exchange.d.ts +3 -0
- package/dist/src/cli/connect/tool-call-defs-exchange.d.ts.map +1 -0
- package/dist/src/cli/connect/tool-call-defs-exchange.js +146 -0
- package/dist/src/cli/connect/tool-call-defs-exchange.js.map +1 -0
- package/dist/src/cli/connect/tool-call.d.ts.map +1 -1
- package/dist/src/cli/connect/tool-call.js +2 -0
- package/dist/src/cli/connect/tool-call.js.map +1 -1
- package/dist/src/commerce/session-service.d.ts +14 -0
- package/dist/src/commerce/session-service.d.ts.map +1 -1
- package/dist/src/commerce/session-service.js +1 -1
- package/dist/src/commerce/session-service.js.map +1 -1
- package/dist/src/data/app-templates/shells.d.ts +7 -3
- package/dist/src/data/app-templates/shells.d.ts.map +1 -1
- package/dist/src/data/app-templates/shells.js +10 -6
- package/dist/src/data/app-templates/shells.js.map +1 -1
- package/dist/src/data/appdev-pitfalls.d.ts +2 -0
- package/dist/src/data/appdev-pitfalls.d.ts.map +1 -1
- package/dist/src/data/appdev-pitfalls.js +24 -1
- package/dist/src/data/appdev-pitfalls.js.map +1 -1
- package/dist/src/generated/api-types.d.ts +1565 -9
- package/dist/src/generated/api-types.d.ts.map +1 -1
- package/dist/src/mcp/annotations.d.ts.map +1 -1
- package/dist/src/mcp/annotations.js +20 -0
- package/dist/src/mcp/annotations.js.map +1 -1
- package/dist/src/mcp/apps.d.ts.map +1 -1
- package/dist/src/mcp/apps.js +8 -4
- package/dist/src/mcp/apps.js.map +1 -1
- package/dist/src/mcp/catalog/definitions/exchange.d.ts +18 -0
- package/dist/src/mcp/catalog/definitions/exchange.d.ts.map +1 -0
- package/dist/src/mcp/catalog/definitions/exchange.js +182 -0
- package/dist/src/mcp/catalog/definitions/exchange.js.map +1 -0
- package/dist/src/mcp/catalog/definitions.d.ts.map +1 -1
- package/dist/src/mcp/catalog/definitions.js +2 -0
- package/dist/src/mcp/catalog/definitions.js.map +1 -1
- package/dist/src/mcp/catalog/scopes.d.ts.map +1 -1
- package/dist/src/mcp/catalog/scopes.js +22 -0
- package/dist/src/mcp/catalog/scopes.js.map +1 -1
- package/dist/src/mcp/catalog/surfaces.d.ts.map +1 -1
- package/dist/src/mcp/catalog/surfaces.js +14 -0
- package/dist/src/mcp/catalog/surfaces.js.map +1 -1
- package/dist/src/mcp/exchange-run.d.ts +5 -0
- package/dist/src/mcp/exchange-run.d.ts.map +1 -0
- package/dist/src/mcp/exchange-run.js +226 -0
- package/dist/src/mcp/exchange-run.js.map +1 -0
- package/dist/src/mcp/exchange.d.ts +5 -0
- package/dist/src/mcp/exchange.d.ts.map +1 -0
- package/dist/src/mcp/exchange.js +351 -0
- package/dist/src/mcp/exchange.js.map +1 -0
- package/dist/src/mcp/extensions.d.ts.map +1 -1
- package/dist/src/mcp/extensions.js +11 -0
- package/dist/src/mcp/extensions.js.map +1 -1
- package/dist/src/mcp/index.d.ts.map +1 -1
- package/dist/src/mcp/index.js +6 -2
- package/dist/src/mcp/index.js.map +1 -1
- package/dist/src/models/app-tool-schemas.d.ts +55 -1
- package/dist/src/models/app-tool-schemas.d.ts.map +1 -1
- package/dist/src/models/app-tool-schemas.js +20 -1
- package/dist/src/models/app-tool-schemas.js.map +1 -1
- package/dist/src/models/offer-schemas.d.ts +2 -2
- package/dist/src/models/schemas.d.ts +1 -0
- package/dist/src/models/schemas.d.ts.map +1 -1
- package/dist/src/models/schemas.js +3 -0
- package/dist/src/models/schemas.js.map +1 -1
- package/dist/src/routes/agents/device-auth.d.ts +4 -0
- package/dist/src/routes/agents/device-auth.d.ts.map +1 -1
- package/dist/src/routes/agents/device-auth.js +19 -4
- package/dist/src/routes/agents/device-auth.js.map +1 -1
- package/dist/src/routes/app-grants.d.ts +6 -0
- package/dist/src/routes/app-grants.d.ts.map +1 -1
- package/dist/src/routes/app-grants.js +35 -2
- package/dist/src/routes/app-grants.js.map +1 -1
- package/dist/src/routes/apps/fork-manage.d.ts.map +1 -1
- package/dist/src/routes/apps/fork-manage.js +24 -1
- package/dist/src/routes/apps/fork-manage.js.map +1 -1
- package/dist/src/routes/apps/publish.d.ts +2 -0
- package/dist/src/routes/apps/publish.d.ts.map +1 -1
- package/dist/src/routes/apps/publish.js +27 -1
- package/dist/src/routes/apps/publish.js.map +1 -1
- package/dist/src/routes/apps-cost.d.ts +25 -0
- package/dist/src/routes/apps-cost.d.ts.map +1 -0
- package/dist/src/routes/apps-cost.js +107 -0
- package/dist/src/routes/apps-cost.js.map +1 -0
- package/dist/src/routes/exchange-market.d.ts +28 -0
- package/dist/src/routes/exchange-market.d.ts.map +1 -0
- package/dist/src/routes/exchange-market.js +454 -0
- package/dist/src/routes/exchange-market.js.map +1 -0
- package/dist/src/routes/exchange.d.ts +33 -0
- package/dist/src/routes/exchange.d.ts.map +1 -0
- package/dist/src/routes/exchange.js +395 -0
- package/dist/src/routes/exchange.js.map +1 -0
- package/dist/src/routes/extensions/entitlement-gate.d.ts +33 -0
- package/dist/src/routes/extensions/entitlement-gate.d.ts.map +1 -0
- package/dist/src/routes/extensions/entitlement-gate.js +143 -0
- package/dist/src/routes/extensions/entitlement-gate.js.map +1 -0
- package/dist/src/routes/extensions/manifest.d.ts +9 -0
- package/dist/src/routes/extensions/manifest.d.ts.map +1 -1
- package/dist/src/routes/extensions/manifest.js +33 -1
- package/dist/src/routes/extensions/manifest.js.map +1 -1
- package/dist/src/routes/extensions/paywall.d.ts.map +1 -1
- package/dist/src/routes/extensions/paywall.js +9 -0
- package/dist/src/routes/extensions/paywall.js.map +1 -1
- package/dist/src/routes/federation-sync/messaging.d.ts.map +1 -1
- package/dist/src/routes/federation-sync/messaging.js +3 -1
- package/dist/src/routes/federation-sync/messaging.js.map +1 -1
- package/dist/src/routes/ghii/recovery.d.ts +2 -0
- package/dist/src/routes/ghii/recovery.d.ts.map +1 -1
- package/dist/src/routes/ghii/recovery.js +7 -0
- package/dist/src/routes/ghii/recovery.js.map +1 -1
- package/dist/src/routes/ghii/register-login.d.ts +4 -0
- package/dist/src/routes/ghii/register-login.d.ts.map +1 -1
- package/dist/src/routes/ghii/register-login.js +45 -22
- package/dist/src/routes/ghii/register-login.js.map +1 -1
- package/dist/src/routes/ghii/web-verify.d.ts +5 -0
- package/dist/src/routes/ghii/web-verify.d.ts.map +1 -1
- package/dist/src/routes/ghii/web-verify.js +24 -3
- package/dist/src/routes/ghii/web-verify.js.map +1 -1
- package/dist/src/routes/libs/sdk-serve.d.ts +11 -0
- package/dist/src/routes/libs/sdk-serve.d.ts.map +1 -0
- package/dist/src/routes/libs/sdk-serve.js +53 -0
- package/dist/src/routes/libs/sdk-serve.js.map +1 -0
- package/dist/src/routes/libs.d.ts +7 -1
- package/dist/src/routes/libs.d.ts.map +1 -1
- package/dist/src/routes/libs.js +65 -146
- package/dist/src/routes/libs.js.map +1 -1
- package/dist/src/routes/messages.d.ts +3 -0
- package/dist/src/routes/messages.d.ts.map +1 -1
- package/dist/src/routes/messages.js +9 -0
- package/dist/src/routes/messages.js.map +1 -1
- package/dist/src/routes/notifications.d.ts +6 -3
- package/dist/src/routes/notifications.d.ts.map +1 -1
- package/dist/src/routes/notifications.js +32 -3
- package/dist/src/routes/notifications.js.map +1 -1
- package/dist/src/routes/organisms/workspace-access.d.ts.map +1 -1
- package/dist/src/routes/organisms/workspace-access.js +38 -20
- package/dist/src/routes/organisms/workspace-access.js.map +1 -1
- package/dist/src/routes/unfurl.d.ts +27 -0
- package/dist/src/routes/unfurl.d.ts.map +1 -0
- package/dist/src/routes/unfurl.js +180 -0
- package/dist/src/routes/unfurl.js.map +1 -0
- package/dist/src/routes/upload.d.ts +2 -0
- package/dist/src/routes/upload.d.ts.map +1 -1
- package/dist/src/routes/upload.js +7 -0
- package/dist/src/routes/upload.js.map +1 -1
- package/dist/src/routes/webmcp.d.ts +3 -0
- package/dist/src/routes/webmcp.d.ts.map +1 -1
- package/dist/src/routes/webmcp.js +55 -0
- package/dist/src/routes/webmcp.js.map +1 -1
- package/dist/src/server-bootstrap/routes-loader.d.ts +1 -0
- package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
- package/dist/src/server-bootstrap/routes-loader.js +8 -0
- package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
- package/dist/src/services/agent-face.d.ts +10 -2
- package/dist/src/services/agent-face.d.ts.map +1 -1
- package/dist/src/services/agent-face.js +10 -4
- package/dist/src/services/agent-face.js.map +1 -1
- package/dist/src/services/app-tool-interfaces.d.ts +37 -0
- package/dist/src/services/app-tool-interfaces.d.ts.map +1 -0
- package/dist/src/services/app-tool-interfaces.js +101 -0
- package/dist/src/services/app-tool-interfaces.js.map +1 -0
- package/dist/src/services/build-app-prompt.d.ts +3 -0
- package/dist/src/services/build-app-prompt.d.ts.map +1 -1
- package/dist/src/services/build-app-prompt.js +12 -1
- package/dist/src/services/build-app-prompt.js.map +1 -1
- package/dist/src/services/contacts.d.ts +25 -1
- package/dist/src/services/contacts.d.ts.map +1 -1
- package/dist/src/services/contacts.js +30 -0
- package/dist/src/services/contacts.js.map +1 -1
- package/dist/src/services/entitlement-money.d.ts +55 -0
- package/dist/src/services/entitlement-money.d.ts.map +1 -0
- package/dist/src/services/entitlement-money.js +68 -0
- package/dist/src/services/entitlement-money.js.map +1 -0
- package/dist/src/services/exchange-market.d.ts +247 -0
- package/dist/src/services/exchange-market.d.ts.map +1 -0
- package/dist/src/services/exchange-market.js +254 -0
- package/dist/src/services/exchange-market.js.map +1 -0
- package/dist/src/services/exchange-proposals.d.ts +47 -0
- package/dist/src/services/exchange-proposals.d.ts.map +1 -0
- package/dist/src/services/exchange-proposals.js +71 -0
- package/dist/src/services/exchange-proposals.js.map +1 -0
- package/dist/src/services/exchange-work.d.ts +30 -0
- package/dist/src/services/exchange-work.d.ts.map +1 -0
- package/dist/src/services/exchange-work.js +43 -0
- package/dist/src/services/exchange-work.js.map +1 -0
- package/dist/src/services/message-send.d.ts.map +1 -1
- package/dist/src/services/message-send.js +4 -1
- package/dist/src/services/message-send.js.map +1 -1
- package/dist/src/services/metered-entitlements.d.ts +203 -0
- package/dist/src/services/metered-entitlements.d.ts.map +1 -0
- package/dist/src/services/metered-entitlements.js +264 -0
- package/dist/src/services/metered-entitlements.js.map +1 -0
- package/dist/src/services/notify.d.ts +7 -0
- package/dist/src/services/notify.d.ts.map +1 -1
- package/dist/src/services/notify.js +35 -0
- package/dist/src/services/notify.js.map +1 -1
- package/dist/src/services/owner-memory.d.ts +16 -0
- package/dist/src/services/owner-memory.d.ts.map +1 -1
- package/dist/src/services/owner-memory.js +23 -0
- package/dist/src/services/owner-memory.js.map +1 -1
- package/dist/src/services/owner-provisioning.d.ts +5 -0
- package/dist/src/services/owner-provisioning.d.ts.map +1 -1
- package/dist/src/services/owner-provisioning.js +20 -2
- package/dist/src/services/owner-provisioning.js.map +1 -1
- package/dist/src/storage/providers/postgres-kysely/methods/apps.d.ts +1 -0
- package/dist/src/storage/providers/postgres-kysely/methods/apps.d.ts.map +1 -1
- package/dist/src/storage/providers/postgres-kysely/methods/apps.js +2 -0
- package/dist/src/storage/providers/postgres-kysely/methods/apps.js.map +1 -1
- package/dist/src/storage/providers/postgres-kysely/methods/identity.d.ts +1 -0
- package/dist/src/storage/providers/postgres-kysely/methods/identity.d.ts.map +1 -1
- package/dist/src/storage/providers/postgres-kysely/methods/identity.js +4 -0
- package/dist/src/storage/providers/postgres-kysely/methods/identity.js.map +1 -1
- package/dist/src/storage/providers/postgres-kysely/migrations/0011_unique_verified_email.sql +25 -0
- package/dist/src/storage/providers/sqlite/methods/apps.d.ts +1 -0
- package/dist/src/storage/providers/sqlite/methods/apps.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/methods/apps.js +2 -0
- package/dist/src/storage/providers/sqlite/methods/apps.js.map +1 -1
- package/dist/src/storage/providers/sqlite/methods/identity-nodes.d.ts +1 -0
- package/dist/src/storage/providers/sqlite/methods/identity-nodes.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/methods/identity-nodes.js +4 -0
- package/dist/src/storage/providers/sqlite/methods/identity-nodes.js.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.js +20 -0
- package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
- package/dist/src/storage/repositories/app.repository.d.ts +0 -0
- package/dist/src/storage/repositories/app.repository.d.ts.map +1 -1
- package/dist/src/storage/repositories/identity.repository.d.ts +6 -0
- package/dist/src/storage/repositories/identity.repository.d.ts.map +1 -1
- package/dist/src/storage/types/apps.d.ts +1 -0
- package/dist/src/storage/types/apps.d.ts.map +1 -1
- package/dist/src/storage/types/organisms-federation.d.ts +21 -0
- package/dist/src/storage/types/organisms-federation.d.ts.map +1 -1
- package/dist/src/utils/app-mobile-lint.d.ts +17 -0
- package/dist/src/utils/app-mobile-lint.d.ts.map +1 -0
- package/dist/src/utils/app-mobile-lint.js +40 -0
- package/dist/src/utils/app-mobile-lint.js.map +1 -0
- package/dist/static/app-catalog/_template.html +12 -77
- package/dist/static/app-catalog/js/apps-io.js +51 -165
- package/dist/static/app-catalog/js/cost.js +100 -0
- package/dist/static/app-catalog/js/db.js +23 -93
- package/dist/static/app-catalog/js/detail.js +158 -24
- package/dist/static/app-catalog/js/favorites.js +71 -0
- package/dist/static/app-catalog/js/i18n-data.js +90 -16
- package/dist/static/app-catalog/js/main.js +23 -103
- package/dist/static/app-catalog/js/migrate.js +88 -0
- package/dist/static/app-catalog/js/monetize.js +1 -1
- package/dist/static/app-catalog/js/promote.js +70 -0
- package/dist/static/app-catalog/js/render.js +26 -139
- package/dist/static/app-catalog/js/server-io.js +126 -223
- package/dist/static/app-catalog/js/settings.js +11 -204
- package/dist/static/app-catalog/styles/app-catalog.css +40 -0
- package/dist/static/app-catalog.html +790 -912
- package/dist/static/sdk-libs/_core/config.js +52 -0
- package/dist/static/sdk-libs/_core/http.js +52 -0
- package/dist/static/sdk-libs/_core/namespace.js +33 -0
- package/dist/static/sdk-libs/_core/sdk-globals.d.ts +50 -0
- package/dist/static/sdk-libs/_core/session.js +66 -0
- package/dist/static/sdk-libs/agentface/index.js +102 -0
- package/dist/static/sdk-libs/agents/index.js +267 -0
- package/dist/static/sdk-libs/ai/index.js +153 -0
- package/dist/static/sdk-libs/audio/core.js +111 -0
- package/dist/static/sdk-libs/audio/index.js +380 -0
- package/dist/static/sdk-libs/audio/instruments.js +369 -0
- package/dist/static/sdk-libs/auth/config.js +46 -0
- package/dist/static/sdk-libs/auth/crypto.js +166 -0
- package/dist/static/sdk-libs/auth/events.js +27 -0
- package/dist/static/sdk-libs/auth/i18n.js +64 -0
- package/dist/static/sdk-libs/auth/index.js +37 -0
- package/dist/static/sdk-libs/auth/modal.js +545 -0
- package/dist/static/sdk-libs/auth/pill.js +167 -0
- package/dist/static/sdk-libs/auth/session.js +792 -0
- package/dist/static/sdk-libs/auth/signup.js +232 -0
- package/dist/static/sdk-libs/auth/theme.js +96 -0
- package/dist/static/sdk-libs/capabilities/index.js +179 -0
- package/dist/static/sdk-libs/commerce/index.js +243 -0
- package/dist/static/sdk-libs/data/index.js +188 -0
- package/dist/static/sdk-libs/dist/aimeat-agentface.js +91 -0
- package/dist/static/sdk-libs/dist/aimeat-agents.js +315 -0
- package/dist/static/sdk-libs/dist/aimeat-ai.js +168 -0
- package/dist/static/sdk-libs/dist/aimeat-audio.js +979 -0
- package/dist/static/sdk-libs/dist/aimeat-auth.js +1831 -0
- package/dist/static/sdk-libs/dist/aimeat-capabilities.js +177 -0
- package/dist/static/sdk-libs/dist/aimeat-commerce.js +265 -0
- package/dist/static/sdk-libs/dist/aimeat-data.js +205 -0
- package/dist/static/sdk-libs/dist/aimeat-editor.js +299 -0
- package/dist/static/sdk-libs/dist/aimeat-header.js +270 -0
- package/dist/static/sdk-libs/dist/aimeat-intake.js +94 -0
- package/dist/static/sdk-libs/dist/aimeat-live.js +265 -0
- package/dist/static/sdk-libs/dist/aimeat-markdown.js +602 -0
- package/dist/static/sdk-libs/dist/aimeat-organism.js +394 -0
- package/dist/static/sdk-libs/dist/aimeat-portfolio-standalone.js +90 -0
- package/dist/static/sdk-libs/dist/aimeat-social.js +157 -0
- package/dist/static/sdk-libs/dist/aimeat-speech.js +300 -0
- package/dist/static/sdk-libs/dist/aimeat-storage.js +210 -0
- package/dist/static/sdk-libs/dist/aimeat-tunnel.js +383 -0
- package/dist/static/sdk-libs/dist/aimeat-wallet.js +118 -0
- package/dist/static/sdk-libs/dist/aimeat-webmcp.js +187 -0
- package/dist/static/sdk-libs/dist/aimeat-work.js +213 -0
- package/dist/static/sdk-libs/dist/aimeat-workflows.js +147 -0
- package/dist/static/sdk-libs/editor/index.js +229 -0
- package/dist/static/sdk-libs/header/index.js +249 -0
- package/dist/static/sdk-libs/intake/index.js +75 -0
- package/dist/static/sdk-libs/live/index.js +156 -0
- package/dist/static/sdk-libs/markdown/index.js +522 -0
- package/dist/static/sdk-libs/organism/index.js +381 -0
- package/dist/static/sdk-libs/portfolio-standalone/index.js +78 -0
- package/dist/static/sdk-libs/social/index.js +140 -0
- package/dist/static/sdk-libs/speech/index.js +321 -0
- package/dist/static/sdk-libs/storage/index.js +189 -0
- package/dist/static/sdk-libs/tunnel/index.js +428 -0
- package/dist/static/sdk-libs/wallet/index.js +115 -0
- package/dist/static/sdk-libs/webmcp/index.js +185 -0
- package/dist/static/sdk-libs/work/index.js +191 -0
- package/dist/static/sdk-libs/workflows/index.js +113 -0
- package/package.json +5 -2
|
@@ -45,8 +45,9 @@ import { h } from 'preact';
|
|
|
45
45
|
import { useState, useEffect, useRef } from 'preact/hooks';
|
|
46
46
|
import htm from 'htm';
|
|
47
47
|
const html = htm.bind(h);
|
|
48
|
-
import { t } from '/js/i18n.js';
|
|
48
|
+
import { t, getLocale } from '/js/i18n.js';
|
|
49
49
|
import { escHtml } from '/js/utils.js';
|
|
50
|
+
import { listApps } from '/js/services/apps.js';
|
|
50
51
|
import { NODE_URL, tr, stampCspNonce, getSession } from './portfolio/shared.js';
|
|
51
52
|
import { PortfolioBuilder } from './portfolio/builder.js';
|
|
52
53
|
|
|
@@ -56,6 +57,8 @@ function PortfolioViewer({ username, navigate }) {
|
|
|
56
57
|
const [data, setData] = useState(null);
|
|
57
58
|
const [loading, setLoading] = useState(true);
|
|
58
59
|
const [errMsg, setErrMsg] = useState(null);
|
|
60
|
+
// Apps the owner promoted (their PUBLIC app-catalog.promoted doc), joined to app metadata.
|
|
61
|
+
const [promotedApps, setPromotedApps] = useState([]);
|
|
59
62
|
const frameRef = useRef(null);
|
|
60
63
|
|
|
61
64
|
// ββ Portfolio bridge ββ
|
|
@@ -144,6 +147,56 @@ function PortfolioViewer({ username, navigate }) {
|
|
|
144
147
|
});
|
|
145
148
|
}, [username]);
|
|
146
149
|
|
|
150
|
+
// Load the owner's promoted apps once we know their GHII (data.owner_gaiis[0]). The doc is PUBLIC,
|
|
151
|
+
// read anonymously via /v1/memory/:gaii/:key; app names/icons come from the public /v1/apps list.
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
const ownerGhii = Array.isArray(data?.owner_gaiis) ? data.owner_gaiis[0] : null;
|
|
154
|
+
if (!ownerGhii) { setPromotedApps([]); return undefined; }
|
|
155
|
+
let cancelled = false;
|
|
156
|
+
(async () => {
|
|
157
|
+
try {
|
|
158
|
+
const resp = await fetch(`${NODE_URL}/v1/memory/${encodeURIComponent(ownerGhii)}/${encodeURIComponent('app-catalog.promoted')}?soft=1`);
|
|
159
|
+
const j = await resp.json().catch(() => null);
|
|
160
|
+
const items = (j && j.data && j.data.value && Array.isArray(j.data.value.items)) ? j.data.value.items : [];
|
|
161
|
+
if (!items.length) { if (!cancelled) setPromotedApps([]); return; }
|
|
162
|
+
let byRef = {};
|
|
163
|
+
try {
|
|
164
|
+
const apps = await listApps();
|
|
165
|
+
(apps || []).forEach(a => { byRef[`${a.owner}/${a.filename || a.name}`] = a; });
|
|
166
|
+
} catch { /* names fall back to the filename */ }
|
|
167
|
+
const cards = items.filter(it => it && typeof it.ref === 'string').map(it => {
|
|
168
|
+
const slash = it.ref.indexOf('/');
|
|
169
|
+
const owner = it.ref.slice(0, slash);
|
|
170
|
+
const filename = it.ref.slice(slash + 1);
|
|
171
|
+
const app = byRef[it.ref];
|
|
172
|
+
return {
|
|
173
|
+
ref: it.ref, owner, filename, text: it.text || {},
|
|
174
|
+
name: (app && app.manifest && app.manifest.name) || filename.replace(/\.html?$/i, ''),
|
|
175
|
+
icon: (app && app.manifest && app.manifest.icon) || '\u{1F4E6}',
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
if (!cancelled) setPromotedApps(cards);
|
|
179
|
+
} catch { if (!cancelled) setPromotedApps([]); }
|
|
180
|
+
})();
|
|
181
|
+
return () => { cancelled = true; };
|
|
182
|
+
}, [data]);
|
|
183
|
+
|
|
184
|
+
// The "Promoted apps" block β the owner's showcased apps with their per-language pitch.
|
|
185
|
+
const promotedBlock = promotedApps.length ? html`
|
|
186
|
+
<div class="portfolio-promoted">
|
|
187
|
+
<h3 class="portfolio-promoted-title">\u{1F4E3} ${tr('portfolio.viewer.promoted', 'Promoted apps')}</h3>
|
|
188
|
+
<div class="portfolio-promoted-grid">
|
|
189
|
+
${promotedApps.map(p => {
|
|
190
|
+
const pitch = p.text[getLocale()] || p.text.en || p.text.fi || '';
|
|
191
|
+
return html`
|
|
192
|
+
<a class="portfolio-promoted-card" href=${`${NODE_URL}/v1/apps/${encodeURIComponent(p.owner)}/${encodeURIComponent(p.filename)}?mode=inline`} target="_blank" rel="noopener">
|
|
193
|
+
<div class="portfolio-promoted-name">${p.icon} ${escHtml(p.name)}</div>
|
|
194
|
+
${pitch ? html`<div class="portfolio-promoted-pitch">${escHtml(pitch)}</div>` : ''}
|
|
195
|
+
</a>`;
|
|
196
|
+
})}
|
|
197
|
+
</div>
|
|
198
|
+
</div>` : '';
|
|
199
|
+
|
|
147
200
|
if (loading) return html`<div class="portfolio-container"><div class="view-loading">Loading portfolio...</div></div>`;
|
|
148
201
|
|
|
149
202
|
if (errMsg || !data) {
|
|
@@ -183,6 +236,7 @@ function PortfolioViewer({ username, navigate }) {
|
|
|
183
236
|
βΆ ${tr('portfolio.viewer.fullscreen', 'Fullscreen')}
|
|
184
237
|
</button>
|
|
185
238
|
</div>
|
|
239
|
+
${promotedBlock}
|
|
186
240
|
<iframe ref=${frameRef} class="portfolio-viewer-frame portfolio-viewer-frame-full"
|
|
187
241
|
srcdoc=${stampCspNonce(data.portfolio_html)} sandbox="allow-scripts"
|
|
188
242
|
onLoad=${(e) => postAuthState(e.target.contentWindow)}></iframe>
|
|
@@ -190,16 +244,17 @@ function PortfolioViewer({ username, navigate }) {
|
|
|
190
244
|
`;
|
|
191
245
|
}
|
|
192
246
|
|
|
193
|
-
// No portfolio HTML β show basic profile info
|
|
247
|
+
// No portfolio HTML β show basic profile info + any promoted apps.
|
|
194
248
|
return html`
|
|
195
249
|
<div class="portfolio-container">
|
|
196
|
-
<div class="portfolio-
|
|
250
|
+
<div class="portfolio-profile-head">
|
|
197
251
|
<h2>${escHtml(data.display_name || username)}</h2>
|
|
198
252
|
<p>${data.bio ? escHtml(data.bio) : t('portfolio.viewer.notFoundDesc')}</p>
|
|
199
|
-
<button class="btn-ghost" style="margin-top:1rem;" onClick=${() => navigate('/v1/portal')}>
|
|
200
|
-
${t('portfolio.viewer.backToPortal')}
|
|
201
|
-
</button>
|
|
202
253
|
</div>
|
|
254
|
+
${promotedBlock}
|
|
255
|
+
<button class="btn-ghost" style="margin-top:1rem;" onClick=${() => navigate('/v1/portal')}>
|
|
256
|
+
${t('portfolio.viewer.backToPortal')}
|
|
257
|
+
</button>
|
|
203
258
|
</div>
|
|
204
259
|
`;
|
|
205
260
|
}
|
|
@@ -24,11 +24,12 @@ import { h } from 'preact';
|
|
|
24
24
|
import { useState, useEffect, useRef, useCallback } from 'preact/hooks';
|
|
25
25
|
import htm from 'htm';
|
|
26
26
|
const html = htm.bind(h);
|
|
27
|
-
import { t } from '/js/i18n.js';
|
|
27
|
+
import { t, getLocale } from '/js/i18n.js';
|
|
28
28
|
import { escHtml, handleImgError, timeAgo } from '/js/utils.js';
|
|
29
29
|
import { Spinner } from './shared.js';
|
|
30
30
|
import { useConfirm } from '/components/Modal.js';
|
|
31
31
|
import { listApps, uploadApp, deleteApp, patchApp, deployAppAgent, undeployAppAgent, appAgentStatus } from '/js/services/apps.js';
|
|
32
|
+
import { getMemory } from '/js/services/memory.js';
|
|
32
33
|
import { listAgents } from '/js/services/agents.js';
|
|
33
34
|
import { listOrganisms } from '/js/services/organisms.js';
|
|
34
35
|
import * as skillsService from '/js/services/skills.js';
|
|
@@ -289,6 +290,8 @@ export default function AppsTab({ session, showToast, onStats }) {
|
|
|
289
290
|
const [editDesc, setEditDesc] = useState('');
|
|
290
291
|
const [editingAgents, setEditingAgents] = useState(null);
|
|
291
292
|
const [agentsJson, setAgentsJson] = useState('');
|
|
293
|
+
// Promoted apps (from the owner's public app-catalog.promoted doc), keyed by "owner/filename".
|
|
294
|
+
const [promoted, setPromoted] = useState({});
|
|
292
295
|
|
|
293
296
|
// Reload the app lists when the session becomes available / changes. loadData closes over the
|
|
294
297
|
// optional onStats prop (not guaranteed memoized), so we key on session only to avoid re-running
|
|
@@ -306,6 +309,14 @@ export default function AppsTab({ session, showToast, onStats }) {
|
|
|
306
309
|
setAllApps(list);
|
|
307
310
|
onStats?.({ apps: own.length });
|
|
308
311
|
} catch { setMyApps([]); setAllApps([]); }
|
|
312
|
+
// Promoted apps: read the owner's own app-catalog.promoted doc (soft = no 404 when unset).
|
|
313
|
+
try {
|
|
314
|
+
const res = await getMemory('app-catalog.promoted', { soft: true });
|
|
315
|
+
const items = res?.data?.value?.items;
|
|
316
|
+
const map = {};
|
|
317
|
+
if (Array.isArray(items)) items.forEach(it => { if (it && it.ref) map[it.ref] = it.text || {}; });
|
|
318
|
+
setPromoted(map);
|
|
319
|
+
} catch { setPromoted({}); }
|
|
309
320
|
}
|
|
310
321
|
|
|
311
322
|
async function handleUpload(file, description, screenshot, accessCode) {
|
|
@@ -480,12 +491,18 @@ export default function AppsTab({ session, showToast, onStats }) {
|
|
|
480
491
|
${a.protected ? html`<span class="badge badge-warn">\u{1F512}</span>` : ''}
|
|
481
492
|
${a.parked ? html`<span class="badge badge-dim">\u{1F17F}οΈ ${t('profile.apps.parkedBadge') || 'Parked'}</span>` : ''}
|
|
482
493
|
${a.manifest?.cortex?.agents?.length ? html`<span class="badge badge-info" title=${t('profile.apps.agentBadgeHint') || 'This app ships its own agent β deploy it onto your fleet below'}>\u{1F916} ${t('profile.apps.agentBadge') || 'Ships an agent'}</span>` : ''}
|
|
494
|
+
${promoted[`${a.owner || session.owner}/${a.filename || a.name}`] ? html`<span class="badge badge-success" title=${t('profile.apps.promotedBadgeHint') || 'Promoted on your public profile β manage the pitch in the app catalog'}>\u{1F4E3} ${t('profile.apps.promotedBadge') || 'Promoted'}</span>` : ''}
|
|
483
495
|
${a.operator_hidden ? html`<span class="badge badge-danger">\u{1F6AB} ${t('profile.apps.operatorHiddenBadge') || 'Moderated by operator: hidden'}</span>` : ''}
|
|
484
496
|
</div>
|
|
485
497
|
</div>
|
|
486
498
|
${a.operator_hidden ? html`<div class="text-meta-sm mb-half">${t('profile.apps.operatorHiddenHint') || 'An operator has hidden this app from public view. Only you can still see it. Contact the operator to appeal.'}${a.operator_hide_reason ? ' β ' + escHtml(a.operator_hide_reason) : ''}</div>` : ''}
|
|
487
499
|
${a.parked ? html`<div class="text-meta-sm mb-half">${t('profile.apps.parkedHint') || 'Only you can see and use this β hidden from the public catalogue.'}</div>` : ''}
|
|
488
500
|
${a.manifest?.description ? html`<div class="text-meta mb-half">${escHtml(a.manifest.description)}</div>` : ''}
|
|
501
|
+
${(() => {
|
|
502
|
+
const pt = promoted[`${a.owner || session.owner}/${a.filename || a.name}`];
|
|
503
|
+
const txt = pt ? (pt[getLocale()] || pt.en || pt.fi || '') : '';
|
|
504
|
+
return txt ? html`<div class="text-meta-sm mb-half">\u{1F4E3} ${escHtml(txt)}</div>` : '';
|
|
505
|
+
})()}
|
|
489
506
|
<div class="card-subtitle">
|
|
490
507
|
<a href="${NODE_URL}/v1/apps/${encodeURIComponent(a.owner || session.owner)}/${encodeURIComponent(a.filename || a.name)}" target="_blank">${t('profile.apps.download')}</a>
|
|
491
508
|
${a.size ? ' \u2022 ' + Math.round(a.size / 1024) + ' KB' : ''}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file public/views/profile/inbox-tab/ai-actions.js
|
|
3
|
+
* @description Builds the config objects behind the Inbox thread-head AI actions, so InboxTab's handlers
|
|
4
|
+
* stay one-liners (and the file stays under max-file-lines). Two families:
|
|
5
|
+
* - Reply with AI (TARGET-031): buildConversationReplyProps / buildMessageReplyProps β { title, build }
|
|
6
|
+
* for ReplyWithAiPopover (build(mode) β copy/mcp prompt).
|
|
7
|
+
* - Conversation β Notebook: buildConversationNotebookProps β { title, promptText, runServerSummary,
|
|
8
|
+
* parkConversation } for ConversationToNotebookPopover. runServerSummary calls /v1/ai/complete on the
|
|
9
|
+
* owner's own OpenRouter key (vision-aware, up to 8 images); parkConversation files the whole thread.
|
|
10
|
+
* @structure
|
|
11
|
+
* - collectThreadImages(thread, urlMap) β [{messageId,id,name,url}] for every image attachment
|
|
12
|
+
* - buildConversationReplyProps({ activeConv, thread, peerName, peerDisplayName })
|
|
13
|
+
* - buildMessageReplyProps({ activeConv, msg, peerName })
|
|
14
|
+
* - buildConversationNotebookProps({ activeConv, thread, urlMap, peerName, title })
|
|
15
|
+
* @usage
|
|
16
|
+
* import { buildConversationNotebookProps } from './inbox-tab/ai-actions.js';
|
|
17
|
+
* setNbConv(buildConversationNotebookProps({ activeConv, thread, urlMap, peerName, title }));
|
|
18
|
+
* @version-history
|
|
19
|
+
* v1.0.0 β 2026-07-19 β Initial: extracted the Reply-with-AI + ConversationβNotebook assembly from inbox-tab.js.
|
|
20
|
+
*/
|
|
21
|
+
import { api } from '/js/api.js';
|
|
22
|
+
import { t } from '/js/i18n.js';
|
|
23
|
+
import { parkConversationToNotebook } from '/js/services/notebook.js';
|
|
24
|
+
import { buildConversationReplyPrompt, buildMessageReplyPrompt, buildConversationSummaryPrompt } from '/js/services/messages-ai-prompts.js';
|
|
25
|
+
import { attachKind } from './helpers.js';
|
|
26
|
+
|
|
27
|
+
/** Reply-with-AI props for the whole open conversation (ReplyWithAiPopover: build(mode) β prompt). */
|
|
28
|
+
export function buildConversationReplyProps({ activeConv, thread, peerName, peerDisplayName }) {
|
|
29
|
+
const src = { peerGhii: activeConv.peerGhii, subject: activeConv.subject, conversationId: activeConv.conversationId, thread, peerName };
|
|
30
|
+
return { title: `${t('inbox.ai.replyTo')} ${peerDisplayName}`, build: (mode) => buildConversationReplyPrompt(src, mode) };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Reply-with-AI props scoped to one message the user pointed at. */
|
|
34
|
+
export function buildMessageReplyProps({ activeConv, msg, peerName }) {
|
|
35
|
+
const src = { peerGhii: activeConv.peerGhii, subject: activeConv.subject, conversationId: activeConv.conversationId, message: msg, peerName };
|
|
36
|
+
return { title: t('inbox.ai.replyToMessage'), build: (mode) => buildMessageReplyPrompt(src, mode) };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Every image attachment across the loaded thread, resolved to a viewable url (urlMap is keyed
|
|
40
|
+
* `${messageId}::${attachmentId}`). Used both for the vision summary call and the notebook embeds. */
|
|
41
|
+
export function collectThreadImages(msgs, urls) {
|
|
42
|
+
const out = [];
|
|
43
|
+
for (const m of (msgs || [])) {
|
|
44
|
+
for (const a of (m.attachments || [])) {
|
|
45
|
+
if (attachKind(a) !== 'image' || a.expired) continue;
|
|
46
|
+
const url = urls[`${m.id}::${a.id}`];
|
|
47
|
+
if (url) out.push({ messageId: m.id, id: a.id, name: a.name || a.storageKey || 'image', url });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Build the ConversationToNotebookPopover config for the open thread: the summary prompt, plus the
|
|
55
|
+
* server-summary and park callbacks (the popover picks the mode and calls back).
|
|
56
|
+
* @param {object} args
|
|
57
|
+
* @param {{ conversationId?:string, peerGhii?:string, subject?:string }} args.activeConv
|
|
58
|
+
* @param {Array} args.thread
|
|
59
|
+
* @param {Record<string,string>} args.urlMap
|
|
60
|
+
* @param {string} [args.peerName]
|
|
61
|
+
* @param {string} args.title
|
|
62
|
+
*/
|
|
63
|
+
export function buildConversationNotebookProps({ activeConv, thread, urlMap, peerName, title }) {
|
|
64
|
+
const src = { peerGhii: activeConv.peerGhii, subject: activeConv.subject, thread, peerName };
|
|
65
|
+
const images = collectThreadImages(thread, urlMap);
|
|
66
|
+
const promptText = buildConversationSummaryPrompt(src);
|
|
67
|
+
// Server-side summary on the owner's own OpenRouter key. Text-only on purpose: the thread's image
|
|
68
|
+
// attachments are JWT-presigned `/v1/download/β¦` urls that OpenRouter's servers can't fetch (β 502),
|
|
69
|
+
// so we never send them to the model. The images are still captured into the notebook entry below
|
|
70
|
+
// (transcript embeds + source.attachments) for the owner to view; the durable value is the text.
|
|
71
|
+
const runServerSummary = async () => {
|
|
72
|
+
const resp = await api('/v1/ai/complete', {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
body: JSON.stringify({ prompt: promptText }),
|
|
75
|
+
timeoutMs: 1_800_000,
|
|
76
|
+
retries: 0,
|
|
77
|
+
});
|
|
78
|
+
if (resp?.ok === false) throw new Error(resp.error?.message || t('inbox.failed'));
|
|
79
|
+
return resp?.data?.content || '';
|
|
80
|
+
};
|
|
81
|
+
const parkConversation = ({ summary }) => parkConversationToNotebook({
|
|
82
|
+
conv: { conversationId: activeConv.conversationId, peerGhii: activeConv.peerGhii, subject: activeConv.subject },
|
|
83
|
+
thread, peerName, summary, images,
|
|
84
|
+
});
|
|
85
|
+
return { title, promptText, runServerSummary, parkConversation };
|
|
86
|
+
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* chat.commands), SchedulePanel (own-agent scheduler), and ReplyWithAiPopover (TARGET-031). Each is
|
|
7
7
|
* self-contained (owns its own hooks). Extracted from inbox-tab.js to satisfy max-file-lines.
|
|
8
8
|
* @version-history
|
|
9
|
+
* v1.8.0 β 2026-07-21 β MessageBubble renders link-preview cards under the body (MessageLinkPreviews),
|
|
10
|
+
* gated by the `showLinkPreviews` prop (the persisted thread-head toggle).
|
|
9
11
|
* v1.0.0 β 2026-07-13 β Extracted from inbox-tab.js (max-file-lines)
|
|
10
12
|
* v1.1.0 β 2026-07-14 β Composer: pasted/dropped images route to the file-attachment path (upload +
|
|
11
13
|
* shown as an image) instead of Toast UI base64-inlining them into the body (which blew the 50k
|
|
@@ -22,6 +24,11 @@
|
|
|
22
24
|
* v1.5.0 β 2026-07-18 β Mobile composer is a plain auto-growing textarea (`mode:'simple'`, no Toast UI
|
|
23
25
|
* toolbar/Write-Preview/WYSIWYG β a phone keyboard + heavy WYSIWYG is miserable); β€760px opens straight
|
|
24
26
|
* into it so Toast UI never even loads there. Desktop keeps the rich editor.
|
|
27
|
+
* v1.6.0 β 2026-07-19 β Composer gets an expand toggle (β€’/‑): enlarges the editor to ~60% of the
|
|
28
|
+
* viewport so long/formatted drafts are fully visible. Rich mode resizes via Toast UI `setHeight`;
|
|
29
|
+
* the markdown fallback + simple textarea grow via the `.inbox-composer--tall` class / lifted cap.
|
|
30
|
+
* v1.7.0 β 2026-07-19 β ConversationToNotebookPopover: capture a whole thread (with images) into the
|
|
31
|
+
* Notebook via three modes β server-side AI summary (owner's key), copy-prompt (own chat), or raw.
|
|
25
32
|
*/
|
|
26
33
|
import { h } from 'preact';
|
|
27
34
|
import { useState, useEffect, useCallback, useRef } from 'preact/hooks';
|
|
@@ -30,6 +37,7 @@ const html = htm.bind(h);
|
|
|
30
37
|
import { t } from '/js/i18n.js';
|
|
31
38
|
import { escHtml } from '/js/utils.js';
|
|
32
39
|
import { Markdown } from '/components/Markdown.js';
|
|
40
|
+
import { MessageLinkPreviews } from '/components/LinkPreview.js';
|
|
33
41
|
import { minidenticon } from '/lib/minidenticons.min.js';
|
|
34
42
|
import * as schedules from '/js/services/schedules.js';
|
|
35
43
|
import { MODES } from '/js/services/messages-ai-prompts.js';
|
|
@@ -243,7 +251,7 @@ export function PollBuilder({ questions, setQuestions }) {
|
|
|
243
251
|
</div>`;
|
|
244
252
|
}
|
|
245
253
|
|
|
246
|
-
export function MessageBubble({ msg, mine, urlMap, starred, onStar, onTrack, onPark, onReplyAi, onQuote, quoted, quotedName, onJumpTo, domId, tracked, onOpenMarkdown, answeredWith, onAnswer, submitting }) {
|
|
254
|
+
export function MessageBubble({ msg, mine, urlMap, starred, onStar, onTrack, onPark, onReplyAi, onQuote, quoted, quotedName, onJumpTo, domId, tracked, onOpenMarkdown, answeredWith, onAnswer, submitting, showLinkPreviews }) {
|
|
247
255
|
const nonInline = (msg.attachments || []).filter(a => !a.inline);
|
|
248
256
|
const expiredIds = new Set((msg.attachments || []).filter(a => a.expired).map(a => a.id));
|
|
249
257
|
// urlMap is keyed by `${messageId}::${attachmentId}` because per-message attachment ids (at0, at1β¦)
|
|
@@ -277,6 +285,7 @@ export function MessageBubble({ msg, mine, urlMap, starred, onStar, onTrack, onP
|
|
|
277
285
|
<span class="inbox-quote-text">${escHtml(quoteSnippet(quoted.body))}</span>
|
|
278
286
|
</button>` : null}
|
|
279
287
|
<div class="inbox-bubble-body"><${Markdown} text=${prepareBody(msg.body, urls, expiredIds)} /></div>
|
|
288
|
+
${showLinkPreviews ? html`<${MessageLinkPreviews} msg=${msg} />` : null}
|
|
280
289
|
${msg.interactive?.role === 'questions' ? (
|
|
281
290
|
answeredWith
|
|
282
291
|
? html`<${InteractiveAnswered} spec=${msg.interactive} answers=${answeredWith.answers || {}} />`
|
|
@@ -310,6 +319,8 @@ export function Composer({ recipient, sendLabel, sending, onSend, initialText =
|
|
|
310
319
|
const isNarrow = typeof window !== 'undefined' && window.matchMedia?.('(max-width: 760px)').matches;
|
|
311
320
|
const [mode, setMode] = useState(isNarrow ? 'simple' : 'rich');
|
|
312
321
|
const [md, setMd] = useState(seeded);
|
|
322
|
+
// Temporarily enlarge the editor (~60% of the viewport) so long/formatted drafts are fully visible.
|
|
323
|
+
const [expanded, setExpanded] = useState(false);
|
|
313
324
|
const [files, setFiles] = useState([]);
|
|
314
325
|
const containerRef = useRef(null);
|
|
315
326
|
const editorRef = useRef(null);
|
|
@@ -419,10 +430,26 @@ export function Composer({ recipient, sendLabel, sending, onSend, initialText =
|
|
|
419
430
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
420
431
|
}, [focusNonce]);
|
|
421
432
|
|
|
422
|
-
//
|
|
423
|
-
|
|
424
|
-
//
|
|
425
|
-
useEffect(() => {
|
|
433
|
+
// Resize the Toast UI editor when the expand toggle flips (rich mode sets its own inline height via
|
|
434
|
+
// JS, so a CSS class can't reach it β the fallback/simple textareas are sized by `.inbox-composer--tall`
|
|
435
|
+
// in CSS instead). `160px` matches the construction default.
|
|
436
|
+
useEffect(() => {
|
|
437
|
+
if (mode !== 'rich' || !editorRef.current?.setHeight) return;
|
|
438
|
+
const tall = typeof window !== 'undefined' ? Math.round(window.innerHeight * 0.6) : 400;
|
|
439
|
+
try { editorRef.current.setHeight(expanded ? tall + 'px' : '160px'); } catch { /* noop */ }
|
|
440
|
+
}, [expanded, mode]);
|
|
441
|
+
|
|
442
|
+
// Auto-grow the simple (mobile) textarea to fit its content, capped so it never eats the thread. When
|
|
443
|
+
// expanded, the cap lifts to ~60vh so a long draft is fully visible.
|
|
444
|
+
const autoGrow = (ta) => {
|
|
445
|
+
if (!ta) return;
|
|
446
|
+
const cap = expanded && typeof window !== 'undefined' ? Math.round(window.innerHeight * 0.6) : 132;
|
|
447
|
+
ta.style.height = 'auto'; ta.style.height = Math.min(ta.scrollHeight, cap) + 'px';
|
|
448
|
+
};
|
|
449
|
+
// Size the simple textarea to any seeded draft on mount (and keep it 1 row when empty); re-fit when the
|
|
450
|
+
// expand cap changes.
|
|
451
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
452
|
+
useEffect(() => { if (mode === 'simple') autoGrow(taRef.current); }, [mode, expanded]);
|
|
426
453
|
|
|
427
454
|
const getText = () => (mode === 'rich' && editorRef.current) ? editorRef.current.getMarkdown() : md;
|
|
428
455
|
const reset = () => {
|
|
@@ -441,7 +468,7 @@ export function Composer({ recipient, sendLabel, sending, onSend, initialText =
|
|
|
441
468
|
});
|
|
442
469
|
|
|
443
470
|
return html`
|
|
444
|
-
<div class="inbox-composer">
|
|
471
|
+
<div class="inbox-composer ${expanded ? 'inbox-composer--tall' : ''}">
|
|
445
472
|
${files.length > 0 ? html`<div class="inbox-file-chips">
|
|
446
473
|
${files.map((f, i) => html`<span class="inbox-file-chip" key=${f.name + i}>π ${escHtml(f.name)}
|
|
447
474
|
<button class="inbox-bc-chip-x" title=${t('inbox.attachmentRemove')} onClick=${() => removeFile(i)}>β</button></span>`)}
|
|
@@ -459,9 +486,13 @@ export function Composer({ recipient, sendLabel, sending, onSend, initialText =
|
|
|
459
486
|
<div class="inbox-md-preview"><${Markdown} text=${md} /></div>
|
|
460
487
|
</div>`}
|
|
461
488
|
<div class="inbox-composer-bar">
|
|
462
|
-
<
|
|
463
|
-
|
|
464
|
-
|
|
489
|
+
<div class="inbox-bar-left">
|
|
490
|
+
<label class="inbox-attach-btn" title=${t('inbox.attach')}>
|
|
491
|
+
π<input ref=${fileRef} type="file" multiple hidden onChange=${(e) => setFiles(Array.from(e.target.files || []))} />
|
|
492
|
+
</label>
|
|
493
|
+
<button type="button" class="inbox-attach-btn" title=${expanded ? t('inbox.collapse') : t('inbox.expand')}
|
|
494
|
+
aria-pressed=${expanded} onClick=${() => setExpanded((v) => !v)}>${expanded ? '‑' : '‒'}</button>
|
|
495
|
+
</div>
|
|
465
496
|
<button class="btn-primary btn-sm" disabled=${sending || !recipient} onClick=${submit}>
|
|
466
497
|
${sending ? t('inbox.sending') : sendLabel}
|
|
467
498
|
</button>
|
|
@@ -590,3 +621,87 @@ export function ReplyWithAiPopover({ title, build, onClose, showToast }) {
|
|
|
590
621
|
</div>
|
|
591
622
|
</div>`;
|
|
592
623
|
}
|
|
624
|
+
|
|
625
|
+
/* ββ Conversation β Notebook β capture a WHOLE thread (with its images) into the notebook for later
|
|
626
|
+
* filing/enrichment into a workspace. Three modes, all landing in parkConversationToNotebook:
|
|
627
|
+
* β¨ ai β summarize server-side with the owner's own OpenRouter key (runServerSummary), edit, park.
|
|
628
|
+
* π copy β copy the summary prompt into the owner's own AI chat, paste the result back, park.
|
|
629
|
+
* π₯ raw β park the whole chain (text + images) as-is; enrich it later in the Notebook.
|
|
630
|
+
* The parent (InboxTab) owns the async work (AI call + park + toasts) via the passed callbacks. ββ */
|
|
631
|
+
export function ConversationToNotebookPopover({ title, promptText, runServerSummary, parkConversation, onClose, showToast }) {
|
|
632
|
+
const [mode, setMode] = useState('ai'); // 'ai' | 'copy' | 'raw'
|
|
633
|
+
const [copied, setCopied] = useState(false);
|
|
634
|
+
const [aiSummary, setAiSummary] = useState('');
|
|
635
|
+
const [pasted, setPasted] = useState('');
|
|
636
|
+
const [running, setRunning] = useState(false);
|
|
637
|
+
const [parking, setParking] = useState(false);
|
|
638
|
+
|
|
639
|
+
const copy = async () => {
|
|
640
|
+
try {
|
|
641
|
+
if (navigator.clipboard?.writeText) await navigator.clipboard.writeText(promptText);
|
|
642
|
+
else { const ta = document.createElement('textarea'); ta.value = promptText; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); ta.remove(); }
|
|
643
|
+
setCopied(true); setTimeout(() => setCopied(false), 1800);
|
|
644
|
+
showToast?.(t('inbox.ai.copied'));
|
|
645
|
+
} catch { showToast?.(t('inbox.failed'), true); }
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
const genSummary = async () => {
|
|
649
|
+
setRunning(true);
|
|
650
|
+
try {
|
|
651
|
+
const s = await runServerSummary();
|
|
652
|
+
if (s && s.trim()) setAiSummary(s.trim());
|
|
653
|
+
else showToast?.(t('inbox.notebook.summaryEmpty'), true);
|
|
654
|
+
} catch (e) { showToast?.(e?.message || t('inbox.failed'), true); }
|
|
655
|
+
finally { setRunning(false); }
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
const doPark = async (summary) => {
|
|
659
|
+
setParking(true);
|
|
660
|
+
try { await parkConversation({ summary: summary || '' }); showToast?.(t('inbox.notebook.parked')); onClose(); }
|
|
661
|
+
catch (e) { showToast?.(e?.message || t('inbox.failed'), true); setParking(false); }
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
return html`
|
|
665
|
+
<div class="inbox-ai-overlay" onClick=${onClose}>
|
|
666
|
+
<div class="inbox-ai-modal" onClick=${(e) => e.stopPropagation()}>
|
|
667
|
+
<div class="inbox-ai-head">
|
|
668
|
+
<span class="inbox-ai-title">π ${title}</span>
|
|
669
|
+
<button class="btn-ghost btn-sm" onClick=${onClose} title=${t('inbox.close')}>β</button>
|
|
670
|
+
</div>
|
|
671
|
+
<div class="inbox-ai-modes">
|
|
672
|
+
<button class=${`inbox-ai-mode${mode === 'ai' ? ' inbox-ai-mode--on' : ''}`} onClick=${() => setMode('ai')}>β¨ ${t('inbox.notebook.modeAi')}</button>
|
|
673
|
+
<button class=${`inbox-ai-mode${mode === 'copy' ? ' inbox-ai-mode--on' : ''}`} onClick=${() => setMode('copy')}>π ${t('inbox.notebook.modeCopy')}</button>
|
|
674
|
+
<button class=${`inbox-ai-mode${mode === 'raw' ? ' inbox-ai-mode--on' : ''}`} onClick=${() => setMode('raw')}>π₯ ${t('inbox.notebook.modeRaw')}</button>
|
|
675
|
+
</div>
|
|
676
|
+
${mode === 'ai' ? html`
|
|
677
|
+
<div class="inbox-ai-hint">${t('inbox.notebook.hintAi')}</div>
|
|
678
|
+
${!aiSummary ? html`
|
|
679
|
+
<div class="inbox-ai-actions">
|
|
680
|
+
<button class="btn-primary btn-sm" disabled=${running} onClick=${genSummary}>${running ? 'β¦ ' + t('inbox.notebook.summarizing') : 'β¨ ' + t('inbox.notebook.genSummary')}</button>
|
|
681
|
+
</div>`
|
|
682
|
+
: html`
|
|
683
|
+
<textarea class="inbox-ai-text" rows="12" value=${aiSummary} onInput=${(e) => setAiSummary(e.target.value)}></textarea>
|
|
684
|
+
<div class="inbox-ai-actions">
|
|
685
|
+
<button class="btn-ghost btn-sm" disabled=${running} onClick=${genSummary}>${running ? 'β¦' : 'β» ' + t('inbox.notebook.regen')}</button>
|
|
686
|
+
<button class="btn-primary btn-sm" disabled=${parking} onClick=${() => doPark(aiSummary)}>${parking ? 'β¦' : 'π ' + t('inbox.notebook.park')}</button>
|
|
687
|
+
</div>`}
|
|
688
|
+
` : mode === 'copy' ? html`
|
|
689
|
+
<div class="inbox-ai-hint">${t('inbox.notebook.hintCopy')}</div>
|
|
690
|
+
<textarea class="inbox-ai-text" readOnly rows="8" value=${promptText}></textarea>
|
|
691
|
+
<div class="inbox-ai-actions">
|
|
692
|
+
<button class="btn-primary btn-sm" onClick=${copy}>${copied ? 'β ' + t('inbox.ai.copied') : 'π ' + t('inbox.ai.copy')}</button>
|
|
693
|
+
</div>
|
|
694
|
+
<div class="inbox-ai-hint">${t('inbox.notebook.pasteHint')}</div>
|
|
695
|
+
<textarea class="inbox-ai-text" rows="8" placeholder=${t('inbox.notebook.pastePh')} value=${pasted} onInput=${(e) => setPasted(e.target.value)}></textarea>
|
|
696
|
+
<div class="inbox-ai-actions">
|
|
697
|
+
<button class="btn-primary btn-sm" disabled=${parking || !pasted.trim()} onClick=${() => doPark(pasted)}>${parking ? 'β¦' : 'π ' + t('inbox.notebook.park')}</button>
|
|
698
|
+
</div>
|
|
699
|
+
` : html`
|
|
700
|
+
<div class="inbox-ai-hint">${t('inbox.notebook.hintRaw')}</div>
|
|
701
|
+
<div class="inbox-ai-actions">
|
|
702
|
+
<button class="btn-primary btn-sm" disabled=${parking} onClick=${() => doPark('')}>${parking ? 'β¦' : 'π₯ ' + t('inbox.notebook.parkRaw')}</button>
|
|
703
|
+
</div>
|
|
704
|
+
`}
|
|
705
|
+
</div>
|
|
706
|
+
</div>`;
|
|
707
|
+
}
|
|
@@ -5,11 +5,37 @@
|
|
|
5
5
|
* pixel defense), tracked-state labels, attachment classification, the interactive-answer summary +
|
|
6
6
|
* poll tally, and the lazy Toast UI editor loader. Extracted from inbox-tab.js to satisfy max-file-lines.
|
|
7
7
|
* @version-history
|
|
8
|
+
* v1.2.0 β 2026-07-21 β resolveThreadAttachmentUrls(): resolve non-inline attachment URLs for a loaded
|
|
9
|
+
* thread (reusing the previous conversation's cache), extracted from inbox-tab loadThread.
|
|
8
10
|
* v1.1.0 β 2026-07-17 β quoteSnippet(): one-line plain-text excerpt of a message body for reply-quotes.
|
|
9
11
|
* v1.0.0 β 2026-07-13 β Extracted from inbox-tab.js (max-file-lines)
|
|
10
12
|
*/
|
|
11
13
|
import { t, getLocale } from '/js/i18n.js';
|
|
12
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Resolve presigned URLs for a thread's non-inline attachments, reusing already-resolved URLs from the
|
|
17
|
+
* SAME conversation's previous cache (a refresh/new message must not re-download every existing image).
|
|
18
|
+
* Keyed by `${messageId}::${attachmentId}` (per-message ids repeat across messages). Inbound resolves the
|
|
19
|
+
* recipient's duplicated local copy; outbound resolves the original. `resolveUrl` = messages.attachmentUrl.
|
|
20
|
+
* Returns { convId, map } to store as the new cache.
|
|
21
|
+
*/
|
|
22
|
+
export async function resolveThreadAttachmentUrls(msgs, conversationId, prevCache, resolveUrl) {
|
|
23
|
+
const prev = prevCache?.convId === conversationId ? (prevCache.map || {}) : {};
|
|
24
|
+
const map = {};
|
|
25
|
+
await Promise.all(msgs.flatMap(m => (m.attachments || [])
|
|
26
|
+
.filter(a => !a.inline)
|
|
27
|
+
.map(async a => {
|
|
28
|
+
const uk = `${m.id}::${a.id}`;
|
|
29
|
+
if (prev[uk]) { map[uk] = prev[uk]; return; }
|
|
30
|
+
const key = (a.mode === 'duplicate' && a.localKey) ? a.localKey
|
|
31
|
+
: (m.direction === 'outbound' && a.storageKey) ? a.storageKey : null;
|
|
32
|
+
if (!key) return;
|
|
33
|
+
const u = await resolveUrl(key).catch(() => null);
|
|
34
|
+
if (u) map[uk] = u;
|
|
35
|
+
})));
|
|
36
|
+
return { convId: conversationId, map };
|
|
37
|
+
}
|
|
38
|
+
|
|
13
39
|
/* Lazy-load the vendored Toast UI Editor (MIT, /lib/toastui/) β the same editor the workspace
|
|
14
40
|
* document space uses, so composing a message feels like editing a document (MarkdownβWYSIWYG).
|
|
15
41
|
* ~520KB, so it stays out of the main bundle and loads only when the Inbox composer mounts. */
|
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
* (broadcast/poll results). Each is a presentational component driven entirely by props from InboxTab;
|
|
7
7
|
* the stateful container keeps all hooks. Extracted from inbox-tab.js to satisfy max-file-lines.
|
|
8
8
|
* @version-history
|
|
9
|
+
* v1.4.0 β 2026-07-21 β ThreadPanel head: "Show all messages / Last 50" toggle (threadAll/
|
|
10
|
+
* toggleThreadAll), shown once a thread has β₯50 messages. Threads default to the full history;
|
|
11
|
+
* the toggle collapses to the newest 50.
|
|
12
|
+
* v1.3.0 β 2026-07-21 β ThreadPanel: link-preview toggle button in the head (showLinkPreviews /
|
|
13
|
+
* toggleLinkPreviews) + passes the flag down to each MessageBubble.
|
|
9
14
|
* v1.2.0 β 2026-07-18 β Clicking β© Reply on a bubble now focuses the composer (via `onQuoteReply` +
|
|
10
15
|
* `composerFocus` bump) so the cursor lands in the input; the β cancel still uses the raw setter.
|
|
11
16
|
* v1.1.0 β 2026-07-17 β Reply-to with quote: ThreadPanel resolves each message's `replyToId` to the
|
|
@@ -99,8 +104,9 @@ export function ThreadPanel({
|
|
|
99
104
|
activeConv, thread, urlMap, important, trackedByMsg, awaitingForConv, awaitingDrafts,
|
|
100
105
|
schedOpen, setSchedOpen, cmdFill, agentCommands, sending, draftPrefill, prefillNonce, msgsRef,
|
|
101
106
|
peerDisplay, showToast, toggleImportant, onTrackMsg, onParkMsg, openMessageAi, submitInteractiveAnswers,
|
|
102
|
-
setMdViewer, openConversationAi, insertCommand, setCmdFill, cancelTracked, openRecord, startSuggestedReply, doSend,
|
|
103
|
-
replyQuote, setReplyQuote, onQuoteReply, composerFocus,
|
|
107
|
+
setMdViewer, openConversationAi, openConversationNotebook, insertCommand, setCmdFill, cancelTracked, openRecord, startSuggestedReply, doSend,
|
|
108
|
+
replyQuote, setReplyQuote, onQuoteReply, composerFocus, showLinkPreviews, toggleLinkPreviews,
|
|
109
|
+
threadAll, toggleThreadAll,
|
|
104
110
|
}) {
|
|
105
111
|
let lastDay = '';
|
|
106
112
|
// Reply-to quotes: resolve a message's `replyToId` to the original within the loaded page (a parent
|
|
@@ -142,6 +148,15 @@ export function ThreadPanel({
|
|
|
142
148
|
<div class="inbox-sub">${escHtml(activeConv.peerGhii)}</div>
|
|
143
149
|
</div>
|
|
144
150
|
${!viaAgentName ? html`<button class="btn-ghost btn-sm inbox-ai-btn" onClick=${openConversationAi} title=${t('inbox.ai.replyWithAi')}>β¨ <span class="inbox-ai-btn-label">${t('inbox.ai.replyWithAi')}</span></button>` : null}
|
|
151
|
+
${!viaAgentName ? html`<button class="btn-ghost btn-sm inbox-ai-btn" onClick=${openConversationNotebook} title=${t('inbox.notebook.toNotebook')}>π <span class="inbox-ai-btn-label">${t('inbox.notebook.toNotebookShort')}</span></button>` : null}
|
|
152
|
+
<button class=${`btn-ghost btn-sm inbox-linkprev-toggle${showLinkPreviews ? ' inbox-linkprev-toggle--on' : ''}`}
|
|
153
|
+
aria-pressed=${!!showLinkPreviews} onClick=${toggleLinkPreviews}
|
|
154
|
+
title=${showLinkPreviews ? t('inbox.linkPreview.hideAll') : t('inbox.linkPreview.showAll')}>
|
|
155
|
+
<span class="inbox-ai-btn-label">${t('inbox.linkPreview.label')}</span></button>
|
|
156
|
+
${(thread.length >= 50) ? html`<button class=${`btn-ghost btn-sm inbox-linkprev-toggle${threadAll ? ' inbox-linkprev-toggle--on' : ''}`}
|
|
157
|
+
aria-pressed=${!!threadAll} onClick=${toggleThreadAll}
|
|
158
|
+
title=${threadAll ? t('inbox.thread.showRecent') : t('inbox.thread.showAll')}>
|
|
159
|
+
<span class="inbox-ai-btn-label">${threadAll ? t('inbox.thread.showRecent') : t('inbox.thread.showAll')}</span></button>` : null}
|
|
145
160
|
${peerIsMyAgent && !viaAgentName ? html`<button class=${`btn-ghost btn-sm inbox-sched-btn${schedOpen ? ' inbox-sched-btn--on' : ''}`}
|
|
146
161
|
onClick=${() => setSchedOpen(o => !o)} title=${t('inbox.schedTitle')}>π
</button>` : null}
|
|
147
162
|
</div>
|
|
@@ -159,7 +174,7 @@ export function ThreadPanel({
|
|
|
159
174
|
onQuote=${(onQuoteReply && !activeConv.viaAgent) ? onQuoteReply : null}
|
|
160
175
|
starred=${important.has(m.id)} onStar=${toggleImportant} onTrack=${onTrackMsg} onPark=${onParkMsg} onReplyAi=${openMessageAi} tracked=${trackedByMsg[m.id]}
|
|
161
176
|
answeredWith=${m.interactive?.role === 'questions' ? answersByQ[m.id] : null}
|
|
162
|
-
onAnswer=${submitInteractiveAnswers} submitting=${sending}
|
|
177
|
+
onAnswer=${submitInteractiveAnswers} submitting=${sending} showLinkPreviews=${showLinkPreviews}
|
|
163
178
|
onOpenMarkdown=${(url, name) => setMdViewer({ url, name })} />`;
|
|
164
179
|
})}
|
|
165
180
|
</div>
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* Extracted from inbox-tab.js to satisfy max-file-lines.
|
|
8
8
|
* @usage import { useThreadAutoScroll, useMobileComposerKeyboard } from './inbox-tab/use-thread-ux.js';
|
|
9
9
|
* @version-history
|
|
10
|
+
* v1.3.0 β 2026-07-21 β Add useLinkPreviewToggle: persisted global on/off for the message link-preview
|
|
11
|
+
* cards (default ON); the ThreadPanel head button flips it.
|
|
10
12
|
* v1.2.0 β 2026-07-19 β useThreadAutoScroll now suppresses the one-time new-message jump while the
|
|
11
13
|
* composer is focused (near-bottom follow still applies). This lets the inbox reload the open thread
|
|
12
14
|
* on EVERY 'messages' live-update β so incoming messages render immediately even while you're typing
|
|
@@ -17,7 +19,22 @@
|
|
|
17
19
|
* visualViewport bottom) as `--inbox-avail` instead of the `dvh β keyboard` double-count that collapsed
|
|
18
20
|
* the messenger on Android Chrome; drop the composer scrollIntoView(center) that left a dead gap.
|
|
19
21
|
*/
|
|
20
|
-
import { useEffect, useRef } from 'preact/hooks';
|
|
22
|
+
import { useEffect, useRef, useState } from 'preact/hooks';
|
|
23
|
+
|
|
24
|
+
const LINK_PREVIEWS_KEY = 'aimeat.inbox.linkPreviews';
|
|
25
|
+
|
|
26
|
+
/** Persisted global toggle for the link-preview cards (default ON). Returns the flag + a toggler that
|
|
27
|
+
* writes the choice to localStorage. Per-card dismissal is owned separately by /components/LinkPreview.js. */
|
|
28
|
+
export function useLinkPreviewToggle() {
|
|
29
|
+
const [showLinkPreviews, setShow] = useState(() => {
|
|
30
|
+
try { return localStorage.getItem(LINK_PREVIEWS_KEY) !== 'off'; } catch { return true; }
|
|
31
|
+
});
|
|
32
|
+
const toggleLinkPreviews = () => setShow((v) => {
|
|
33
|
+
try { localStorage.setItem(LINK_PREVIEWS_KEY, v ? 'off' : 'on'); } catch { /* quota */ }
|
|
34
|
+
return !v;
|
|
35
|
+
});
|
|
36
|
+
return { showLinkPreviews, toggleLinkPreviews };
|
|
37
|
+
}
|
|
21
38
|
|
|
22
39
|
/** Auto-scroll policy for the open thread: jump to the latest message when a thread is OPENED, follow
|
|
23
40
|
* when the reader is already near the bottom, and when a genuinely NEW message arrives (last message
|