@geminilight/mindos 0.6.61 → 0.6.65
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/_standalone/.antigravity/mcp_config.json +14 -0
- package/_standalone/.mindos-build-version +1 -1
- package/_standalone/.next/BUILD_ID +1 -1
- package/_standalone/.next/app-path-routes-manifest.json +23 -23
- package/_standalone/.next/build-manifest.json +2 -2
- package/_standalone/.next/cache/.previewinfo +1 -1
- package/_standalone/.next/cache/.rscinfo +1 -1
- package/_standalone/.next/cache/config.json +3 -3
- package/_standalone/.next/prerender-manifest.json +3 -3
- package/_standalone/.next/server/app/.well-known/agent-card.json/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/_global-error.html +2 -2
- package/_standalone/.next/server/app/_global-error.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/_standalone/.next/server/app/_not-found/page.js +1 -1
- package/_standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page.js +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/agents/page.js +1 -1
- package/_standalone/.next/server/app/agents/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/agents/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/agents/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/delegations/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/discover/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/config/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/detect/route.js +1 -1
- package/_standalone/.next/server/app/api/acp/detect/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/install/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/registry/route.js +1 -1
- package/_standalone/.next/server/app/api/acp/registry/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/session/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agent-activity/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agents/copy-skill/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/agents/copy-skill/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agents/custom/detect/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agents/custom/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/ask/route.js +53 -47
- package/_standalone/.next/server/app/api/ask/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/ask/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/ask-sessions/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/auth/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/backlinks/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/backlinks/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/bootstrap/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/bootstrap/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/changes/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/changes/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/export/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/export/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/extract-pdf/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/import/route.js +1 -1
- package/_standalone/.next/server/app/api/file/import/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/file/import/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/raw/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/file/raw/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/graph/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/graph/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/inbox/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/inbox/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/init/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/agents/route.js +1 -1
- package/_standalone/.next/server/app/api/mcp/agents/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/mcp/agents/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/install/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/install-skill/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/restart/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/status/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/uninstall/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/monitoring/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/monitoring/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/recent-files/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/recent-files/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/search/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/list-models/route.js +1 -1
- package/_standalone/.next/server/app/api/settings/list-models/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/reset-token/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/route.js +1 -1
- package/_standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/test-key/route.js +1 -1
- package/_standalone/.next/server/app/api/settings/test-key/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/check-path/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/check-port/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/generate-token/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/ls/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/route.js +1 -1
- package/_standalone/.next/server/app/api/setup/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/skills/route.js +1 -1
- package/_standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/tree-version/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/tree-version/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/uninstall/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update-check/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update-status/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/workflows/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/workflows/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/changes/page.js +1 -1
- package/_standalone/.next/server/app/changes/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/changes/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/echo/[segment]/page.js +2 -2
- package/_standalone/.next/server/app/echo/[segment]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/echo/[segment]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/echo/page.js +1 -1
- package/_standalone/.next/server/app/echo/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/echo/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/explore/page.js +1 -1
- package/_standalone/.next/server/app/explore/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/explore/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/help/page.js +1 -1
- package/_standalone/.next/server/app/help/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/help/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/inbox/history/page.js +1 -1
- package/_standalone/.next/server/app/inbox/history/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/inbox/history/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/login/page.js +1 -1
- package/_standalone/.next/server/app/login/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/page.js +1 -1
- package/_standalone/.next/server/app/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/setup/page.js +2 -2
- package/_standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/trash/page.js +3 -3
- package/_standalone/.next/server/app/trash/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/trash/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/view/[...path]/page.js +2 -2
- package/_standalone/.next/server/app/view/[...path]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/view/[...path]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/wiki/page.js +1 -1
- package/_standalone/.next/server/app/wiki/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/wiki/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app-paths-manifest.json +23 -23
- package/_standalone/.next/server/chunks/122.js +222 -0
- package/_standalone/.next/server/chunks/1550.js +1 -1
- package/_standalone/.next/server/chunks/1750.js +1 -1
- package/_standalone/.next/server/chunks/3113.js +52 -0
- package/_standalone/.next/server/chunks/6539.js +1 -1
- package/_standalone/.next/server/chunks/8388.js +3 -3
- package/_standalone/.next/server/chunks/953.js +3 -3
- package/_standalone/.next/server/pages/500.html +2 -2
- package/_standalone/.next/server/server-reference-manifest.js +1 -1
- package/_standalone/.next/server/server-reference-manifest.json +1 -1
- package/_standalone/.next/static/chunks/1001-99da82ec8d8c136f.js +1 -0
- package/_standalone/.next/static/chunks/1088-77544af0a50cb7a4.js +1 -0
- package/_standalone/.next/static/chunks/1467-87dde7eed498806f.js +1 -0
- package/_standalone/.next/static/chunks/5149-4d828886dda479fa.js +1 -0
- package/_standalone/.next/static/chunks/5581-c671163a2fe1b312.js +29 -0
- package/_standalone/.next/static/chunks/{7266-bb7be1128eccd48e.js → 5718-3837c3210a0e175f.js} +2 -2
- package/_standalone/.next/static/chunks/6636-53238eff89503f03.js +6 -0
- package/_standalone/.next/static/chunks/6757-1c1a89720fdda8f0.js +1 -0
- package/_standalone/.next/static/chunks/7129-20e9d2463a9da646.js +1 -0
- package/_standalone/.next/static/chunks/7294-cac25d97869afadc.js +1 -0
- package/_standalone/.next/static/chunks/8225-21e5cebc3731ddf0.js +1 -0
- package/_standalone/.next/static/chunks/8520-b51810e66293ceb8.js +22 -0
- package/_standalone/.next/static/chunks/9207-dc9c31b351a2ed78.js +1 -0
- package/_standalone/.next/static/chunks/app/agents/[agentKey]/page-2f5cf97e03dc1cc9.js +1 -0
- package/_standalone/.next/static/chunks/app/agents/page-50eac58d511dcc6e.js +1 -0
- package/_standalone/.next/static/chunks/app/echo/[segment]/page-2a00f4686adf3885.js +11 -0
- package/_standalone/.next/static/chunks/app/layout-2cb7a6602d2e5d5f.js +168 -0
- package/_standalone/.next/static/chunks/app/{page-6a1f8d21c12b829e.js → page-5ab911b2226f6ff7.js} +1 -1
- package/_standalone/.next/static/chunks/app/setup/page-907b7c57fad2292b.js +1 -0
- package/_standalone/.next/static/chunks/app/trash/page-11a511b065ea84c2.js +1 -0
- package/_standalone/.next/static/chunks/app/view/[...path]/page-26e47dd4c533a58c.js +12 -0
- package/_standalone/.next/static/chunks/app/wiki/page-dce495b9048022fb.js +1 -0
- package/_standalone/.next/static/css/67e7918f5ed7d147.css +1 -0
- package/_standalone/.next/trace +65 -65
- package/_standalone/__tests__/acp/registry.test.ts +30 -20
- package/_standalone/__tests__/api/ask-attachments.test.ts +194 -0
- package/_standalone/__tests__/api/mcp-install.test.ts +49 -2
- package/_standalone/__tests__/api/settings.test.ts +16 -12
- package/_standalone/__tests__/api/setup.test.ts +11 -9
- package/_standalone/__tests__/api/test-key.test.ts +0 -10
- package/_standalone/__tests__/components/UpdateToast.test.ts +344 -0
- package/_standalone/__tests__/core/context.test.ts +48 -426
- package/_standalone/__tests__/lib/pi-skills.test.ts +4 -4
- package/_standalone/__tests__/lib/settings-ai-client.test.ts +32 -12
- package/_standalone/__tests__/setup.ts +5 -5
- package/_standalone/app/globals.css +4 -4
- package/_standalone/components/ActivityBar.tsx +17 -6
- package/_standalone/components/Panel.tsx +24 -6
- package/_standalone/components/SidebarLayout.tsx +36 -8
- package/_standalone/components/agents/AgentsMcpSection.tsx +2 -2
- package/_standalone/components/agents/AgentsOverviewSection.tsx +5 -1
- package/_standalone/components/agents/AgentsPanelA2aTab.tsx +173 -113
- package/_standalone/components/agents/AgentsSkillsSection.tsx +2 -2
- package/_standalone/components/ask/AskContent.tsx +83 -44
- package/_standalone/components/ask/AskHeader.tsx +8 -1
- package/_standalone/components/ask/MessageList.tsx +37 -3
- package/_standalone/components/ask/ProviderModelCapsule.tsx +444 -174
- package/_standalone/components/home/InboxSection.tsx +25 -25
- package/_standalone/components/settings/AiTab.tsx +353 -298
- package/_standalone/components/settings/CustomProviderFields.tsx +121 -0
- package/_standalone/components/settings/CustomProvidersCard.tsx +154 -0
- package/_standalone/components/settings/KnowledgeTab.tsx +6 -20
- package/_standalone/components/settings/McpAgentInstall.tsx +7 -2
- package/_standalone/components/settings/Primitives.tsx +48 -104
- package/_standalone/components/settings/ProviderModal.tsx +87 -0
- package/_standalone/components/settings/SettingsContent.tsx +2 -5
- package/_standalone/components/settings/TestButton.tsx +64 -0
- package/_standalone/components/settings/types.ts +3 -9
- package/_standalone/components/settings/useCustomProviderForm.ts +132 -0
- package/_standalone/components/setup/StepAI.tsx +12 -5
- package/_standalone/components/shared/ModelInput.tsx +220 -0
- package/_standalone/components/shared/ProviderSelect.tsx +126 -36
- package/_standalone/hooks/useAskChat.ts +100 -13
- package/_standalone/hooks/useAskPanel.ts +17 -1
- package/_standalone/lib/settings-ai-client.ts +17 -8
- package/_standalone/tsconfig.tsbuildinfo +1 -1
- package/app/.antigravity/mcp_config.json +14 -0
- package/app/app/api/ask/route.ts +154 -45
- package/app/app/api/mcp/agents/route.ts +3 -3
- package/app/app/api/settings/list-models/route.ts +36 -9
- package/app/app/api/settings/route.ts +14 -42
- package/app/app/api/settings/test-key/route.ts +78 -2
- package/app/app/api/setup/route.ts +36 -18
- package/app/app/api/skills/route.ts +1 -1
- package/app/app/globals.css +4 -4
- package/app/app/layout.tsx +5 -3
- package/app/app/view/[...path]/page.tsx +5 -0
- package/app/components/ActivityBar.tsx +17 -6
- package/app/components/HomeContent.tsx +11 -0
- package/app/components/InboxView.tsx +656 -0
- package/app/components/Panel.tsx +24 -6
- package/app/components/SidebarLayout.tsx +36 -8
- package/app/components/UpdateToast.tsx +255 -0
- package/app/components/agents/AgentDetailContent.tsx +8 -8
- package/app/components/agents/AgentsMcpSection.tsx +2 -2
- package/app/components/agents/AgentsOverviewSection.tsx +5 -1
- package/app/components/agents/AgentsPanelA2aTab.tsx +173 -113
- package/app/components/agents/AgentsSkillsSection.tsx +2 -2
- package/app/components/ask/AskContent.tsx +83 -44
- package/app/components/ask/AskHeader.tsx +8 -1
- package/app/components/ask/MessageList.tsx +37 -3
- package/app/components/ask/ProviderModelCapsule.tsx +444 -174
- package/app/components/home/InboxSection.tsx +25 -25
- package/app/components/settings/AiTab.tsx +353 -298
- package/app/components/settings/CustomProviderFields.tsx +121 -0
- package/app/components/settings/CustomProvidersCard.tsx +154 -0
- package/app/components/settings/KnowledgeTab.tsx +6 -20
- package/app/components/settings/McpAgentInstall.tsx +7 -2
- package/app/components/settings/Primitives.tsx +48 -104
- package/app/components/settings/ProviderModal.tsx +87 -0
- package/app/components/settings/SettingsContent.tsx +2 -5
- package/app/components/settings/TestButton.tsx +64 -0
- package/app/components/settings/types.ts +3 -9
- package/app/components/settings/useCustomProviderForm.ts +132 -0
- package/app/components/setup/StepAI.tsx +12 -5
- package/app/components/shared/ModelInput.tsx +220 -0
- package/app/components/shared/ProviderSelect.tsx +126 -36
- package/app/hooks/useAskChat.ts +100 -13
- package/app/hooks/useAskPanel.ts +17 -1
- package/app/lib/acp/registry.ts +92 -10
- package/app/lib/agent/context.ts +65 -0
- package/app/lib/agent/providers.ts +25 -0
- package/app/lib/agent/tools.ts +1 -1
- package/app/lib/custom-endpoints.ts +160 -0
- package/app/lib/fs.ts +8 -1
- package/app/lib/i18n/modules/ai-chat.ts +6 -0
- package/app/lib/i18n/modules/knowledge.ts +16 -0
- package/app/lib/i18n/modules/onboarding.ts +4 -0
- package/app/lib/i18n/modules/settings.ts +88 -2
- package/app/lib/mcp-agents.ts +11 -0
- package/app/lib/pi-integration/skills.ts +16 -4
- package/app/lib/settings-ai-client.ts +17 -8
- package/app/lib/settings.ts +68 -72
- package/app/lib/types.ts +4 -0
- package/bin/lib/mcp-agents.js +11 -0
- package/bin/lib/mcp-install.js +71 -7
- package/package.json +1 -1
- package/_standalone/.next/server/chunks/530.js +0 -218
- package/_standalone/.next/server/chunks/8955.js +0 -52
- package/_standalone/.next/static/chunks/1369-7d0ac5d1564eed1e.js +0 -1
- package/_standalone/.next/static/chunks/3427-2e61a5df1f5e55fb.js +0 -1
- package/_standalone/.next/static/chunks/5581-0c700c20718bd916.js +0 -29
- package/_standalone/.next/static/chunks/6297-085daa21037d5f81.js +0 -1
- package/_standalone/.next/static/chunks/6636-9bbc90fb3b8731fe.js +0 -6
- package/_standalone/.next/static/chunks/7637-904b0a381dc3ec02.js +0 -1
- package/_standalone/.next/static/chunks/8520-76d1b05072178b43.js +0 -22
- package/_standalone/.next/static/chunks/8658-16ff58b75ae37fbb.js +0 -1
- package/_standalone/.next/static/chunks/9905-a19d379cb225246e.js +0 -1
- package/_standalone/.next/static/chunks/app/agents/[agentKey]/page-0ea3571c8fbae823.js +0 -1
- package/_standalone/.next/static/chunks/app/agents/page-66858acbcd1d4bf8.js +0 -1
- package/_standalone/.next/static/chunks/app/echo/[segment]/page-bf5c290fa3ccff09.js +0 -11
- package/_standalone/.next/static/chunks/app/layout-a5d5925b47e87cc3.js +0 -164
- package/_standalone/.next/static/chunks/app/setup/page-821714e7477be46c.js +0 -1
- package/_standalone/.next/static/chunks/app/trash/page-40bc7316806acd62.js +0 -1
- package/_standalone/.next/static/chunks/app/view/[...path]/page-6fbb14b8f322d0f0.js +0 -12
- package/_standalone/.next/static/chunks/app/wiki/page-ba36eccf4fe62cfe.js +0 -1
- package/_standalone/.next/static/css/b57c4eb3cc88308b.css +0 -1
- package/_standalone/lib/agent/context.ts +0 -403
- /package/_standalone/.next/static/{5GmVArEG8OX03azKICsGq → eIlwbGas1iRGonlPyEwj7}/_buildManifest.js +0 -0
- /package/_standalone/.next/static/{5GmVArEG8OX03azKICsGq → eIlwbGas1iRGonlPyEwj7}/_ssgManifest.js +0 -0
|
@@ -2,102 +2,120 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
|
-
import { Cpu, ChevronDown, Check,
|
|
5
|
+
import { Cpu, ChevronDown, ChevronRight, Check, Loader2, Search, RefreshCw } from 'lucide-react';
|
|
6
6
|
import { useLocale } from '@/lib/stores/locale-store';
|
|
7
7
|
import {
|
|
8
8
|
type ProviderId,
|
|
9
9
|
PROVIDER_PRESETS,
|
|
10
|
-
ALL_PROVIDER_IDS,
|
|
11
10
|
isProviderId,
|
|
12
|
-
getApiKeyEnvVar,
|
|
13
11
|
} from '@/lib/agent/providers';
|
|
12
|
+
import { type Provider, isProviderEntryId, findProvider } from '@/lib/custom-endpoints';
|
|
14
13
|
|
|
15
|
-
const STORAGE_KEY = 'mindos-provider-
|
|
14
|
+
const STORAGE_KEY = 'mindos-provider-model';
|
|
15
|
+
|
|
16
|
+
type ProviderSelection = ProviderId | `p_${string}` | null;
|
|
16
17
|
|
|
17
18
|
interface ProviderModelCapsuleProps {
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
providerValue: ProviderSelection;
|
|
20
|
+
onProviderChange: (provider: ProviderSelection) => void;
|
|
21
|
+
modelValue: string | null;
|
|
22
|
+
onModelChange: (model: string | null) => void;
|
|
20
23
|
disabled?: boolean;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
interface DropdownPos {
|
|
24
|
-
top: number;
|
|
25
|
-
left: number;
|
|
26
|
-
direction: 'up' | 'down';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
26
|
interface SettingsData {
|
|
30
27
|
ai?: {
|
|
31
|
-
|
|
32
|
-
providers?:
|
|
28
|
+
activeProvider?: string;
|
|
29
|
+
providers?: Provider[];
|
|
33
30
|
};
|
|
34
31
|
envOverrides?: Record<string, boolean>;
|
|
35
32
|
}
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
/* ── Persistence ── */
|
|
35
|
+
|
|
36
|
+
export function getPersistedProviderModel(): { provider: ProviderSelection; model: string | null } {
|
|
37
|
+
if (typeof window === 'undefined') return { provider: null, model: null };
|
|
39
38
|
try {
|
|
40
|
-
const
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
40
|
+
if (!raw) {
|
|
41
|
+
const old = localStorage.getItem('mindos-provider-override');
|
|
42
|
+
if (old && (isProviderId(old) || isProviderEntryId(old))) return { provider: old as any, model: null };
|
|
43
|
+
return { provider: null, model: null };
|
|
44
|
+
}
|
|
45
|
+
const parsed = JSON.parse(raw);
|
|
46
|
+
const provider = parsed?.provider && (isProviderId(parsed.provider) || isProviderEntryId(parsed.provider))
|
|
47
|
+
? parsed.provider : null;
|
|
48
|
+
const model = typeof parsed?.model === 'string' ? parsed.model : null;
|
|
49
|
+
return { provider, model };
|
|
50
|
+
} catch { return { provider: null, model: null }; }
|
|
44
51
|
}
|
|
45
52
|
|
|
46
|
-
|
|
53
|
+
function persistProviderModel(provider: ProviderSelection, model: string | null): void {
|
|
47
54
|
try {
|
|
48
|
-
if (provider) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
} catch { /* localStorage unavailable */ }
|
|
55
|
+
if (provider || model) localStorage.setItem(STORAGE_KEY, JSON.stringify({ provider, model }));
|
|
56
|
+
else localStorage.removeItem(STORAGE_KEY);
|
|
57
|
+
localStorage.removeItem('mindos-provider-override');
|
|
58
|
+
} catch {}
|
|
54
59
|
}
|
|
55
60
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
*/
|
|
61
|
-
function getConfiguredProviders(data: SettingsData): ProviderId[] {
|
|
62
|
-
const result: ProviderId[] = [];
|
|
63
|
-
const providers = data.ai?.providers ?? {};
|
|
64
|
-
const env = data.envOverrides ?? {};
|
|
65
|
-
|
|
66
|
-
for (const id of ALL_PROVIDER_IDS) {
|
|
67
|
-
const preset = PROVIDER_PRESETS[id];
|
|
68
|
-
const hasSettingsKey = providers[id]?.apiKey === '***set***';
|
|
69
|
-
const envVar = getApiKeyEnvVar(id);
|
|
70
|
-
const hasEnvKey = envVar ? !!env[envVar] : false;
|
|
71
|
-
|
|
72
|
-
if (hasSettingsKey || hasEnvKey) {
|
|
73
|
-
result.push(id);
|
|
74
|
-
} else if (preset.apiKeyFallback) {
|
|
75
|
-
// Local providers (Ollama etc.): only show if user has explicitly configured
|
|
76
|
-
// them (selected as default, or saved a model/baseUrl), not just because
|
|
77
|
-
// a fallback key exists and they haven't been touched.
|
|
78
|
-
const isDefault = data.ai?.provider === id;
|
|
79
|
-
const cfg = providers[id];
|
|
80
|
-
if (isDefault || (cfg && (cfg.model || cfg.baseUrl))) {
|
|
81
|
-
result.push(id);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return result;
|
|
61
|
+
/* ── Configured providers ── */
|
|
62
|
+
|
|
63
|
+
function getConfiguredProviders(data: SettingsData): string[] {
|
|
64
|
+
return (data.ai?.providers ?? []).map(p => p.id);
|
|
86
65
|
}
|
|
87
66
|
|
|
67
|
+
/* ── Component ── */
|
|
68
|
+
|
|
88
69
|
export default function ProviderModelCapsule({
|
|
89
|
-
|
|
90
|
-
onChange,
|
|
91
|
-
disabled = false,
|
|
70
|
+
providerValue, onProviderChange, modelValue, onModelChange, disabled = false,
|
|
92
71
|
}: ProviderModelCapsuleProps) {
|
|
93
72
|
const { t, locale } = useLocale();
|
|
94
73
|
const [open, setOpen] = useState(false);
|
|
95
|
-
const [pos, setPos] = useState<DropdownPos | null>(null);
|
|
96
74
|
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
97
|
-
const
|
|
75
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
76
|
+
const flyoutRef = useRef<HTMLDivElement>(null);
|
|
77
|
+
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
78
|
+
const modelListRef = useRef<HTMLDivElement>(null);
|
|
98
79
|
|
|
99
80
|
const [settingsData, setSettingsData] = useState<SettingsData | null>(null);
|
|
100
81
|
|
|
82
|
+
// Flyout state
|
|
83
|
+
const providerPanelRef = useRef<HTMLDivElement>(null);
|
|
84
|
+
const hoveredRowRef = useRef<HTMLDivElement>(null);
|
|
85
|
+
const [hoveredProvider, setHoveredProvider] = useState<string | null>(null);
|
|
86
|
+
const [flyoutStyle, setFlyoutStyle] = useState<React.CSSProperties>({});
|
|
87
|
+
const [expandedModels, setExpandedModels] = useState<string[] | null>(null);
|
|
88
|
+
const [modelsLoading, setModelsLoading] = useState(false);
|
|
89
|
+
const [modelsError, setModelsError] = useState('');
|
|
90
|
+
const [modelSearch, setModelSearch] = useState('');
|
|
91
|
+
const [modelHighlight, setModelHighlight] = useState(-1);
|
|
92
|
+
const fetchVersionRef = useRef(0);
|
|
93
|
+
const modelsCacheRef = useRef<Record<string, string[]>>({});
|
|
94
|
+
|
|
95
|
+
// Debounced flyout close — prevents flicker when mouse crosses gap
|
|
96
|
+
const closeTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
97
|
+
const openTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
98
|
+
|
|
99
|
+
const cancelCloseTimer = useCallback(() => {
|
|
100
|
+
if (closeTimerRef.current) { clearTimeout(closeTimerRef.current); closeTimerRef.current = undefined; }
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
103
|
+
const startCloseTimer = useCallback(() => {
|
|
104
|
+
cancelCloseTimer();
|
|
105
|
+
closeTimerRef.current = setTimeout(() => {
|
|
106
|
+
setHoveredProvider(null);
|
|
107
|
+
setModelSearch('');
|
|
108
|
+
}, 300); // 300ms grace period to cross the gap smoothly
|
|
109
|
+
}, [cancelCloseTimer]);
|
|
110
|
+
|
|
111
|
+
// Cleanup timers on unmount
|
|
112
|
+
useEffect(() => () => {
|
|
113
|
+
if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
|
|
114
|
+
if (openTimerRef.current) clearTimeout(openTimerRef.current);
|
|
115
|
+
if (repositionTimerRef.current) clearTimeout(repositionTimerRef.current);
|
|
116
|
+
}, []);
|
|
117
|
+
|
|
118
|
+
// Fetch settings
|
|
101
119
|
useEffect(() => {
|
|
102
120
|
let cancelled = false;
|
|
103
121
|
const doFetch = () => {
|
|
@@ -108,192 +126,444 @@ export default function ProviderModelCapsule({
|
|
|
108
126
|
};
|
|
109
127
|
doFetch();
|
|
110
128
|
const onVisible = () => { if (document.visibilityState === 'visible') doFetch(); };
|
|
111
|
-
const
|
|
129
|
+
const onChange = () => doFetch();
|
|
112
130
|
document.addEventListener('visibilitychange', onVisible);
|
|
113
|
-
window.addEventListener('mindos:settings-changed',
|
|
114
|
-
return () => { cancelled = true; document.removeEventListener('visibilitychange', onVisible); window.removeEventListener('mindos:settings-changed',
|
|
131
|
+
window.addEventListener('mindos:settings-changed', onChange);
|
|
132
|
+
return () => { cancelled = true; document.removeEventListener('visibilitychange', onVisible); window.removeEventListener('mindos:settings-changed', onChange); };
|
|
115
133
|
}, []);
|
|
116
134
|
|
|
117
|
-
const defaultProvider =
|
|
118
|
-
|
|
119
|
-
: 'anthropic';
|
|
135
|
+
const defaultProvider = settingsData?.ai?.activeProvider || '';
|
|
136
|
+
|
|
120
137
|
const configuredProviders = useMemo(
|
|
121
138
|
() => settingsData ? getConfiguredProviders(settingsData) : [],
|
|
122
139
|
[settingsData],
|
|
123
140
|
);
|
|
124
141
|
|
|
125
|
-
// Auto-clear stale override
|
|
142
|
+
// Auto-clear stale override
|
|
126
143
|
useEffect(() => {
|
|
127
|
-
if (!settingsData || !
|
|
128
|
-
if (!configuredProviders.includes(
|
|
129
|
-
|
|
130
|
-
|
|
144
|
+
if (!settingsData || !providerValue) return;
|
|
145
|
+
if (!configuredProviders.includes(providerValue)) {
|
|
146
|
+
onProviderChange(null); onModelChange(null);
|
|
147
|
+
persistProviderModel(null, null);
|
|
131
148
|
}
|
|
132
|
-
}, [settingsData,
|
|
149
|
+
}, [settingsData, providerValue, configuredProviders, onProviderChange, onModelChange]);
|
|
150
|
+
|
|
151
|
+
// Resolve active display
|
|
152
|
+
const activeProvider = providerValue ?? defaultProvider;
|
|
153
|
+
const activeEntry = findProvider(settingsData?.ai?.providers ?? [], String(activeProvider));
|
|
154
|
+
const activePreset = activeEntry ? PROVIDER_PRESETS[activeEntry.protocol] : null;
|
|
155
|
+
const defaultModel = activeEntry?.model
|
|
156
|
+
|| activePreset?.defaultModel || '';
|
|
157
|
+
const displayModel = modelValue || defaultModel;
|
|
158
|
+
const displayName = activeEntry?.name
|
|
159
|
+
|| (activePreset ? (locale === 'zh' ? activePreset.nameZh : activePreset.name) : String(activeProvider));
|
|
133
160
|
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
const displayName = locale === 'zh' ? activePreset.nameZh : activePreset.name;
|
|
161
|
+
/* ── Dropdown positioning ── */
|
|
162
|
+
const [dropdownStyle, setDropdownStyle] = useState<React.CSSProperties>({});
|
|
163
|
+
const repositionTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
138
164
|
|
|
139
165
|
const reposition = useCallback(() => {
|
|
140
166
|
if (!triggerRef.current) return;
|
|
141
167
|
const rect = triggerRef.current.getBoundingClientRect();
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
top: direction === 'up' ? rect.top - 6 : rect.bottom + 6,
|
|
149
|
-
direction,
|
|
168
|
+
const goUp = rect.top > window.innerHeight - rect.bottom && rect.top > 280;
|
|
169
|
+
setDropdownStyle({
|
|
170
|
+
position: 'fixed',
|
|
171
|
+
left: Math.min(rect.left, window.innerWidth - 230),
|
|
172
|
+
...(goUp ? { bottom: window.innerHeight - rect.top + 6 } : { top: rect.bottom + 6 }),
|
|
173
|
+
zIndex: 50,
|
|
150
174
|
});
|
|
151
175
|
}, []);
|
|
152
176
|
|
|
177
|
+
// Debounce repositioning to prevent jank from rapid mouse events
|
|
178
|
+
const debouncedReposition = useCallback(() => {
|
|
179
|
+
if (repositionTimerRef.current) clearTimeout(repositionTimerRef.current);
|
|
180
|
+
repositionTimerRef.current = setTimeout(() => {
|
|
181
|
+
reposition();
|
|
182
|
+
}, 0); // Use requestAnimationFrame-like timing
|
|
183
|
+
}, [reposition]);
|
|
184
|
+
|
|
185
|
+
useEffect(() => { if (open) reposition(); }, [open, reposition]);
|
|
153
186
|
useEffect(() => {
|
|
154
187
|
if (!open) return;
|
|
155
|
-
|
|
156
|
-
|
|
188
|
+
window.addEventListener('scroll', debouncedReposition, true);
|
|
189
|
+
window.addEventListener('resize', debouncedReposition);
|
|
190
|
+
return () => {
|
|
191
|
+
window.removeEventListener('scroll', debouncedReposition, true);
|
|
192
|
+
window.removeEventListener('resize', debouncedReposition);
|
|
193
|
+
if (repositionTimerRef.current) clearTimeout(repositionTimerRef.current);
|
|
194
|
+
};
|
|
195
|
+
}, [open, debouncedReposition]);
|
|
157
196
|
|
|
197
|
+
// Close on outside click — check trigger, provider panel, and flyout
|
|
158
198
|
useEffect(() => {
|
|
159
199
|
if (!open) return;
|
|
160
200
|
const handler = (e: MouseEvent) => {
|
|
161
201
|
const target = e.target as Node;
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
)
|
|
166
|
-
setOpen(false);
|
|
167
|
-
}
|
|
202
|
+
if (triggerRef.current?.contains(target)) return;
|
|
203
|
+
if (containerRef.current?.contains(target)) return;
|
|
204
|
+
if (flyoutRef.current?.contains(target)) return;
|
|
205
|
+
setOpen(false); setHoveredProvider(null);
|
|
168
206
|
};
|
|
169
207
|
document.addEventListener('mousedown', handler);
|
|
170
208
|
return () => document.removeEventListener('mousedown', handler);
|
|
171
209
|
}, [open]);
|
|
172
210
|
|
|
211
|
+
// Escape key
|
|
173
212
|
useEffect(() => {
|
|
174
213
|
if (!open) return;
|
|
175
214
|
const handler = (e: KeyboardEvent) => {
|
|
176
|
-
if (e.key === 'Escape')
|
|
215
|
+
if (e.key === 'Escape') {
|
|
216
|
+
if (hoveredProvider) { setHoveredProvider(null); setModelSearch(''); }
|
|
217
|
+
else setOpen(false);
|
|
218
|
+
}
|
|
177
219
|
};
|
|
178
220
|
document.addEventListener('keydown', handler);
|
|
179
221
|
return () => document.removeEventListener('keydown', handler);
|
|
180
|
-
}, [open]);
|
|
222
|
+
}, [open, hoveredProvider]);
|
|
181
223
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
224
|
+
/* ── Model fetching ── */
|
|
225
|
+
const fetchModels = useCallback(async (providerId: string, force = false) => {
|
|
226
|
+
if (!force && modelsCacheRef.current[providerId]) {
|
|
227
|
+
setExpandedModels(modelsCacheRef.current[providerId]);
|
|
228
|
+
setModelsLoading(false);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (force) delete modelsCacheRef.current[providerId];
|
|
232
|
+
setModelsLoading(true); setModelsError(''); setExpandedModels(null);
|
|
233
|
+
const version = ++fetchVersionRef.current;
|
|
234
|
+
try {
|
|
235
|
+
const res = await fetch('/api/settings/list-models', {
|
|
236
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
237
|
+
body: JSON.stringify({ provider: providerId }),
|
|
238
|
+
});
|
|
239
|
+
if (version !== fetchVersionRef.current) return;
|
|
240
|
+
const json = await res.json();
|
|
241
|
+
if (version !== fetchVersionRef.current) return;
|
|
242
|
+
if (json.ok && Array.isArray(json.models)) {
|
|
243
|
+
modelsCacheRef.current[providerId] = json.models;
|
|
244
|
+
setExpandedModels(json.models);
|
|
245
|
+
} else { setModelsError(json.error || 'Failed'); }
|
|
246
|
+
} catch {
|
|
247
|
+
if (version === fetchVersionRef.current) setModelsError('Network error');
|
|
248
|
+
} finally {
|
|
249
|
+
if (version === fetchVersionRef.current) setModelsLoading(false);
|
|
250
|
+
}
|
|
251
|
+
}, []);
|
|
191
252
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
253
|
+
// Determine if a provider can show model flyout
|
|
254
|
+
const canProviderExpand = useCallback((id: string) => {
|
|
255
|
+
const entry = findProvider(settingsData?.ai?.providers ?? [], id);
|
|
256
|
+
if (!entry) return false;
|
|
257
|
+
const preset = PROVIDER_PRESETS[entry.protocol];
|
|
258
|
+
return preset?.supportsListModels ?? false;
|
|
259
|
+
}, [settingsData]);
|
|
197
260
|
|
|
261
|
+
// Compute flyout position: anchored to right edge of provider panel, aligned to hovered row
|
|
262
|
+
const computeFlyoutPosition = useCallback(() => {
|
|
263
|
+
const panel = providerPanelRef.current;
|
|
264
|
+
const row = hoveredRowRef.current;
|
|
265
|
+
if (!panel || !row) return;
|
|
266
|
+
const panelRect = panel.getBoundingClientRect();
|
|
267
|
+
const rowRect = row.getBoundingClientRect();
|
|
268
|
+
const flyoutWidth = 220;
|
|
269
|
+
const flyoutMaxH = 320; // approx max height of flyout
|
|
270
|
+
const gap = 4;
|
|
271
|
+
const left = panelRect.right + gap;
|
|
272
|
+
// Align top to the hovered row by default
|
|
273
|
+
let top = rowRect.top;
|
|
274
|
+
// If flyout would overflow below viewport, shift upward
|
|
275
|
+
const spaceBelow = window.innerHeight - top;
|
|
276
|
+
if (spaceBelow < flyoutMaxH) {
|
|
277
|
+
// Align bottom of flyout to bottom of row instead
|
|
278
|
+
top = Math.max(8, rowRect.bottom - flyoutMaxH);
|
|
279
|
+
}
|
|
280
|
+
// If flyout would overflow right edge, flip to left side
|
|
281
|
+
const actualLeft = left + flyoutWidth > window.innerWidth - 8
|
|
282
|
+
? panelRect.left - flyoutWidth - gap
|
|
283
|
+
: left;
|
|
284
|
+
setFlyoutStyle({
|
|
285
|
+
position: 'fixed',
|
|
286
|
+
left: actualLeft,
|
|
287
|
+
top,
|
|
288
|
+
zIndex: 51,
|
|
289
|
+
});
|
|
290
|
+
}, []);
|
|
291
|
+
|
|
292
|
+
// Open flyout for a provider (debounced to prevent flicker)
|
|
293
|
+
const openFlyout = useCallback((providerId: string) => {
|
|
294
|
+
cancelCloseTimer();
|
|
295
|
+
if (openTimerRef.current) clearTimeout(openTimerRef.current);
|
|
296
|
+
openTimerRef.current = setTimeout(() => {
|
|
297
|
+
if (!canProviderExpand(providerId)) {
|
|
298
|
+
setHoveredProvider(null); setModelSearch('');
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
setHoveredProvider(providerId);
|
|
302
|
+
setModelSearch(''); setModelHighlight(-1); setModelsError('');
|
|
303
|
+
setExpandedModels(modelsCacheRef.current[providerId] ?? null);
|
|
304
|
+
if (!modelsCacheRef.current[providerId]) fetchModels(providerId);
|
|
305
|
+
// Position flyout after state update
|
|
306
|
+
requestAnimationFrame(() => {
|
|
307
|
+
computeFlyoutPosition();
|
|
308
|
+
setTimeout(() => searchInputRef.current?.focus(), 50);
|
|
309
|
+
});
|
|
310
|
+
}, 80);
|
|
311
|
+
}, [cancelCloseTimer, fetchModels, computeFlyoutPosition, canProviderExpand]);
|
|
312
|
+
|
|
313
|
+
// Close flyout for non-expandable items (immediate, no debounce)
|
|
314
|
+
const closeFlyoutImmediate = useCallback(() => {
|
|
315
|
+
cancelCloseTimer();
|
|
316
|
+
if (openTimerRef.current) { clearTimeout(openTimerRef.current); openTimerRef.current = undefined; }
|
|
317
|
+
setHoveredProvider(null); setModelSearch('');
|
|
318
|
+
}, [cancelCloseTimer]);
|
|
319
|
+
|
|
320
|
+
/* ── Selection handlers ── */
|
|
321
|
+
const handleSelectProvider = useCallback((provider: ProviderSelection) => {
|
|
322
|
+
onProviderChange(provider); onModelChange(null);
|
|
323
|
+
persistProviderModel(provider, null);
|
|
324
|
+
setOpen(false); setHoveredProvider(null); setModelSearch('');
|
|
325
|
+
}, [onProviderChange, onModelChange]);
|
|
326
|
+
|
|
327
|
+
const handleSelectModel = useCallback((provider: ProviderSelection, model: string) => {
|
|
328
|
+
onProviderChange(provider); onModelChange(model);
|
|
329
|
+
persistProviderModel(provider, model);
|
|
330
|
+
setOpen(false); setHoveredProvider(null); setModelSearch('');
|
|
331
|
+
}, [onProviderChange, onModelChange]);
|
|
332
|
+
|
|
333
|
+
/* ── Filtered models ── */
|
|
334
|
+
const filteredModels = useMemo(() => {
|
|
335
|
+
if (!expandedModels) return [];
|
|
336
|
+
if (!modelSearch.trim()) return expandedModels;
|
|
337
|
+
const q = modelSearch.toLowerCase();
|
|
338
|
+
return expandedModels.filter(m => m.toLowerCase().includes(q));
|
|
339
|
+
}, [expandedModels, modelSearch]);
|
|
340
|
+
|
|
341
|
+
useEffect(() => { setModelHighlight(-1); }, [filteredModels]);
|
|
342
|
+
|
|
343
|
+
const handleModelKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
344
|
+
if (!filteredModels.length) return;
|
|
345
|
+
if (e.key === 'ArrowDown') { e.preventDefault(); setModelHighlight(i => (i + 1) % filteredModels.length); }
|
|
346
|
+
else if (e.key === 'ArrowUp') { e.preventDefault(); setModelHighlight(i => (i - 1 + filteredModels.length) % filteredModels.length); }
|
|
347
|
+
else if (e.key === 'Enter' && modelHighlight >= 0 && modelHighlight < filteredModels.length && hoveredProvider) {
|
|
348
|
+
e.preventDefault(); handleSelectModel(hoveredProvider as ProviderSelection, filteredModels[modelHighlight]);
|
|
349
|
+
} else if (e.key === 'Escape') { e.preventDefault(); setHoveredProvider(null); setModelSearch(''); }
|
|
350
|
+
}, [filteredModels, modelHighlight, hoveredProvider, handleSelectModel]);
|
|
351
|
+
|
|
352
|
+
useEffect(() => {
|
|
353
|
+
if (modelHighlight < 0 || !modelListRef.current) return;
|
|
354
|
+
const items = modelListRef.current.querySelectorAll('[data-model-item]');
|
|
355
|
+
items[modelHighlight]?.scrollIntoView({ block: 'nearest' });
|
|
356
|
+
}, [modelHighlight]);
|
|
357
|
+
|
|
358
|
+
/* ── Guards ── */
|
|
198
359
|
if (!settingsData || configuredProviders.length === 0) return null;
|
|
199
360
|
|
|
200
|
-
const modelShort =
|
|
201
|
-
?
|
|
202
|
-
|
|
361
|
+
const modelShort = (displayModel || '').length > 20
|
|
362
|
+
? (displayModel || '').slice(0, 18) + '…' : displayModel;
|
|
363
|
+
// For built-in providers, use shortLabel; for custom, truncate if too long
|
|
364
|
+
const providerDisplay = (displayName || '').length > 12
|
|
365
|
+
? (displayName || '').slice(0, 10) + '…'
|
|
366
|
+
: (activePreset?.shortLabel || displayName);
|
|
367
|
+
const capsuleTooltip = `${displayName} · ${displayModel}`;
|
|
368
|
+
const providerIds = configuredProviders;
|
|
369
|
+
const hasModelOverride = !!(modelValue && modelValue !== defaultModel);
|
|
203
370
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
<button
|
|
219
|
-
type="button"
|
|
220
|
-
role="option"
|
|
221
|
-
aria-selected={value === null}
|
|
222
|
-
onClick={() => handleSelect(null)}
|
|
223
|
-
className="flex w-full items-center gap-2.5 px-3 py-2 text-xs text-left transition-colors hover:bg-muted"
|
|
371
|
+
/* ── Render: flyout (right panel) — positioned absolutely via portal ── */
|
|
372
|
+
const renderFlyout = () => {
|
|
373
|
+
if (!hoveredProvider) return null;
|
|
374
|
+
const hovEntry = findProvider(settingsData?.ai?.providers ?? [], hoveredProvider);
|
|
375
|
+
const preset = hovEntry ? PROVIDER_PRESETS[hovEntry.protocol] : null;
|
|
376
|
+
const displayName = hovEntry?.name || (preset ? (locale === 'zh' ? preset.nameZh : preset.name) : String(hoveredProvider));
|
|
377
|
+
|
|
378
|
+
return createPortal(
|
|
379
|
+
<div
|
|
380
|
+
ref={flyoutRef}
|
|
381
|
+
style={flyoutStyle}
|
|
382
|
+
className="w-[220px] rounded-lg border border-border bg-card shadow-lg py-1 animate-in fade-in-0 zoom-in-95 duration-100"
|
|
383
|
+
onMouseEnter={cancelCloseTimer}
|
|
384
|
+
onMouseLeave={startCloseTimer}
|
|
224
385
|
>
|
|
225
|
-
|
|
226
|
-
<div className="flex-1
|
|
227
|
-
<
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
386
|
+
{/* Header */}
|
|
387
|
+
<div className="flex items-center justify-between px-3 py-1.5 border-b border-border/40">
|
|
388
|
+
<span className="text-2xs font-medium text-muted-foreground">
|
|
389
|
+
{displayName}
|
|
390
|
+
</span>
|
|
391
|
+
<button
|
|
392
|
+
type="button"
|
|
393
|
+
onClick={() => fetchModels(hoveredProvider, true)}
|
|
394
|
+
disabled={modelsLoading}
|
|
395
|
+
className="p-1 text-muted-foreground/50 hover:text-foreground transition-colors disabled:opacity-30"
|
|
396
|
+
title="Refresh"
|
|
397
|
+
>
|
|
398
|
+
<RefreshCw size={10} className={modelsLoading ? 'animate-spin' : ''} />
|
|
399
|
+
</button>
|
|
400
|
+
</div>
|
|
401
|
+
{/* Search */}
|
|
402
|
+
<div className="px-2 pt-1.5 pb-1">
|
|
403
|
+
<div className="relative">
|
|
404
|
+
<Search size={10} className="absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground/40" />
|
|
405
|
+
<input
|
|
406
|
+
ref={searchInputRef}
|
|
407
|
+
type="text"
|
|
408
|
+
value={modelSearch}
|
|
409
|
+
onChange={e => setModelSearch(e.target.value)}
|
|
410
|
+
onKeyDown={handleModelKeyDown}
|
|
411
|
+
placeholder={t.ask?.searchModels ?? 'Search...'}
|
|
412
|
+
className="w-full text-2xs pl-6 pr-2 py-1 rounded border border-border/50 bg-transparent text-foreground placeholder:text-muted-foreground/40 focus:outline-none focus:border-[var(--amber)]/50"
|
|
413
|
+
autoComplete="off"
|
|
414
|
+
/>
|
|
232
415
|
</div>
|
|
233
416
|
</div>
|
|
234
|
-
{
|
|
235
|
-
|
|
417
|
+
{/* List */}
|
|
418
|
+
<div ref={modelListRef} className="max-h-[240px] overflow-y-auto px-1 pb-1">
|
|
419
|
+
{modelsLoading && !expandedModels && (
|
|
420
|
+
<div className="flex items-center gap-1.5 px-2 py-3 text-2xs text-muted-foreground justify-center">
|
|
421
|
+
<Loader2 size={10} className="animate-spin" />
|
|
422
|
+
{t.ask?.loadingModels ?? 'Loading...'}
|
|
423
|
+
</div>
|
|
424
|
+
)}
|
|
425
|
+
{modelsError && !modelsLoading && (
|
|
426
|
+
<div className="px-2 py-3 text-2xs text-destructive text-center">{modelsError}</div>
|
|
427
|
+
)}
|
|
428
|
+
{!modelsLoading && !modelsError && filteredModels.length === 0 && expandedModels !== null && (
|
|
429
|
+
<div className="px-2 py-3 text-2xs text-muted-foreground text-center">
|
|
430
|
+
{modelSearch ? 'No matches' : 'No models'}
|
|
431
|
+
</div>
|
|
432
|
+
)}
|
|
433
|
+
{filteredModels.map((m, i) => {
|
|
434
|
+
const isModelSelected = providerValue === hoveredProvider && modelValue === m;
|
|
435
|
+
const defModel = hovEntry?.model || preset?.defaultModel;
|
|
436
|
+
return (
|
|
437
|
+
<button
|
|
438
|
+
key={m} type="button" data-model-item
|
|
439
|
+
onClick={() => handleSelectModel(hoveredProvider as ProviderSelection, m)}
|
|
440
|
+
className={`w-full text-left px-2 py-1 text-2xs rounded transition-colors flex items-center gap-1 ${
|
|
441
|
+
isModelSelected ? 'bg-[var(--amber)]/12 text-foreground font-medium'
|
|
442
|
+
: i === modelHighlight ? 'bg-accent' : 'hover:bg-accent/60'
|
|
443
|
+
}`}
|
|
444
|
+
>
|
|
445
|
+
{isModelSelected
|
|
446
|
+
? <Check size={9} className="shrink-0 text-[var(--amber)]" />
|
|
447
|
+
: <span className="w-[9px] shrink-0" />}
|
|
448
|
+
<span className="truncate">{m}</span>
|
|
449
|
+
{m === defModel && !isModelSelected && (
|
|
450
|
+
<span className="ml-auto text-[9px] text-muted-foreground/30 shrink-0">default</span>
|
|
451
|
+
)}
|
|
452
|
+
</button>
|
|
453
|
+
);
|
|
454
|
+
})}
|
|
455
|
+
</div>
|
|
456
|
+
</div>,
|
|
457
|
+
document.body,
|
|
458
|
+
);
|
|
459
|
+
};
|
|
236
460
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
461
|
+
/* ── Render: dropdown ── */
|
|
462
|
+
const dropdown = open ? (
|
|
463
|
+
<div
|
|
464
|
+
ref={containerRef}
|
|
465
|
+
style={dropdownStyle}
|
|
466
|
+
>
|
|
467
|
+
{/* Provider list (sole child — never moves) */}
|
|
468
|
+
<div
|
|
469
|
+
ref={providerPanelRef}
|
|
470
|
+
role="listbox"
|
|
471
|
+
aria-label={t.ask?.providerCapsule ?? 'Provider'}
|
|
472
|
+
className="w-[220px] rounded-lg border border-border bg-card shadow-lg py-1 animate-in fade-in-0 zoom-in-95 duration-100"
|
|
473
|
+
style={{ maxHeight: '70vh', overflowY: 'auto' }}
|
|
474
|
+
>
|
|
475
|
+
{providerIds.map((id) => {
|
|
476
|
+
const entry = findProvider(settingsData?.ai?.providers ?? [], id);
|
|
477
|
+
if (!entry) return null;
|
|
478
|
+
const preset = PROVIDER_PRESETS[entry.protocol];
|
|
479
|
+
const provName = entry.name || (locale === 'zh' ? preset?.nameZh : preset?.name) || id;
|
|
480
|
+
const provModel = modelValue && providerValue === id ? modelValue
|
|
481
|
+
: entry.model || preset?.defaultModel || '';
|
|
482
|
+
const isSelected = providerValue === id || (!providerValue && defaultProvider === id);
|
|
483
|
+
const isHovered = hoveredProvider === id;
|
|
484
|
+
const canExpand = canProviderExpand(id);
|
|
240
485
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
486
|
+
return (
|
|
487
|
+
<div
|
|
488
|
+
key={id}
|
|
489
|
+
ref={isHovered ? hoveredRowRef : undefined}
|
|
490
|
+
onMouseEnter={() => canExpand ? openFlyout(id) : closeFlyoutImmediate()}
|
|
491
|
+
onMouseLeave={startCloseTimer}
|
|
492
|
+
>
|
|
493
|
+
<div className={`flex w-full items-center text-xs transition-colors ${isHovered ? 'bg-accent/60' : 'hover:bg-muted/60'}`}>
|
|
494
|
+
<button
|
|
495
|
+
type="button" role="option" aria-selected={isSelected}
|
|
496
|
+
onClick={() => handleSelectProvider(id as ProviderSelection)}
|
|
497
|
+
className="flex flex-1 items-center gap-2 px-3 py-1.5 min-w-0"
|
|
498
|
+
>
|
|
499
|
+
<div className="flex-1 min-w-0 truncate">
|
|
500
|
+
<span className={`text-xs ${isSelected ? 'font-medium text-foreground' : 'text-foreground/80'}`}>
|
|
501
|
+
{provName}
|
|
502
|
+
</span>
|
|
503
|
+
<span className="text-2xs text-muted-foreground ml-1.5">{provModel}</span>
|
|
504
|
+
</div>
|
|
505
|
+
{isSelected && <Check size={11} className="shrink-0 text-[var(--amber)]" />}
|
|
506
|
+
</button>
|
|
507
|
+
{canExpand && (
|
|
508
|
+
<button
|
|
509
|
+
type="button"
|
|
510
|
+
onClick={(e) => {
|
|
511
|
+
e.stopPropagation();
|
|
512
|
+
if (hoveredProvider === id) closeFlyoutImmediate();
|
|
513
|
+
else openFlyout(id);
|
|
514
|
+
}}
|
|
515
|
+
className={`shrink-0 px-1.5 py-1.5 mr-1 rounded transition-colors ${
|
|
516
|
+
isHovered ? 'text-foreground' : 'text-muted-foreground/40 hover:text-muted-foreground'
|
|
517
|
+
}`}
|
|
518
|
+
title={t.ask?.selectModel ?? 'Select model'}
|
|
519
|
+
>
|
|
520
|
+
<ChevronRight size={11} />
|
|
521
|
+
</button>
|
|
522
|
+
)}
|
|
523
|
+
</div>
|
|
259
524
|
</div>
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
})}
|
|
525
|
+
);
|
|
526
|
+
})}
|
|
527
|
+
</div>
|
|
264
528
|
</div>
|
|
265
529
|
) : null;
|
|
266
530
|
|
|
531
|
+
/* ── Capsule button ── */
|
|
267
532
|
return (
|
|
268
533
|
<>
|
|
269
534
|
<button
|
|
270
535
|
ref={triggerRef}
|
|
271
536
|
type="button"
|
|
272
|
-
onClick={() => {
|
|
537
|
+
onClick={() => {
|
|
538
|
+
if (disabled) return;
|
|
539
|
+
setOpen(v => !v);
|
|
540
|
+
if (open) { setHoveredProvider(null); setModelSearch(''); }
|
|
541
|
+
}}
|
|
273
542
|
disabled={disabled}
|
|
274
543
|
className={`
|
|
275
544
|
inline-flex items-center gap-1 rounded-full px-2.5 py-0.5
|
|
276
|
-
text-2xs font-medium transition-colors select-none
|
|
545
|
+
text-2xs font-medium transition-colors select-none max-w-[260px]
|
|
277
546
|
border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring
|
|
278
547
|
disabled:opacity-40 disabled:cursor-not-allowed
|
|
279
|
-
${
|
|
548
|
+
${providerValue || hasModelOverride
|
|
280
549
|
? 'bg-[var(--amber)]/10 border-[var(--amber)]/25 text-foreground hover:bg-[var(--amber)]/15'
|
|
281
550
|
: 'bg-muted/50 border-border/50 text-muted-foreground hover:bg-muted hover:text-foreground'
|
|
282
551
|
}
|
|
283
552
|
`}
|
|
284
|
-
title={
|
|
553
|
+
title={capsuleTooltip}
|
|
285
554
|
aria-expanded={open}
|
|
286
555
|
aria-haspopup="listbox"
|
|
287
556
|
>
|
|
288
557
|
<Cpu size={11} className="shrink-0" />
|
|
289
|
-
<span className="truncate
|
|
290
|
-
{
|
|
558
|
+
<span className="truncate">
|
|
559
|
+
{providerDisplay}
|
|
291
560
|
<span className="text-muted-foreground"> · </span>
|
|
292
|
-
<span className=
|
|
561
|
+
<span className={hasModelOverride ? 'text-[var(--amber)]' : 'text-muted-foreground'}>{modelShort}</span>
|
|
293
562
|
</span>
|
|
294
563
|
<ChevronDown size={10} className="shrink-0 text-muted-foreground" />
|
|
295
564
|
</button>
|
|
296
565
|
{typeof document !== 'undefined' && dropdown && createPortal(dropdown, document.body)}
|
|
566
|
+
{typeof document !== 'undefined' && open && renderFlyout()}
|
|
297
567
|
</>
|
|
298
568
|
);
|
|
299
569
|
}
|