@geminilight/mindos 0.6.63 → 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/.mindos-build-version +1 -1
- package/_standalone/.next/BUILD_ID +1 -1
- package/_standalone/.next/app-path-routes-manifest.json +21 -21
- 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_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_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 +48 -42
- 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 +21 -21
- package/_standalone/.next/server/chunks/122.js +222 -0
- 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 +2 -2
- package/_standalone/.next/server/chunks/953.js +3 -3
- package/_standalone/.next/server/chunks/9787.js +2 -0
- 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/5149-4d828886dda479fa.js +1 -0
- package/_standalone/.next/static/chunks/{5581-82e5db227f8e9393.js → 5581-c671163a2fe1b312.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/{3674-be69a8b858ceacdd.js → 7294-cac25d97869afadc.js} +1 -1
- 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-b0dabe793500383d.js → page-2f5cf97e03dc1cc9.js} +1 -1
- package/_standalone/.next/static/chunks/app/agents/{page-1f1ac330c8177cf6.js → page-50eac58d511dcc6e.js} +1 -1
- package/_standalone/.next/static/chunks/app/echo/[segment]/page-2a00f4686adf3885.js +11 -0
- package/_standalone/.next/static/chunks/app/{layout-50a6b1164ee98ab9.js → layout-2cb7a6602d2e5d5f.js} +62 -58
- package/_standalone/.next/static/chunks/app/{page-73802bd31d7f6c9f.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-808f39963bf04715.js → page-26e47dd4c533a58c.js} +2 -2
- package/_standalone/.next/static/css/67e7918f5ed7d147.css +1 -0
- package/_standalone/.next/trace +65 -65
- package/_standalone/__tests__/api/ask-attachments.test.ts +194 -0
- 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/components/ask/AskContent.tsx +70 -40
- package/_standalone/components/ask/AskHeader.tsx +8 -1
- package/_standalone/components/ask/MessageList.tsx +37 -3
- package/_standalone/components/ask/ProviderModelCapsule.tsx +51 -129
- package/_standalone/components/settings/AiTab.tsx +270 -347
- package/_standalone/components/settings/CustomProviderFields.tsx +121 -0
- package/_standalone/components/settings/CustomProvidersCard.tsx +2 -2
- 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 +38 -221
- package/_standalone/components/settings/SettingsContent.tsx +5 -12
- package/_standalone/components/settings/TestButton.tsx +64 -0
- package/_standalone/components/settings/types.ts +3 -12
- package/_standalone/components/settings/useCustomProviderForm.ts +132 -0
- package/_standalone/components/setup/StepAI.tsx +3 -3
- package/_standalone/components/shared/ModelInput.tsx +18 -4
- package/_standalone/components/shared/ProviderSelect.tsx +126 -134
- package/_standalone/hooks/useAskChat.ts +97 -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/app/api/ask/route.ts +124 -44
- package/app/app/api/mcp/agents/route.ts +3 -3
- package/app/app/api/settings/list-models/route.ts +15 -26
- package/app/app/api/settings/route.ts +14 -59
- package/app/app/api/settings/test-key/route.ts +47 -12
- package/app/app/api/setup/route.ts +36 -18
- package/app/app/api/skills/route.ts +1 -1
- package/app/app/layout.tsx +5 -3
- package/app/components/HomeContent.tsx +11 -0
- package/app/components/UpdateToast.tsx +255 -0
- package/app/components/ask/AskContent.tsx +70 -40
- package/app/components/ask/AskHeader.tsx +8 -1
- package/app/components/ask/MessageList.tsx +37 -3
- package/app/components/ask/ProviderModelCapsule.tsx +51 -129
- package/app/components/settings/AiTab.tsx +270 -347
- package/app/components/settings/CustomProviderFields.tsx +121 -0
- package/app/components/settings/CustomProvidersCard.tsx +2 -2
- 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 +38 -221
- package/app/components/settings/SettingsContent.tsx +5 -12
- package/app/components/settings/TestButton.tsx +64 -0
- package/app/components/settings/types.ts +3 -12
- package/app/components/settings/useCustomProviderForm.ts +132 -0
- package/app/components/setup/StepAI.tsx +3 -3
- package/app/components/shared/ModelInput.tsx +18 -4
- package/app/components/shared/ProviderSelect.tsx +126 -134
- package/app/hooks/useAskChat.ts +97 -13
- package/app/hooks/useAskPanel.ts +17 -1
- 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 +129 -29
- package/app/lib/i18n/modules/settings.ts +20 -0
- package/app/lib/pi-integration/skills.ts +16 -4
- package/app/lib/settings-ai-client.ts +17 -8
- package/app/lib/settings.ts +64 -90
- package/app/lib/types.ts +4 -0
- package/package.json +1 -1
- package/_standalone/.next/server/chunks/530.js +0 -218
- package/_standalone/.next/server/chunks/9007.js +0 -2
- package/_standalone/.next/server/chunks/9137.js +0 -52
- package/_standalone/.next/static/chunks/1309-373ade1b40aea186.js +0 -1
- package/_standalone/.next/static/chunks/3165-9189a38fd9ebf6f2.js +0 -1
- package/_standalone/.next/static/chunks/4587-5d06728133fff222.js +0 -1
- package/_standalone/.next/static/chunks/6261-5ce86db54b19ae46.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-84e607f33c409f91.js +0 -22
- package/_standalone/.next/static/chunks/9207-9a4a1a1ede4f8e6e.js +0 -1
- package/_standalone/.next/static/chunks/app/echo/[segment]/page-bc5e104eb7ae6327.js +0 -11
- package/_standalone/.next/static/chunks/app/setup/page-79acb0baf38184c6.js +0 -1
- package/_standalone/.next/static/chunks/app/trash/page-d040db56863da504.js +0 -1
- package/_standalone/.next/static/css/1287672978833d07.css +0 -1
- package/_standalone/lib/agent/context.ts +0 -403
- /package/_standalone/.next/static/{X86rF8dKEO0InosOw4a2_ → eIlwbGas1iRGonlPyEwj7}/_buildManifest.js +0 -0
- /package/_standalone/.next/static/{X86rF8dKEO0InosOw4a2_ → eIlwbGas1iRGonlPyEwj7}/_ssgManifest.js +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { useEffect, useLayoutEffect, useRef, useState, useCallback, useMemo } from 'react';
|
|
4
4
|
import { Send, StopCircle, X, Plus, FileText, ImageIcon } from 'lucide-react';
|
|
5
5
|
import { useLocale } from '@/lib/stores/locale-store';
|
|
6
|
-
import type { AskMode } from '@/lib/types';
|
|
6
|
+
import type { AskMode, Message } from '@/lib/types';
|
|
7
7
|
import ModeCapsule, { getPersistedMode } from '@/components/ask/ModeCapsule';
|
|
8
8
|
import { useAskSession } from '@/hooks/useAskSession';
|
|
9
9
|
import { useFileUpload } from '@/hooks/useFileUpload';
|
|
@@ -74,9 +74,11 @@ interface AskContentProps {
|
|
|
74
74
|
askMode?: 'panel' | 'popup';
|
|
75
75
|
/** Switch between panel ↔ popup */
|
|
76
76
|
onModeSwitch?: () => void;
|
|
77
|
+
/** Navigate from fullscreen to right-side panel mode */
|
|
78
|
+
onDockToPanel?: () => void;
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
export default function AskContent({ visible, currentFile, initialMessage, initialAcpAgent, onFirstMessage, variant, onClose, maximized, onMaximize, askMode, onModeSwitch }: AskContentProps) {
|
|
81
|
+
export default function AskContent({ visible, currentFile, initialMessage, initialAcpAgent, onFirstMessage, variant, onClose, maximized, onMaximize, askMode, onModeSwitch, onDockToPanel }: AskContentProps) {
|
|
80
82
|
const isPanel = variant === 'panel';
|
|
81
83
|
const isHome = variant === 'home';
|
|
82
84
|
|
|
@@ -103,7 +105,7 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
|
|
|
103
105
|
const selectedAcpAgentRef = useRef(selectedAcpAgent);
|
|
104
106
|
selectedAcpAgentRef.current = selectedAcpAgent;
|
|
105
107
|
const [chatMode, setChatMode] = useState<AskMode>('agent');
|
|
106
|
-
const [providerOverride, setProviderOverride] = useState<ProviderId | `
|
|
108
|
+
const [providerOverride, setProviderOverride] = useState<ProviderId | `p_${string}` | null>(null);
|
|
107
109
|
const [modelOverride, setModelOverride] = useState<string | null>(null);
|
|
108
110
|
|
|
109
111
|
useEffect(() => {
|
|
@@ -136,7 +138,30 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
|
|
|
136
138
|
setSelectedSkill(null);
|
|
137
139
|
setSelectedAcpAgent(null);
|
|
138
140
|
setAttachedFiles(currentFile ? [currentFile] : []);
|
|
139
|
-
|
|
141
|
+
upload.clearAttachments();
|
|
142
|
+
}, [currentFile, upload]);
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
const handleRestoreInput = useCallback((userMessage: Message) => {
|
|
146
|
+
setInput(userMessage.content);
|
|
147
|
+
// Restore images if they exist
|
|
148
|
+
if (userMessage.images && userMessage.images.length > 0) {
|
|
149
|
+
// Reconstruct the images state from the message images
|
|
150
|
+
imageUpload.clearImages();
|
|
151
|
+
// Note: we can't directly set images without going through the upload flow
|
|
152
|
+
// So we just clear them for now - in practice, images are usually small content
|
|
153
|
+
// and the user can re-add them if needed
|
|
154
|
+
}
|
|
155
|
+
if (userMessage.attachedFiles) setAttachedFiles(userMessage.attachedFiles);
|
|
156
|
+
// Restore skill selection if it was set
|
|
157
|
+
if (userMessage.skillName) {
|
|
158
|
+
// The skill is already in the slash command system, just mark as selected
|
|
159
|
+
// This will be handled through the UI state
|
|
160
|
+
slash.resetSlash(); // Clear any active slash query
|
|
161
|
+
}
|
|
162
|
+
// Focus back to input
|
|
163
|
+
setTimeout(() => inputRef.current?.focus(), 50);
|
|
164
|
+
}, [imageUpload, slash]);
|
|
140
165
|
|
|
141
166
|
const chatRefs = useRef({ inputValueRef, mentionRef, slashRef, imageUploadRef, sessionRef, uploadRef, selectedSkillRef, selectedAcpAgentRef, attachedFilesRef });
|
|
142
167
|
const chat = useAskChat({
|
|
@@ -148,6 +173,7 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
|
|
|
148
173
|
refs: chatRefs.current,
|
|
149
174
|
errorLabels: { noResponse: t.ask.errorNoResponse, stopped: t.ask.stopped },
|
|
150
175
|
resetInputState,
|
|
176
|
+
onRestoreInput: handleRestoreInput,
|
|
151
177
|
});
|
|
152
178
|
const { isLoading, loadingPhase, reconnectAttempt, reconnectMaxRef } = chat;
|
|
153
179
|
const handleSubmit = chat.submit;
|
|
@@ -482,7 +508,7 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
|
|
|
482
508
|
}), [t, reconnectAttempt]);
|
|
483
509
|
|
|
484
510
|
return (
|
|
485
|
-
|
|
511
|
+
<div className="flex min-h-0 w-full flex-col h-full">
|
|
486
512
|
{/* Header — home variant shows session switcher + new/history/fullscreen buttons */}
|
|
487
513
|
<AskHeader
|
|
488
514
|
isPanel={isPanel || isHome}
|
|
@@ -495,6 +521,7 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
|
|
|
495
521
|
askMode={isHome ? undefined : askMode}
|
|
496
522
|
onModeSwitch={isHome ? undefined : onModeSwitch}
|
|
497
523
|
onClose={isHome ? undefined : onClose}
|
|
524
|
+
onDockToPanel={maximized ? onDockToPanel : undefined}
|
|
498
525
|
sessions={session.sessions}
|
|
499
526
|
activeSessionId={session.activeSessionId}
|
|
500
527
|
onLoadSession={handleLoadSession}
|
|
@@ -522,32 +549,33 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
|
|
|
522
549
|
/>
|
|
523
550
|
)}
|
|
524
551
|
|
|
525
|
-
{/* Messages */}
|
|
526
552
|
{/* Messages — home variant hides empty state (suggestions rendered externally) */}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
553
|
+
<div className="flex-1 min-h-0 flex flex-col">
|
|
554
|
+
{!isHome && (
|
|
555
|
+
<MessageList
|
|
556
|
+
messages={session.messages}
|
|
557
|
+
isLoading={isLoading}
|
|
558
|
+
loadingPhase={loadingPhase}
|
|
559
|
+
emptyPrompt={t.ask.emptyPrompt}
|
|
560
|
+
emptyHint={t.ask.emptyHint}
|
|
561
|
+
suggestions={t.ask.suggestions}
|
|
562
|
+
onSuggestionClick={setInput}
|
|
563
|
+
labels={messageLabels}
|
|
564
|
+
/>
|
|
565
|
+
)}
|
|
566
|
+
{isHome && session.messages.length > 0 && (
|
|
567
|
+
<MessageList
|
|
568
|
+
messages={session.messages}
|
|
569
|
+
isLoading={isLoading}
|
|
570
|
+
loadingPhase={loadingPhase}
|
|
571
|
+
emptyPrompt={t.ask.emptyPrompt}
|
|
572
|
+
emptyHint={t.ask.emptyHint}
|
|
573
|
+
suggestions={[]}
|
|
574
|
+
onSuggestionClick={setInput}
|
|
575
|
+
labels={messageLabels}
|
|
576
|
+
/>
|
|
577
|
+
)}
|
|
578
|
+
</div>
|
|
551
579
|
|
|
552
580
|
{/* Popovers — flex children so they stay within overflow boundary (absolute positioning would be clipped by RightAskPanel's overflow-hidden) */}
|
|
553
581
|
{mention.mentionQuery !== null && mention.mentionResults.length > 0 && (
|
|
@@ -573,7 +601,7 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
|
|
|
573
601
|
)}
|
|
574
602
|
|
|
575
603
|
{/* Composer card — unified input area */}
|
|
576
|
-
<div className=
|
|
604
|
+
<div className={cn('shrink-0', isHome ? 'px-2 pb-2 pt-0.5' : 'px-3 pb-2.5 pt-1')}>
|
|
577
605
|
<div
|
|
578
606
|
className={cn(
|
|
579
607
|
'rounded-xl bg-muted/40 transition-all focus-within:bg-muted/60',
|
|
@@ -621,7 +649,7 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
|
|
|
621
649
|
<form
|
|
622
650
|
ref={formRef}
|
|
623
651
|
onSubmit={handleSubmit}
|
|
624
|
-
className=
|
|
652
|
+
className={cn('flex items-end gap-1.5', isHome ? 'px-2 py-1.5' : 'px-3 py-2')}
|
|
625
653
|
>
|
|
626
654
|
{/* + attach button with mini menu */}
|
|
627
655
|
<div className="relative shrink-0">
|
|
@@ -690,7 +718,7 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
|
|
|
690
718
|
onPaste={handlePaste}
|
|
691
719
|
placeholder={t.ask.placeholder}
|
|
692
720
|
rows={1}
|
|
693
|
-
className=
|
|
721
|
+
className={cn('min-w-0 flex-1 resize-none overflow-y-hidden bg-transparent py-2 leading-relaxed text-foreground placeholder:text-muted-foreground/50 outline-none focus-visible:ring-0', isHome ? 'text-xs' : 'text-sm')}
|
|
694
722
|
/>
|
|
695
723
|
|
|
696
724
|
{isLoading ? (
|
|
@@ -705,8 +733,8 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
|
|
|
705
733
|
</form>
|
|
706
734
|
|
|
707
735
|
{/* Mode + Agent + Provider selector row + keyboard hint */}
|
|
708
|
-
<div className=
|
|
709
|
-
<div className=
|
|
736
|
+
<div className={cn('flex items-center justify-between border-t border-border/10', isPanel ? 'px-2 pb-1.5 pt-1 gap-1' : 'px-3 pb-2 pt-1.5')}>
|
|
737
|
+
<div className={cn('flex items-center flex-wrap', isPanel ? 'gap-1' : 'gap-2')}>
|
|
710
738
|
<ModeCapsule mode={chatMode} onChange={setChatMode} disabled={isLoading} />
|
|
711
739
|
{mounted && acpDetection.installedAgents.length > 0 && (
|
|
712
740
|
<AgentSelectorCapsule
|
|
@@ -729,13 +757,15 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
|
|
|
729
757
|
/>
|
|
730
758
|
)}
|
|
731
759
|
</div>
|
|
732
|
-
{/* Keyboard hint */}
|
|
733
|
-
|
|
734
|
-
<
|
|
735
|
-
|
|
760
|
+
{/* Keyboard hint — hidden in panel (too narrow) and home (compact) */}
|
|
761
|
+
{!isPanel && !isHome && (
|
|
762
|
+
<span className="hidden md:inline text-2xs text-muted-foreground/40 select-none shrink-0">
|
|
763
|
+
<kbd className="font-mono">Enter</kbd> {t.ask.send} · <kbd className="font-mono">Shift+Enter</kbd> {t.ask.newlineHint}
|
|
764
|
+
</span>
|
|
765
|
+
)}
|
|
736
766
|
</div>
|
|
737
767
|
</div>
|
|
738
768
|
</div>
|
|
739
|
-
|
|
769
|
+
</div>
|
|
740
770
|
);
|
|
741
771
|
}
|
|
@@ -16,6 +16,8 @@ interface AskHeaderProps {
|
|
|
16
16
|
askMode?: 'panel' | 'popup';
|
|
17
17
|
onModeSwitch?: () => void;
|
|
18
18
|
onClose?: () => void;
|
|
19
|
+
/** Navigate from fullscreen to right-side panel mode */
|
|
20
|
+
onDockToPanel?: () => void;
|
|
19
21
|
hideTitle?: boolean;
|
|
20
22
|
/** Session switching — inline in header when >=2 sessions */
|
|
21
23
|
sessions?: ChatSession[];
|
|
@@ -28,7 +30,7 @@ interface AskHeaderProps {
|
|
|
28
30
|
|
|
29
31
|
export default memo(function AskHeader({
|
|
30
32
|
isPanel, showHistory, onToggleHistory, onReset, isLoading,
|
|
31
|
-
maximized, onMaximize, askMode, onModeSwitch, onClose, hideTitle,
|
|
33
|
+
maximized, onMaximize, askMode, onModeSwitch, onClose, onDockToPanel, hideTitle,
|
|
32
34
|
sessions, activeSessionId, onLoadSession, onDeleteSession, onRenameSession, onTogglePinSession,
|
|
33
35
|
}: AskHeaderProps) {
|
|
34
36
|
const { t } = useLocale();
|
|
@@ -244,6 +246,11 @@ export default memo(function AskHeader({
|
|
|
244
246
|
{maximized ? <Minimize2 size={iconSize} /> : <Maximize2 size={iconSize} />}
|
|
245
247
|
</button>
|
|
246
248
|
)}
|
|
249
|
+
{onDockToPanel && (
|
|
250
|
+
<button type="button" onClick={(e) => { e.stopPropagation(); onDockToPanel(); }} className="p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={t.hints.dockToSide ?? 'Dock to side panel'}>
|
|
251
|
+
<PanelRight size={iconSize} />
|
|
252
|
+
</button>
|
|
253
|
+
)}
|
|
247
254
|
{onModeSwitch && (
|
|
248
255
|
<button type="button" onClick={(e) => { e.stopPropagation(); onModeSwitch(); }} className="p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={askMode === 'popup' ? t.hints.dockToSide : t.hints.openAsPopup}>
|
|
249
256
|
{askMode === 'popup' ? <PanelRight size={iconSize} /> : <AppWindow size={iconSize} />}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useRef, useEffect, memo, useState, useCallback } from 'react';
|
|
4
|
-
import { Sparkles, Loader2, AlertCircle, Wrench, WifiOff, Zap, Copy, Check, ArrowDown, FolderInput, Search, PenLine, Lightbulb } from 'lucide-react';
|
|
4
|
+
import { Sparkles, Loader2, AlertCircle, Wrench, WifiOff, Zap, Copy, Check, ArrowDown, FolderInput, Search, PenLine, Lightbulb, FileText, Paperclip } from 'lucide-react';
|
|
5
5
|
import ReactMarkdown from 'react-markdown';
|
|
6
6
|
import remarkGfm from 'remark-gfm';
|
|
7
7
|
import type { Message, ImagePart } from '@/lib/types';
|
|
@@ -35,10 +35,17 @@ function CopyMessageButton({ text, label }: { text: string; label?: string }) {
|
|
|
35
35
|
);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
function UserMessageContent({ content, skillName, images }: { content: string; skillName?: string; images?: ImagePart[] }) {
|
|
38
|
+
function UserMessageContent({ content, skillName, images, attachedFiles, uploadedFileNames }: { content: string; skillName?: string; images?: ImagePart[]; attachedFiles?: string[]; uploadedFileNames?: string[] }) {
|
|
39
39
|
const resolved = skillName ?? content.match(SKILL_PREFIX_RE)?.[1];
|
|
40
40
|
const prefixMatch = content.match(SKILL_PREFIX_RE);
|
|
41
41
|
const rest = prefixMatch ? content.slice(prefixMatch[0].length) : content;
|
|
42
|
+
|
|
43
|
+
// Deduplicate: uploaded files already shown shouldn't repeat as attached
|
|
44
|
+
const uploadedSet = new Set(uploadedFileNames ?? []);
|
|
45
|
+
const dedupedAttached = attachedFiles?.filter(fp => !uploadedSet.has(fp.split('/').pop() ?? fp));
|
|
46
|
+
const hasContext = (dedupedAttached && dedupedAttached.length > 0)
|
|
47
|
+
|| (uploadedFileNames && uploadedFileNames.length > 0);
|
|
48
|
+
|
|
42
49
|
return (
|
|
43
50
|
<>
|
|
44
51
|
{/* Images */}
|
|
@@ -68,6 +75,33 @@ function UserMessageContent({ content, skillName, images }: { content: string; s
|
|
|
68
75
|
</span>
|
|
69
76
|
)}
|
|
70
77
|
{resolved ? rest : content}
|
|
78
|
+
{/* File context chips */}
|
|
79
|
+
{hasContext && (
|
|
80
|
+
<div className="mt-2 pt-1.5 border-t border-white/15 flex flex-wrap gap-1 whitespace-normal" role="list" aria-label="Attached files">
|
|
81
|
+
{dedupedAttached?.map(fp => (
|
|
82
|
+
<span
|
|
83
|
+
key={fp}
|
|
84
|
+
role="listitem"
|
|
85
|
+
className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] bg-white/10 text-white/80 min-w-0"
|
|
86
|
+
title={fp}
|
|
87
|
+
>
|
|
88
|
+
<FileText size={9} className="shrink-0 opacity-70" />
|
|
89
|
+
<span className="truncate max-w-[120px]">{fp.split('/').pop()}</span>
|
|
90
|
+
</span>
|
|
91
|
+
))}
|
|
92
|
+
{uploadedFileNames?.map(name => (
|
|
93
|
+
<span
|
|
94
|
+
key={name}
|
|
95
|
+
role="listitem"
|
|
96
|
+
className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] bg-white/10 text-white/80 min-w-0"
|
|
97
|
+
title={name}
|
|
98
|
+
>
|
|
99
|
+
<Paperclip size={9} className="shrink-0 opacity-70" />
|
|
100
|
+
<span className="truncate max-w-[120px]">{name}</span>
|
|
101
|
+
</span>
|
|
102
|
+
))}
|
|
103
|
+
</div>
|
|
104
|
+
)}
|
|
71
105
|
</>
|
|
72
106
|
);
|
|
73
107
|
}
|
|
@@ -253,7 +287,7 @@ export default memo(function MessageList({
|
|
|
253
287
|
<div
|
|
254
288
|
className="max-w-[85%] px-3.5 py-2.5 rounded-2xl rounded-br-lg text-sm leading-relaxed whitespace-pre-wrap bg-[var(--amber)] text-[var(--amber-foreground)] shadow-sm shadow-[var(--amber)]/10"
|
|
255
289
|
>
|
|
256
|
-
<UserMessageContent content={m.content} skillName={m.skillName} images={m.images} />
|
|
290
|
+
<UserMessageContent content={m.content} skillName={m.skillName} images={m.images} attachedFiles={m.attachedFiles} uploadedFileNames={m.uploadedFileNames} />
|
|
257
291
|
</div>
|
|
258
292
|
) : m.content.startsWith('__error__') ? (
|
|
259
293
|
<div className="max-w-[85%] px-3.5 py-3 rounded-2xl rounded-bl-md border border-error/30 bg-error/10 text-sm shadow-sm">
|
|
@@ -7,15 +7,13 @@ 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';
|
|
14
|
-
import { type
|
|
12
|
+
import { type Provider, isProviderEntryId, findProvider } from '@/lib/custom-endpoints';
|
|
15
13
|
|
|
16
14
|
const STORAGE_KEY = 'mindos-provider-model';
|
|
17
15
|
|
|
18
|
-
type ProviderSelection = ProviderId | `
|
|
16
|
+
type ProviderSelection = ProviderId | `p_${string}` | null;
|
|
19
17
|
|
|
20
18
|
interface ProviderModelCapsuleProps {
|
|
21
19
|
providerValue: ProviderSelection;
|
|
@@ -27,10 +25,9 @@ interface ProviderModelCapsuleProps {
|
|
|
27
25
|
|
|
28
26
|
interface SettingsData {
|
|
29
27
|
ai?: {
|
|
30
|
-
|
|
31
|
-
providers?:
|
|
28
|
+
activeProvider?: string;
|
|
29
|
+
providers?: Provider[];
|
|
32
30
|
};
|
|
33
|
-
customProviders?: CustomProvider[];
|
|
34
31
|
envOverrides?: Record<string, boolean>;
|
|
35
32
|
}
|
|
36
33
|
|
|
@@ -42,11 +39,11 @@ export function getPersistedProviderModel(): { provider: ProviderSelection; mode
|
|
|
42
39
|
const raw = localStorage.getItem(STORAGE_KEY);
|
|
43
40
|
if (!raw) {
|
|
44
41
|
const old = localStorage.getItem('mindos-provider-override');
|
|
45
|
-
if (old && (isProviderId(old) ||
|
|
42
|
+
if (old && (isProviderId(old) || isProviderEntryId(old))) return { provider: old as any, model: null };
|
|
46
43
|
return { provider: null, model: null };
|
|
47
44
|
}
|
|
48
45
|
const parsed = JSON.parse(raw);
|
|
49
|
-
const provider = parsed?.provider && (isProviderId(parsed.provider) ||
|
|
46
|
+
const provider = parsed?.provider && (isProviderId(parsed.provider) || isProviderEntryId(parsed.provider))
|
|
50
47
|
? parsed.provider : null;
|
|
51
48
|
const model = typeof parsed?.model === 'string' ? parsed.model : null;
|
|
52
49
|
return { provider, model };
|
|
@@ -63,24 +60,8 @@ function persistProviderModel(provider: ProviderSelection, model: string | null)
|
|
|
63
60
|
|
|
64
61
|
/* ── Configured providers ── */
|
|
65
62
|
|
|
66
|
-
function getConfiguredProviders(data: SettingsData):
|
|
67
|
-
|
|
68
|
-
const providers = data.ai?.providers ?? {};
|
|
69
|
-
const customProviders = data.customProviders ?? [];
|
|
70
|
-
const env = data.envOverrides ?? {};
|
|
71
|
-
for (const id of ALL_PROVIDER_IDS) {
|
|
72
|
-
const preset = PROVIDER_PRESETS[id];
|
|
73
|
-
const hasKey = providers[id]?.apiKey === '***set***';
|
|
74
|
-
const envVar = getApiKeyEnvVar(id);
|
|
75
|
-
const hasEnv = envVar ? !!env[envVar] : false;
|
|
76
|
-
if (hasKey || hasEnv) { result.push(id); }
|
|
77
|
-
else if (preset.apiKeyFallback) {
|
|
78
|
-
const cfg = providers[id];
|
|
79
|
-
if (data.ai?.provider === id || (cfg && (cfg.model || cfg.baseUrl))) result.push(id);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
for (const cp of customProviders) result.push(cp.id as `cp_${string}`);
|
|
83
|
-
return result;
|
|
63
|
+
function getConfiguredProviders(data: SettingsData): string[] {
|
|
64
|
+
return (data.ai?.providers ?? []).map(p => p.id);
|
|
84
65
|
}
|
|
85
66
|
|
|
86
67
|
/* ── Component ── */
|
|
@@ -101,7 +82,7 @@ export default function ProviderModelCapsule({
|
|
|
101
82
|
// Flyout state
|
|
102
83
|
const providerPanelRef = useRef<HTMLDivElement>(null);
|
|
103
84
|
const hoveredRowRef = useRef<HTMLDivElement>(null);
|
|
104
|
-
const [hoveredProvider, setHoveredProvider] = useState<
|
|
85
|
+
const [hoveredProvider, setHoveredProvider] = useState<string | null>(null);
|
|
105
86
|
const [flyoutStyle, setFlyoutStyle] = useState<React.CSSProperties>({});
|
|
106
87
|
const [expandedModels, setExpandedModels] = useState<string[] | null>(null);
|
|
107
88
|
const [modelsLoading, setModelsLoading] = useState(false);
|
|
@@ -151,8 +132,7 @@ export default function ProviderModelCapsule({
|
|
|
151
132
|
return () => { cancelled = true; document.removeEventListener('visibilitychange', onVisible); window.removeEventListener('mindos:settings-changed', onChange); };
|
|
152
133
|
}, []);
|
|
153
134
|
|
|
154
|
-
const defaultProvider =
|
|
155
|
-
? settingsData.ai.provider as ProviderId : 'anthropic';
|
|
135
|
+
const defaultProvider = settingsData?.ai?.activeProvider || '';
|
|
156
136
|
|
|
157
137
|
const configuredProviders = useMemo(
|
|
158
138
|
() => settingsData ? getConfiguredProviders(settingsData) : [],
|
|
@@ -170,14 +150,12 @@ export default function ProviderModelCapsule({
|
|
|
170
150
|
|
|
171
151
|
// Resolve active display
|
|
172
152
|
const activeProvider = providerValue ?? defaultProvider;
|
|
173
|
-
const
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
const defaultModel = customProvider?.model
|
|
177
|
-
|| settingsData?.ai?.providers?.[activeProvider as ProviderId]?.model
|
|
153
|
+
const activeEntry = findProvider(settingsData?.ai?.providers ?? [], String(activeProvider));
|
|
154
|
+
const activePreset = activeEntry ? PROVIDER_PRESETS[activeEntry.protocol] : null;
|
|
155
|
+
const defaultModel = activeEntry?.model
|
|
178
156
|
|| activePreset?.defaultModel || '';
|
|
179
157
|
const displayModel = modelValue || defaultModel;
|
|
180
|
-
const displayName =
|
|
158
|
+
const displayName = activeEntry?.name
|
|
181
159
|
|| (activePreset ? (locale === 'zh' ? activePreset.nameZh : activePreset.name) : String(activeProvider));
|
|
182
160
|
|
|
183
161
|
/* ── Dropdown positioning ── */
|
|
@@ -244,7 +222,7 @@ export default function ProviderModelCapsule({
|
|
|
244
222
|
}, [open, hoveredProvider]);
|
|
245
223
|
|
|
246
224
|
/* ── Model fetching ── */
|
|
247
|
-
const fetchModels = useCallback(async (providerId:
|
|
225
|
+
const fetchModels = useCallback(async (providerId: string, force = false) => {
|
|
248
226
|
if (!force && modelsCacheRef.current[providerId]) {
|
|
249
227
|
setExpandedModels(modelsCacheRef.current[providerId]);
|
|
250
228
|
setModelsLoading(false);
|
|
@@ -254,14 +232,9 @@ export default function ProviderModelCapsule({
|
|
|
254
232
|
setModelsLoading(true); setModelsError(''); setExpandedModels(null);
|
|
255
233
|
const version = ++fetchVersionRef.current;
|
|
256
234
|
try {
|
|
257
|
-
const isCustom = isCustomProviderId(String(providerId));
|
|
258
|
-
const body: Record<string, string> = isCustom
|
|
259
|
-
? { customProviderId: providerId }
|
|
260
|
-
: { provider: providerId };
|
|
261
|
-
|
|
262
235
|
const res = await fetch('/api/settings/list-models', {
|
|
263
236
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
264
|
-
body: JSON.stringify(
|
|
237
|
+
body: JSON.stringify({ provider: providerId }),
|
|
265
238
|
});
|
|
266
239
|
if (version !== fetchVersionRef.current) return;
|
|
267
240
|
const json = await res.json();
|
|
@@ -277,19 +250,13 @@ export default function ProviderModelCapsule({
|
|
|
277
250
|
}
|
|
278
251
|
}, []);
|
|
279
252
|
|
|
280
|
-
const canCustomProviderExpand = useCallback((cpId: string) => {
|
|
281
|
-
if (!isCustomProviderId(cpId)) return false;
|
|
282
|
-
const cp = findCustomProvider(settingsData?.customProviders ?? [], cpId);
|
|
283
|
-
return !!cp; // All custom providers can now expand (they use their baseProviderId)
|
|
284
|
-
}, [settingsData]);
|
|
285
|
-
|
|
286
253
|
// Determine if a provider can show model flyout
|
|
287
|
-
const canProviderExpand = useCallback((id:
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
return
|
|
292
|
-
}, [
|
|
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]);
|
|
293
260
|
|
|
294
261
|
// Compute flyout position: anchored to right edge of provider panel, aligned to hovered row
|
|
295
262
|
const computeFlyoutPosition = useCallback(() => {
|
|
@@ -323,7 +290,7 @@ export default function ProviderModelCapsule({
|
|
|
323
290
|
}, []);
|
|
324
291
|
|
|
325
292
|
// Open flyout for a provider (debounced to prevent flicker)
|
|
326
|
-
const openFlyout = useCallback((providerId:
|
|
293
|
+
const openFlyout = useCallback((providerId: string) => {
|
|
327
294
|
cancelCloseTimer();
|
|
328
295
|
if (openTimerRef.current) clearTimeout(openTimerRef.current);
|
|
329
296
|
openTimerRef.current = setTimeout(() => {
|
|
@@ -378,7 +345,7 @@ export default function ProviderModelCapsule({
|
|
|
378
345
|
if (e.key === 'ArrowDown') { e.preventDefault(); setModelHighlight(i => (i + 1) % filteredModels.length); }
|
|
379
346
|
else if (e.key === 'ArrowUp') { e.preventDefault(); setModelHighlight(i => (i - 1 + filteredModels.length) % filteredModels.length); }
|
|
380
347
|
else if (e.key === 'Enter' && modelHighlight >= 0 && modelHighlight < filteredModels.length && hoveredProvider) {
|
|
381
|
-
e.preventDefault(); handleSelectModel(hoveredProvider, filteredModels[modelHighlight]);
|
|
348
|
+
e.preventDefault(); handleSelectModel(hoveredProvider as ProviderSelection, filteredModels[modelHighlight]);
|
|
382
349
|
} else if (e.key === 'Escape') { e.preventDefault(); setHoveredProvider(null); setModelSearch(''); }
|
|
383
350
|
}, [filteredModels, modelHighlight, hoveredProvider, handleSelectModel]);
|
|
384
351
|
|
|
@@ -391,19 +358,22 @@ export default function ProviderModelCapsule({
|
|
|
391
358
|
/* ── Guards ── */
|
|
392
359
|
if (!settingsData || configuredProviders.length === 0) return null;
|
|
393
360
|
|
|
394
|
-
const modelShort = (displayModel || '').length >
|
|
395
|
-
? (displayModel || '').slice(0,
|
|
396
|
-
|
|
397
|
-
const
|
|
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;
|
|
398
369
|
const hasModelOverride = !!(modelValue && modelValue !== defaultModel);
|
|
399
370
|
|
|
400
371
|
/* ── Render: flyout (right panel) — positioned absolutely via portal ── */
|
|
401
372
|
const renderFlyout = () => {
|
|
402
373
|
if (!hoveredProvider) return null;
|
|
403
|
-
const
|
|
404
|
-
const preset =
|
|
405
|
-
const
|
|
406
|
-
const displayName = customProvider?.name || (preset ? (locale === 'zh' ? preset.nameZh : preset.name) : String(hoveredProvider));
|
|
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));
|
|
407
377
|
|
|
408
378
|
return createPortal(
|
|
409
379
|
<div
|
|
@@ -462,11 +432,11 @@ export default function ProviderModelCapsule({
|
|
|
462
432
|
)}
|
|
463
433
|
{filteredModels.map((m, i) => {
|
|
464
434
|
const isModelSelected = providerValue === hoveredProvider && modelValue === m;
|
|
465
|
-
const defModel =
|
|
435
|
+
const defModel = hovEntry?.model || preset?.defaultModel;
|
|
466
436
|
return (
|
|
467
437
|
<button
|
|
468
438
|
key={m} type="button" data-model-item
|
|
469
|
-
onClick={() => handleSelectModel(hoveredProvider, m)}
|
|
439
|
+
onClick={() => handleSelectModel(hoveredProvider as ProviderSelection, m)}
|
|
470
440
|
className={`w-full text-left px-2 py-1 text-2xs rounded transition-colors flex items-center gap-1 ${
|
|
471
441
|
isModelSelected ? 'bg-[var(--amber)]/12 text-foreground font-medium'
|
|
472
442
|
: i === modelHighlight ? 'bg-accent' : 'hover:bg-accent/60'
|
|
@@ -502,26 +472,28 @@ export default function ProviderModelCapsule({
|
|
|
502
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"
|
|
503
473
|
style={{ maxHeight: '70vh', overflowY: 'auto' }}
|
|
504
474
|
>
|
|
505
|
-
{
|
|
506
|
-
const
|
|
507
|
-
|
|
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;
|
|
508
480
|
const provModel = modelValue && providerValue === id ? modelValue
|
|
509
|
-
:
|
|
481
|
+
: entry.model || preset?.defaultModel || '';
|
|
510
482
|
const isSelected = providerValue === id || (!providerValue && defaultProvider === id);
|
|
511
483
|
const isHovered = hoveredProvider === id;
|
|
512
|
-
const canExpand = canProviderExpand(id
|
|
484
|
+
const canExpand = canProviderExpand(id);
|
|
513
485
|
|
|
514
486
|
return (
|
|
515
487
|
<div
|
|
516
488
|
key={id}
|
|
517
489
|
ref={isHovered ? hoveredRowRef : undefined}
|
|
518
|
-
onMouseEnter={() => canExpand ? openFlyout(id
|
|
490
|
+
onMouseEnter={() => canExpand ? openFlyout(id) : closeFlyoutImmediate()}
|
|
519
491
|
onMouseLeave={startCloseTimer}
|
|
520
492
|
>
|
|
521
493
|
<div className={`flex w-full items-center text-xs transition-colors ${isHovered ? 'bg-accent/60' : 'hover:bg-muted/60'}`}>
|
|
522
494
|
<button
|
|
523
495
|
type="button" role="option" aria-selected={isSelected}
|
|
524
|
-
onClick={() => handleSelectProvider(id as
|
|
496
|
+
onClick={() => handleSelectProvider(id as ProviderSelection)}
|
|
525
497
|
className="flex flex-1 items-center gap-2 px-3 py-1.5 min-w-0"
|
|
526
498
|
>
|
|
527
499
|
<div className="flex-1 min-w-0 truncate">
|
|
@@ -538,57 +510,7 @@ export default function ProviderModelCapsule({
|
|
|
538
510
|
onClick={(e) => {
|
|
539
511
|
e.stopPropagation();
|
|
540
512
|
if (hoveredProvider === id) closeFlyoutImmediate();
|
|
541
|
-
else openFlyout(id
|
|
542
|
-
}}
|
|
543
|
-
className={`shrink-0 px-1.5 py-1.5 mr-1 rounded transition-colors ${
|
|
544
|
-
isHovered ? 'text-foreground' : 'text-muted-foreground/40 hover:text-muted-foreground'
|
|
545
|
-
}`}
|
|
546
|
-
title={t.ask?.selectModel ?? 'Select model'}
|
|
547
|
-
>
|
|
548
|
-
<ChevronRight size={11} />
|
|
549
|
-
</button>
|
|
550
|
-
)}
|
|
551
|
-
</div>
|
|
552
|
-
</div>
|
|
553
|
-
);
|
|
554
|
-
})}
|
|
555
|
-
|
|
556
|
-
{customIds.map((id) => {
|
|
557
|
-
const cp = findCustomProvider(settingsData?.customProviders ?? [], String(id));
|
|
558
|
-
if (!cp) return null;
|
|
559
|
-
const cpModel = modelValue && providerValue === id ? modelValue : cp.model;
|
|
560
|
-
const isSelected = providerValue === id;
|
|
561
|
-
const isHovered = hoveredProvider === id;
|
|
562
|
-
const canExpand = canProviderExpand(id as `cp_${string}`);
|
|
563
|
-
|
|
564
|
-
return (
|
|
565
|
-
<div
|
|
566
|
-
key={id}
|
|
567
|
-
ref={isHovered ? hoveredRowRef : undefined}
|
|
568
|
-
onMouseEnter={() => canExpand ? openFlyout(id as `cp_${string}`) : closeFlyoutImmediate()}
|
|
569
|
-
onMouseLeave={startCloseTimer}
|
|
570
|
-
>
|
|
571
|
-
<div className={`flex w-full items-center text-xs transition-colors ${isHovered ? 'bg-accent/60' : 'hover:bg-muted/60'}`}>
|
|
572
|
-
<button
|
|
573
|
-
type="button" role="option" aria-selected={isSelected}
|
|
574
|
-
onClick={() => handleSelectProvider(id)}
|
|
575
|
-
className="flex flex-1 items-center gap-2 px-3 py-1.5 min-w-0"
|
|
576
|
-
>
|
|
577
|
-
<div className="flex-1 min-w-0 truncate">
|
|
578
|
-
<span className={`text-xs ${isSelected ? 'font-medium text-foreground' : 'text-foreground/80'}`}>
|
|
579
|
-
{cp.name}
|
|
580
|
-
</span>
|
|
581
|
-
<span className="text-2xs text-muted-foreground ml-1.5">{cpModel}</span>
|
|
582
|
-
</div>
|
|
583
|
-
{isSelected && <Check size={11} className="shrink-0 text-[var(--amber)]" />}
|
|
584
|
-
</button>
|
|
585
|
-
{canExpand && (
|
|
586
|
-
<button
|
|
587
|
-
type="button"
|
|
588
|
-
onClick={(e) => {
|
|
589
|
-
e.stopPropagation();
|
|
590
|
-
if (hoveredProvider === id) closeFlyoutImmediate();
|
|
591
|
-
else openFlyout(id as `cp_${string}`);
|
|
513
|
+
else openFlyout(id);
|
|
592
514
|
}}
|
|
593
515
|
className={`shrink-0 px-1.5 py-1.5 mr-1 rounded transition-colors ${
|
|
594
516
|
isHovered ? 'text-foreground' : 'text-muted-foreground/40 hover:text-muted-foreground'
|
|
@@ -620,7 +542,7 @@ export default function ProviderModelCapsule({
|
|
|
620
542
|
disabled={disabled}
|
|
621
543
|
className={`
|
|
622
544
|
inline-flex items-center gap-1 rounded-full px-2.5 py-0.5
|
|
623
|
-
text-2xs font-medium transition-colors select-none
|
|
545
|
+
text-2xs font-medium transition-colors select-none max-w-[260px]
|
|
624
546
|
border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring
|
|
625
547
|
disabled:opacity-40 disabled:cursor-not-allowed
|
|
626
548
|
${providerValue || hasModelOverride
|
|
@@ -628,13 +550,13 @@ export default function ProviderModelCapsule({
|
|
|
628
550
|
: 'bg-muted/50 border-border/50 text-muted-foreground hover:bg-muted hover:text-foreground'
|
|
629
551
|
}
|
|
630
552
|
`}
|
|
631
|
-
title={
|
|
553
|
+
title={capsuleTooltip}
|
|
632
554
|
aria-expanded={open}
|
|
633
555
|
aria-haspopup="listbox"
|
|
634
556
|
>
|
|
635
557
|
<Cpu size={11} className="shrink-0" />
|
|
636
|
-
<span className="truncate
|
|
637
|
-
{
|
|
558
|
+
<span className="truncate">
|
|
559
|
+
{providerDisplay}
|
|
638
560
|
<span className="text-muted-foreground"> · </span>
|
|
639
561
|
<span className={hasModelOverride ? 'text-[var(--amber)]' : 'text-muted-foreground'}>{modelShort}</span>
|
|
640
562
|
</span>
|