@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
|
@@ -17,7 +17,7 @@ export async function GET() {
|
|
|
17
17
|
try {
|
|
18
18
|
const settings = readSettings();
|
|
19
19
|
const disabledSkills = settings.disabledSkills ?? [];
|
|
20
|
-
const skills = scanSkillDirs({
|
|
20
|
+
const skills = await scanSkillDirs({
|
|
21
21
|
projectRoot: PROJECT_ROOT,
|
|
22
22
|
mindRoot: getMindRoot(),
|
|
23
23
|
disabledSkills,
|
package/app/app/layout.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import ErrorBoundary from '@/components/ErrorBoundary';
|
|
|
9
9
|
import Toaster from '@/components/ui/Toaster';
|
|
10
10
|
import RegisterSW from './register-sw';
|
|
11
11
|
import UpdateOverlay from '@/components/UpdateOverlay';
|
|
12
|
+
import UpdateToast from '@/components/UpdateToast';
|
|
12
13
|
import { cookies, headers } from 'next/headers';
|
|
13
14
|
import type { Locale } from '@/lib/i18n';
|
|
14
15
|
import '@/lib/renderers/index'; // globally register built-in renderers once
|
|
@@ -120,9 +121,10 @@ export default async function RootLayout({
|
|
|
120
121
|
</ShellLayout>
|
|
121
122
|
</ErrorBoundary>
|
|
122
123
|
</TooltipProvider>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
<Toaster />
|
|
125
|
+
<RegisterSW />
|
|
126
|
+
<UpdateOverlay />
|
|
127
|
+
<UpdateToast />
|
|
126
128
|
</body>
|
|
127
129
|
</html>
|
|
128
130
|
);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState, useCallback } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
4
5
|
import { useLocale } from '@/lib/stores/locale-store';
|
|
5
6
|
import { FolderSync, PenLine, BarChart3, Sparkles, ArrowUpRight } from 'lucide-react';
|
|
6
7
|
import OnboardingView from './OnboardingView';
|
|
@@ -22,6 +23,7 @@ const TAB_ICONS = [FolderSync, PenLine, BarChart3, Sparkles];
|
|
|
22
23
|
|
|
23
24
|
export default function HomeContent({ recent, existingFiles, spaces }: { recent: RecentFile[]; existingFiles?: string[]; spaces?: SpaceInfo[] }) {
|
|
24
25
|
const { t } = useLocale();
|
|
26
|
+
const router = useRouter();
|
|
25
27
|
const [activeTab, setActiveTab] = useState(0);
|
|
26
28
|
const [maximized, setMaximized] = useState(false);
|
|
27
29
|
|
|
@@ -32,6 +34,14 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
|
|
|
32
34
|
setMaximized(true);
|
|
33
35
|
}, []);
|
|
34
36
|
|
|
37
|
+
// Navigate to editor with right-side Ask panel open
|
|
38
|
+
const handleDockToPanel = useCallback(() => {
|
|
39
|
+
const target = recent.length > 0 ? `/view/${recent[0].path}` : '/';
|
|
40
|
+
// Signal the already-mounted SidebarLayout to open the Ask panel
|
|
41
|
+
window.dispatchEvent(new CustomEvent('mindos:open-ask-panel'));
|
|
42
|
+
router.push(target);
|
|
43
|
+
}, [recent, router]);
|
|
44
|
+
|
|
35
45
|
if (recent.length === 0) {
|
|
36
46
|
return <OnboardingView />;
|
|
37
47
|
}
|
|
@@ -98,6 +108,7 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
|
|
|
98
108
|
maximized={maximized}
|
|
99
109
|
onMaximize={toggleMaximize}
|
|
100
110
|
onFirstMessage={handleFirstMessage}
|
|
111
|
+
onDockToPanel={handleDockToPanel}
|
|
101
112
|
/>
|
|
102
113
|
</div>
|
|
103
114
|
</div>
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useCallback, useRef } from 'react';
|
|
4
|
+
import { X } from 'lucide-react';
|
|
5
|
+
import { useLocale } from '@/lib/stores/locale-store';
|
|
6
|
+
|
|
7
|
+
/* ── Bridge interface ──────────────────────────────────────────────── */
|
|
8
|
+
|
|
9
|
+
interface MindosDesktopBridge {
|
|
10
|
+
checkUpdate?: () => Promise<{ available: boolean; version?: string }>;
|
|
11
|
+
onUpdateAvailable?: (cb: (info: { version?: string }) => void) => () => void;
|
|
12
|
+
checkCoreUpdate?: () => Promise<{
|
|
13
|
+
available: boolean;
|
|
14
|
+
currentVersion: string;
|
|
15
|
+
latestVersion: string;
|
|
16
|
+
}>;
|
|
17
|
+
onCoreUpdateAvailable?: (
|
|
18
|
+
cb: (info: { current: string; latest: string; ready?: boolean }) => void,
|
|
19
|
+
) => () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getDesktopBridge(): MindosDesktopBridge | null {
|
|
23
|
+
if (typeof window === 'undefined') return null;
|
|
24
|
+
const w = window as unknown as { mindos?: MindosDesktopBridge };
|
|
25
|
+
return w.mindos?.checkUpdate ? (w.mindos as MindosDesktopBridge) : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* ── Types ─────────────────────────────────────────────────────────── */
|
|
29
|
+
|
|
30
|
+
interface PendingUpdate {
|
|
31
|
+
type: 'desktop' | 'core';
|
|
32
|
+
version: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type ToastVisibility = 'hidden' | 'visible' | 'dismissing';
|
|
36
|
+
|
|
37
|
+
/* ── Constants ─────────────────────────────────────────────────────── */
|
|
38
|
+
|
|
39
|
+
const SKIP_DESKTOP_KEY = 'mindos_update_skip_desktop';
|
|
40
|
+
const SKIP_CORE_KEY = 'mindos_update_skip_core';
|
|
41
|
+
const SHOW_DELAY_MS = 10_000; // Wait 10 s after startup before showing
|
|
42
|
+
const DISMISS_MS = 200; // Match the CSS transition duration
|
|
43
|
+
|
|
44
|
+
/* ── Helpers ───────────────────────────────────────────────────────── */
|
|
45
|
+
|
|
46
|
+
/** Proper semantic-version comparison: returns true when `a` is strictly newer than `b`. */
|
|
47
|
+
function isNewer(a: string, b: string): boolean {
|
|
48
|
+
const pa = a.split('.').map(Number);
|
|
49
|
+
const pb = b.split('.').map(Number);
|
|
50
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
51
|
+
const va = pa[i] ?? 0;
|
|
52
|
+
const vb = pb[i] ?? 0;
|
|
53
|
+
if (va > vb) return true;
|
|
54
|
+
if (va < vb) return false;
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* ── Component ─────────────────────────────────────────────────────── */
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Desktop-only update notification toast.
|
|
63
|
+
*
|
|
64
|
+
* Appears in the bottom-right corner when the Electron bridge reports a new
|
|
65
|
+
* Desktop shell or MindOS Core update. Persists until the user clicks
|
|
66
|
+
* "View Details" (→ Settings > Update tab) or "Skip Version" (→ stored in
|
|
67
|
+
* localStorage so it won't re-appear for that version).
|
|
68
|
+
*
|
|
69
|
+
* Renders `null` in browser/CLI mode (no bridge).
|
|
70
|
+
*/
|
|
71
|
+
export default function UpdateToast() {
|
|
72
|
+
const { t } = useLocale();
|
|
73
|
+
const ut = t.settings.update.updateToast;
|
|
74
|
+
|
|
75
|
+
const [visibility, setVisibility] = useState<ToastVisibility>('hidden');
|
|
76
|
+
const [updates, setUpdates] = useState<{
|
|
77
|
+
desktop?: PendingUpdate;
|
|
78
|
+
core?: PendingUpdate;
|
|
79
|
+
}>({});
|
|
80
|
+
|
|
81
|
+
// Stable ref for the bridge — avoids re-running the effect every render.
|
|
82
|
+
const bridgeRef = useRef<MindosDesktopBridge | null>(null);
|
|
83
|
+
const [isDesktop, setIsDesktop] = useState(false);
|
|
84
|
+
|
|
85
|
+
// Timeout bookkeeping — prevents stale setState after dismiss / unmount.
|
|
86
|
+
const timers = useRef(new Set<ReturnType<typeof setTimeout>>());
|
|
87
|
+
const queued = useRef<{ desktop?: string; core?: string }>({});
|
|
88
|
+
|
|
89
|
+
// ── Timeout helpers ─────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
const clearTimers = useCallback(() => {
|
|
92
|
+
timers.current.forEach(clearTimeout);
|
|
93
|
+
timers.current.clear();
|
|
94
|
+
}, []);
|
|
95
|
+
|
|
96
|
+
const schedule = useCallback((state: ToastVisibility, ms: number) => {
|
|
97
|
+
const id = setTimeout(() => {
|
|
98
|
+
timers.current.delete(id);
|
|
99
|
+
setVisibility(state);
|
|
100
|
+
}, ms);
|
|
101
|
+
timers.current.add(id);
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
// ── Detect Desktop bridge once on mount ─────────────────────────────
|
|
105
|
+
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
const b = getDesktopBridge();
|
|
108
|
+
bridgeRef.current = b;
|
|
109
|
+
setIsDesktop(!!b);
|
|
110
|
+
}, []);
|
|
111
|
+
|
|
112
|
+
// ── Subscribe to IPC update events ──────────────────────────────────
|
|
113
|
+
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
const bridge = bridgeRef.current;
|
|
116
|
+
if (!bridge) return;
|
|
117
|
+
|
|
118
|
+
clearTimers();
|
|
119
|
+
const teardowns: Array<() => void> = [];
|
|
120
|
+
|
|
121
|
+
// Helper: queue an update unless already queued for this version.
|
|
122
|
+
const enqueue = (
|
|
123
|
+
key: 'desktop' | 'core',
|
|
124
|
+
version: string,
|
|
125
|
+
skipKey: string,
|
|
126
|
+
) => {
|
|
127
|
+
const skipped = localStorage.getItem(skipKey);
|
|
128
|
+
if (skipped && !isNewer(version, skipped)) return;
|
|
129
|
+
if (queued.current[key] === version) return; // de-dup
|
|
130
|
+
|
|
131
|
+
queued.current[key] = version;
|
|
132
|
+
setUpdates(prev => ({ ...prev, [key]: { type: key, version } }));
|
|
133
|
+
schedule('visible', SHOW_DELAY_MS);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
if (bridge.onUpdateAvailable) {
|
|
137
|
+
teardowns.push(
|
|
138
|
+
bridge.onUpdateAvailable(info => {
|
|
139
|
+
if (info?.version) enqueue('desktop', info.version, SKIP_DESKTOP_KEY);
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (bridge.onCoreUpdateAvailable) {
|
|
145
|
+
teardowns.push(
|
|
146
|
+
bridge.onCoreUpdateAvailable(info => {
|
|
147
|
+
if (info?.latest && !info.ready) {
|
|
148
|
+
enqueue('core', info.latest, SKIP_CORE_KEY);
|
|
149
|
+
}
|
|
150
|
+
}),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return () => {
|
|
155
|
+
teardowns.forEach(fn => fn());
|
|
156
|
+
clearTimers();
|
|
157
|
+
};
|
|
158
|
+
}, [isDesktop, clearTimers, schedule]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
159
|
+
|
|
160
|
+
// Clean up on unmount
|
|
161
|
+
useEffect(() => () => clearTimers(), [clearTimers]);
|
|
162
|
+
|
|
163
|
+
// ── Actions ─────────────────────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
const handleViewDetails = useCallback(() => {
|
|
166
|
+
clearTimers();
|
|
167
|
+
window.dispatchEvent(
|
|
168
|
+
new CustomEvent('mindos:open-settings', { detail: { tab: 'update' } }),
|
|
169
|
+
);
|
|
170
|
+
setVisibility('dismissing');
|
|
171
|
+
schedule('hidden', DISMISS_MS);
|
|
172
|
+
}, [clearTimers, schedule]);
|
|
173
|
+
|
|
174
|
+
const handleSkip = useCallback(() => {
|
|
175
|
+
clearTimers();
|
|
176
|
+
if (updates.desktop) localStorage.setItem(SKIP_DESKTOP_KEY, updates.desktop.version);
|
|
177
|
+
if (updates.core) localStorage.setItem(SKIP_CORE_KEY, updates.core.version);
|
|
178
|
+
setVisibility('dismissing');
|
|
179
|
+
schedule('hidden', DISMISS_MS);
|
|
180
|
+
}, [updates, clearTimers, schedule]);
|
|
181
|
+
|
|
182
|
+
// ── Render ──────────────────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
if (!isDesktop || visibility === 'hidden') return null;
|
|
185
|
+
|
|
186
|
+
const hasBoth = !!(updates.desktop && updates.core);
|
|
187
|
+
const title = hasBoth
|
|
188
|
+
? ut.titleMultiple
|
|
189
|
+
: updates.desktop
|
|
190
|
+
? ut.titleSingle(ut.desktopLabel, updates.desktop.version)
|
|
191
|
+
: updates.core
|
|
192
|
+
? ut.titleSingle(ut.coreLabel, updates.core.version)
|
|
193
|
+
: '';
|
|
194
|
+
|
|
195
|
+
const subtitle = hasBoth
|
|
196
|
+
? `${ut.desktopLabel} v${updates.desktop!.version} \u00B7 ${ut.coreLabel} v${updates.core!.version}`
|
|
197
|
+
: '';
|
|
198
|
+
|
|
199
|
+
const show = visibility === 'visible';
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<div
|
|
203
|
+
role="status"
|
|
204
|
+
aria-live="polite"
|
|
205
|
+
className={`
|
|
206
|
+
fixed bottom-14 right-4 z-40 pointer-events-none
|
|
207
|
+
transition-all duration-200
|
|
208
|
+
${show ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-2'}
|
|
209
|
+
`}
|
|
210
|
+
>
|
|
211
|
+
<div className="pointer-events-auto flex flex-col gap-2.5 bg-card border border-border rounded-xl shadow-lg px-4 py-3 w-[290px]">
|
|
212
|
+
{/* ── Title row ── */}
|
|
213
|
+
<div className="flex items-start gap-2">
|
|
214
|
+
{/* Amber indicator dot */}
|
|
215
|
+
<span className="mt-[5px] w-2 h-2 rounded-full bg-[var(--amber)] shrink-0" />
|
|
216
|
+
|
|
217
|
+
<div className="flex-1 min-w-0">
|
|
218
|
+
<p className="text-sm font-medium text-foreground leading-snug">{title}</p>
|
|
219
|
+
{subtitle && (
|
|
220
|
+
<p className="text-xs text-muted-foreground mt-0.5 truncate">{subtitle}</p>
|
|
221
|
+
)}
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
{/* Close = same as skip */}
|
|
225
|
+
<button
|
|
226
|
+
type="button"
|
|
227
|
+
onClick={handleSkip}
|
|
228
|
+
className="shrink-0 p-0.5 rounded text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
229
|
+
aria-label="Dismiss"
|
|
230
|
+
>
|
|
231
|
+
<X size={13} />
|
|
232
|
+
</button>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
{/* ── Actions ── */}
|
|
236
|
+
<div className="flex gap-2">
|
|
237
|
+
<button
|
|
238
|
+
type="button"
|
|
239
|
+
onClick={handleViewDetails}
|
|
240
|
+
className="flex-1 px-3 py-1.5 text-xs font-medium rounded-lg text-[var(--amber-foreground)] bg-[var(--amber)] hover:opacity-90 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
241
|
+
>
|
|
242
|
+
{ut.viewDetails}
|
|
243
|
+
</button>
|
|
244
|
+
<button
|
|
245
|
+
type="button"
|
|
246
|
+
onClick={handleSkip}
|
|
247
|
+
className="flex-1 px-3 py-1.5 text-xs rounded-lg text-muted-foreground border border-border hover:text-foreground hover:bg-muted transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
248
|
+
>
|
|
249
|
+
{hasBoth ? ut.skipAll : ut.skipVersion}
|
|
250
|
+
</button>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
@@ -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">
|