@geminilight/mindos 0.6.29 → 0.6.31
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/README.md +10 -4
- package/README_zh.md +10 -4
- package/app/app/api/acp/config/route.ts +82 -0
- package/app/app/api/acp/detect/route.ts +71 -48
- package/app/app/api/acp/install/route.ts +51 -0
- package/app/app/api/acp/session/route.ts +141 -11
- package/app/app/api/ask/route.ts +126 -18
- package/app/app/api/export/route.ts +105 -0
- package/app/app/api/workflows/route.ts +156 -0
- package/app/app/globals.css +2 -2
- package/app/app/page.tsx +7 -2
- package/app/app/trash/page.tsx +7 -0
- package/app/app/view/[...path]/ViewPageClient.tsx +234 -2
- package/app/components/ActivityBar.tsx +12 -4
- package/app/components/AskModal.tsx +4 -1
- package/app/components/ExportModal.tsx +220 -0
- package/app/components/FileTree.tsx +42 -11
- package/app/components/HomeContent.tsx +92 -20
- package/app/components/MarkdownView.tsx +45 -10
- package/app/components/Panel.tsx +1 -0
- package/app/components/RightAskPanel.tsx +5 -1
- package/app/components/Sidebar.tsx +10 -1
- package/app/components/SidebarLayout.tsx +6 -0
- package/app/components/TrashPageClient.tsx +263 -0
- package/app/components/agents/AgentDetailContent.tsx +263 -47
- package/app/components/agents/AgentsContentPage.tsx +11 -0
- package/app/components/agents/AgentsPanelA2aTab.tsx +285 -46
- package/app/components/agents/AgentsPanelSessionsTab.tsx +166 -0
- package/app/components/agents/agents-content-model.ts +2 -2
- package/app/components/ask/AgentSelectorCapsule.tsx +218 -0
- package/app/components/ask/AskContent.tsx +197 -239
- package/app/components/ask/FileChip.tsx +82 -17
- package/app/components/ask/MentionPopover.tsx +21 -3
- package/app/components/ask/MessageList.tsx +30 -9
- package/app/components/ask/SlashCommandPopover.tsx +21 -3
- package/app/components/ask/ToolCallBlock.tsx +102 -18
- package/app/components/changes/ChangesContentPage.tsx +58 -14
- package/app/components/explore/ExploreContent.tsx +4 -7
- package/app/components/explore/UseCaseCard.tsx +18 -1
- package/app/components/explore/use-cases.generated.ts +76 -0
- package/app/components/explore/use-cases.yaml +185 -0
- package/app/components/panels/AgentsPanel.tsx +1 -0
- package/app/components/panels/AgentsPanelHubNav.tsx +9 -2
- package/app/components/panels/DiscoverPanel.tsx +1 -1
- package/app/components/panels/WorkflowsPanel.tsx +206 -0
- package/app/components/renderers/workflow-yaml/StepEditor.tsx +164 -0
- package/app/components/renderers/workflow-yaml/WorkflowEditor.tsx +211 -0
- package/app/components/renderers/workflow-yaml/WorkflowRunner.tsx +269 -0
- package/app/components/renderers/workflow-yaml/WorkflowYamlRenderer.tsx +126 -0
- package/app/components/renderers/workflow-yaml/execution.ts +229 -0
- package/app/components/renderers/workflow-yaml/index.ts +6 -0
- package/app/components/renderers/workflow-yaml/manifest.ts +21 -0
- package/app/components/renderers/workflow-yaml/parser.ts +172 -0
- package/app/components/renderers/workflow-yaml/selectors.tsx +574 -0
- package/app/components/renderers/workflow-yaml/serializer.ts +56 -0
- package/app/components/renderers/workflow-yaml/types.ts +46 -0
- package/app/components/settings/AiTab.tsx +191 -174
- package/app/components/settings/AppearanceTab.tsx +168 -77
- package/app/components/settings/KnowledgeTab.tsx +131 -136
- package/app/components/settings/McpTab.tsx +11 -11
- package/app/components/settings/Primitives.tsx +60 -0
- package/app/components/settings/SettingsContent.tsx +15 -8
- package/app/components/settings/SyncTab.tsx +12 -12
- package/app/components/settings/UninstallTab.tsx +8 -18
- package/app/components/settings/UpdateTab.tsx +82 -82
- package/app/components/settings/types.ts +17 -8
- package/app/hooks/useAcpConfig.ts +96 -0
- package/app/hooks/useAcpDetection.ts +69 -14
- package/app/hooks/useAcpRegistry.ts +46 -11
- package/app/hooks/useAskModal.ts +12 -5
- package/app/hooks/useAskPanel.ts +8 -5
- package/app/hooks/useAskSession.ts +19 -2
- package/app/hooks/useImageUpload.ts +152 -0
- package/app/lib/acp/acp-tools.ts +3 -1
- package/app/lib/acp/agent-descriptors.ts +274 -0
- package/app/lib/acp/bridge.ts +6 -0
- package/app/lib/acp/index.ts +20 -4
- package/app/lib/acp/registry.ts +74 -7
- package/app/lib/acp/session.ts +490 -28
- package/app/lib/acp/subprocess.ts +307 -21
- package/app/lib/acp/types.ts +158 -20
- package/app/lib/actions.ts +57 -3
- package/app/lib/agent/model.ts +18 -3
- package/app/lib/agent/stream-consumer.ts +18 -0
- package/app/lib/agent/to-agent-messages.ts +25 -2
- package/app/lib/agent/tools.ts +56 -9
- package/app/lib/core/export.ts +116 -0
- package/app/lib/core/trash.ts +241 -0
- package/app/lib/fs.ts +47 -0
- package/app/lib/hooks/usePinnedFiles.ts +90 -0
- package/app/lib/i18n/generated/explore-i18n.generated.ts +138 -0
- package/app/lib/i18n/index.ts +3 -0
- package/app/lib/i18n/modules/knowledge.ts +124 -6
- package/app/lib/i18n/modules/navigation.ts +2 -0
- package/app/lib/i18n/modules/onboarding.ts +2 -134
- package/app/lib/i18n/modules/panels.ts +146 -2
- package/app/lib/i18n/modules/settings.ts +12 -0
- package/app/lib/pi-integration/skills.ts +21 -6
- package/app/lib/renderers/index.ts +2 -2
- package/app/lib/settings.ts +10 -0
- package/app/lib/types.ts +12 -1
- package/app/next-env.d.ts +1 -1
- package/app/package.json +11 -3
- package/app/scripts/generate-explore.ts +145 -0
- package/package.json +1 -1
- package/templates/en/.mindos/workflows/Sprint Release.flow.yaml +130 -0
- package/templates/zh/.mindos/workflows//345/221/250/350/277/255/344/273/243/346/243/200/346/237/245.flow.yaml +84 -0
- package/app/components/explore/use-cases.ts +0 -58
- package/app/components/renderers/workflow/WorkflowRenderer.tsx +0 -409
- package/app/components/renderers/workflow/manifest.ts +0 -14
|
@@ -6,11 +6,12 @@ import { FileNode } from '@/lib/types';
|
|
|
6
6
|
import { encodePath } from '@/lib/utils';
|
|
7
7
|
import {
|
|
8
8
|
ChevronDown, FileText, Table, Folder, FolderOpen, Plus, Loader2,
|
|
9
|
-
Trash2, Pencil, Layers, ScrollText, FolderInput, Copy,
|
|
9
|
+
Trash2, Pencil, Layers, ScrollText, FolderInput, Copy, MoreHorizontal, Star,
|
|
10
10
|
} from 'lucide-react';
|
|
11
11
|
import { createFileAction, deleteFileAction, renameFileAction, renameSpaceAction, deleteSpaceAction, convertToSpaceAction, deleteFolderAction } from '@/lib/actions';
|
|
12
12
|
import { useLocale } from '@/lib/LocaleContext';
|
|
13
13
|
import { ConfirmDialog } from '@/components/agents/AgentsPrimitives';
|
|
14
|
+
import { usePinnedFiles } from '@/lib/hooks/usePinnedFiles';
|
|
14
15
|
|
|
15
16
|
function notifyFilesChanged() {
|
|
16
17
|
window.dispatchEvent(new Event('mindos:files-changed'));
|
|
@@ -134,9 +135,15 @@ function SpaceContextMenu({ x, y, node, onClose, onRename, onImport, onDelete }:
|
|
|
134
135
|
}) {
|
|
135
136
|
const router = useRouter();
|
|
136
137
|
const { t } = useLocale();
|
|
138
|
+
const { isPinned, togglePin } = usePinnedFiles();
|
|
139
|
+
const pinned = isPinned(node.path);
|
|
137
140
|
|
|
138
141
|
return (
|
|
139
142
|
<ContextMenuShell x={x} y={y} onClose={onClose}>
|
|
143
|
+
<button className={MENU_ITEM} onClick={() => { togglePin(node.path); onClose(); }}>
|
|
144
|
+
<Star size={14} className={`shrink-0 ${pinned ? 'fill-[var(--amber)] text-[var(--amber)]' : ''}`} />
|
|
145
|
+
{pinned ? t.fileTree.removeFromFavorites : t.fileTree.pinToFavorites}
|
|
146
|
+
</button>
|
|
140
147
|
<button className={MENU_ITEM} onClick={() => { router.push(`/view/${encodePath(`${node.path}/INSTRUCTION.md`)}`); onClose(); }}>
|
|
141
148
|
<ScrollText size={14} className="shrink-0" /> {t.fileTree.editRules}
|
|
142
149
|
</button>
|
|
@@ -168,9 +175,15 @@ function FolderContextMenu({ x, y, node, onClose, onRename, onDelete }: {
|
|
|
168
175
|
const router = useRouter();
|
|
169
176
|
const { t } = useLocale();
|
|
170
177
|
const [isPending, startTransition] = useTransition();
|
|
178
|
+
const { isPinned, togglePin } = usePinnedFiles();
|
|
179
|
+
const pinned = isPinned(node.path);
|
|
171
180
|
|
|
172
181
|
return (
|
|
173
|
-
<ContextMenuShell x={x} y={y} onClose={onClose} menuHeight={
|
|
182
|
+
<ContextMenuShell x={x} y={y} onClose={onClose} menuHeight={180}>
|
|
183
|
+
<button className={MENU_ITEM} onClick={() => { togglePin(node.path); onClose(); }}>
|
|
184
|
+
<Star size={14} className={`shrink-0 ${pinned ? 'fill-[var(--amber)] text-[var(--amber)]' : ''}`} />
|
|
185
|
+
{pinned ? t.fileTree.removeFromFavorites : t.fileTree.pinToFavorites}
|
|
186
|
+
</button>
|
|
174
187
|
<button className={MENU_ITEM} disabled={isPending} onClick={() => {
|
|
175
188
|
startTransition(async () => {
|
|
176
189
|
const result = await convertToSpaceAction(node.path);
|
|
@@ -567,9 +580,11 @@ function FileNodeItem({ node, depth, currentPath, onNavigate }: {
|
|
|
567
580
|
const [renaming, setRenaming] = useState(false);
|
|
568
581
|
const [renameValue, setRenameValue] = useState(node.name);
|
|
569
582
|
const [isPending, startTransition] = useTransition();
|
|
570
|
-
const [
|
|
583
|
+
const [, startDeleteTransition] = useTransition();
|
|
571
584
|
const renameRef = useRef<HTMLInputElement>(null);
|
|
572
585
|
const { t } = useLocale();
|
|
586
|
+
const { isPinned, togglePin } = usePinnedFiles();
|
|
587
|
+
const pinned = isPinned(node.path);
|
|
573
588
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
|
574
589
|
const [contextMenu, setContextMenu] = useState<{ x: number; y: number } | null>(null);
|
|
575
590
|
|
|
@@ -659,16 +674,20 @@ function FileNodeItem({ node, depth, currentPath, onNavigate }: {
|
|
|
659
674
|
>
|
|
660
675
|
{getIcon(node)}
|
|
661
676
|
<span className="truncate leading-5" suppressHydrationWarning>{node.name}</span>
|
|
677
|
+
{pinned && <Star size={10} className="shrink-0 fill-[var(--amber)] text-[var(--amber)] opacity-60" />}
|
|
662
678
|
</button>
|
|
663
679
|
<div className="absolute right-1 top-1/2 -translate-y-1/2 hidden group-hover/file:flex items-center gap-0.5">
|
|
664
|
-
<button
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
680
|
+
<button
|
|
681
|
+
type="button"
|
|
682
|
+
onClick={(e) => {
|
|
683
|
+
e.stopPropagation();
|
|
684
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
685
|
+
setContextMenu({ x: rect.left, y: rect.bottom + 4 });
|
|
686
|
+
}}
|
|
687
|
+
className="p-0.5 rounded text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
|
|
688
|
+
title="More"
|
|
689
|
+
>
|
|
690
|
+
<MoreHorizontal size={14} />
|
|
672
691
|
</button>
|
|
673
692
|
</div>
|
|
674
693
|
{contextMenu && (
|
|
@@ -676,10 +695,22 @@ function FileNodeItem({ node, depth, currentPath, onNavigate }: {
|
|
|
676
695
|
x={contextMenu.x}
|
|
677
696
|
y={contextMenu.y}
|
|
678
697
|
onClose={() => setContextMenu(null)}
|
|
698
|
+
menuHeight={140}
|
|
679
699
|
>
|
|
680
700
|
<button className={MENU_ITEM} onClick={() => { copyPathToClipboard(node.path); setContextMenu(null); }}>
|
|
681
701
|
<Copy size={14} className="shrink-0" /> {t.fileTree.copyPath}
|
|
682
702
|
</button>
|
|
703
|
+
<button className={MENU_ITEM} onClick={() => { togglePin(node.path); setContextMenu(null); }}>
|
|
704
|
+
<Star size={14} className={`shrink-0 ${pinned ? 'fill-[var(--amber)] text-[var(--amber)]' : ''}`} />
|
|
705
|
+
{pinned ? t.fileTree.removeFromFavorites : t.fileTree.pinToFavorites}
|
|
706
|
+
</button>
|
|
707
|
+
<button className={MENU_ITEM} onClick={(e) => { setContextMenu(null); startRename(e); }}>
|
|
708
|
+
<Pencil size={14} className="shrink-0" /> {t.fileTree.rename}
|
|
709
|
+
</button>
|
|
710
|
+
<div className={MENU_DIVIDER} />
|
|
711
|
+
<button className={MENU_DANGER} onClick={(e) => { setContextMenu(null); handleDelete(e); }}>
|
|
712
|
+
<Trash2 size={14} className="shrink-0" /> {t.fileTree.delete}
|
|
713
|
+
</button>
|
|
683
714
|
</ContextMenuShell>
|
|
684
715
|
)}
|
|
685
716
|
<ConfirmDialog
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import Link from 'next/link';
|
|
4
|
-
import { FileText, Table, Clock, Sparkles, ArrowRight, FilePlus, Search, ChevronDown, Compass, Folder, Puzzle, Brain, Plus, Trash2, Check, Loader2, X, FolderInput, Zap, History, SlidersHorizontal, ListTodo } from 'lucide-react';
|
|
4
|
+
import { FileText, Table, Clock, Sparkles, ArrowRight, FilePlus, Search, ChevronDown, Compass, Folder, Puzzle, Brain, Plus, Trash2, Check, Loader2, X, FolderInput, Zap, History, SlidersHorizontal, ListTodo, Star } from 'lucide-react';
|
|
5
5
|
import type { LucideIcon } from 'lucide-react';
|
|
6
6
|
import { useState, useEffect, useMemo, useCallback } from 'react';
|
|
7
7
|
import { useLocale } from '@/lib/LocaleContext';
|
|
8
8
|
import { encodePath, relativeTime, extractEmoji, stripEmoji } from '@/lib/utils';
|
|
9
|
+
import { usePinnedFiles } from '@/lib/hooks/usePinnedFiles';
|
|
9
10
|
import { getAllRenderers, getPluginRenderers } from '@/lib/renderers/registry';
|
|
10
11
|
import OnboardingView from './OnboardingView';
|
|
11
12
|
import GuideCard from './GuideCard';
|
|
@@ -168,6 +169,7 @@ const TOOL_ICONS: Record<string, LucideIcon> = {
|
|
|
168
169
|
'agent-inspector': Search,
|
|
169
170
|
'config-panel': SlidersHorizontal,
|
|
170
171
|
'todo': ListTodo,
|
|
172
|
+
'workflow-yaml': Zap,
|
|
171
173
|
};
|
|
172
174
|
|
|
173
175
|
/** Mini-card for built-in tools — visually distinct from plugin chips */
|
|
@@ -251,14 +253,14 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
|
|
|
251
253
|
<ExampleCleanupBanner />
|
|
252
254
|
|
|
253
255
|
{/* ── Hero ── */}
|
|
254
|
-
<div className="mb-
|
|
256
|
+
<div className="mb-14">
|
|
255
257
|
<div className="flex items-center gap-2 mb-3">
|
|
256
|
-
<div className="w-1 h-
|
|
258
|
+
<div className="w-1 h-6 rounded-full bg-gradient-to-b from-[var(--amber)] to-[var(--amber)]/30" />
|
|
257
259
|
<h1 className="text-2xl font-semibold tracking-tight font-display text-foreground">
|
|
258
260
|
MindOS
|
|
259
261
|
</h1>
|
|
260
262
|
</div>
|
|
261
|
-
<p className="text-
|
|
263
|
+
<p className="text-base leading-relaxed mb-5 text-muted-foreground pl-4 max-w-md">
|
|
262
264
|
{t.app.tagline}
|
|
263
265
|
</p>
|
|
264
266
|
|
|
@@ -268,7 +270,7 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
|
|
|
268
270
|
onClick={triggerAsk}
|
|
269
271
|
title="⌘/"
|
|
270
272
|
data-walkthrough="ask-button"
|
|
271
|
-
className="flex-1 flex items-center gap-3 px-4 py-3 rounded-xl border border-border bg-card transition-all duration-150 hover:border-[var(--amber)]/50 hover:bg-[var(--amber-dim)]"
|
|
273
|
+
className="flex-1 flex items-center gap-3 px-4 py-3 rounded-xl border border-border/60 shadow-sm bg-card transition-all duration-150 hover:border-[var(--amber)]/50 hover:bg-[var(--amber-dim)]"
|
|
272
274
|
>
|
|
273
275
|
<Sparkles size={15} className="shrink-0 text-[var(--amber)]" />
|
|
274
276
|
<span className="text-sm flex-1 text-left text-foreground">
|
|
@@ -292,7 +294,7 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
|
|
|
292
294
|
</div>
|
|
293
295
|
|
|
294
296
|
{/* Quick Actions */}
|
|
295
|
-
<div className="flex flex-wrap gap-
|
|
297
|
+
<div className="flex flex-wrap gap-3 mt-5 pl-4">
|
|
296
298
|
<button
|
|
297
299
|
onClick={() => window.dispatchEvent(new CustomEvent('mindos:open-import'))}
|
|
298
300
|
className="inline-flex items-center gap-2 px-3.5 py-2 rounded-lg text-sm font-medium transition-all duration-150 hover:translate-x-0.5 bg-[var(--amber-dim)] text-[var(--amber-text)]"
|
|
@@ -330,8 +332,11 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
|
|
|
330
332
|
</div>
|
|
331
333
|
</div>
|
|
332
334
|
|
|
333
|
-
{/* ── Section
|
|
334
|
-
<
|
|
335
|
+
{/* ── Section: Pinned Files ── */}
|
|
336
|
+
<PinnedFilesSection formatTime={formatTime} />
|
|
337
|
+
|
|
338
|
+
{/* ── Section: Spaces ── */}
|
|
339
|
+
<section className="mb-12">
|
|
335
340
|
<SectionTitle
|
|
336
341
|
icon={<Brain size={13} />}
|
|
337
342
|
count={spaceList.length > 0 ? spaceList.length : undefined}
|
|
@@ -391,9 +396,9 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
|
|
|
391
396
|
)}
|
|
392
397
|
</section>
|
|
393
398
|
|
|
394
|
-
{/* ── Section
|
|
399
|
+
{/* ── Section: Tools ── */}
|
|
395
400
|
{builtinFeatures.length > 0 && (
|
|
396
|
-
<section className="mb-
|
|
401
|
+
<section className="mb-12">
|
|
397
402
|
<SectionTitle icon={<Zap size={13} />} count={builtinFeatures.length}>
|
|
398
403
|
{t.home.builtinFeatures}
|
|
399
404
|
</SectionTitle>
|
|
@@ -418,9 +423,9 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
|
|
|
418
423
|
</section>
|
|
419
424
|
)}
|
|
420
425
|
|
|
421
|
-
{/* ── Section
|
|
426
|
+
{/* ── Section: Extensions ── */}
|
|
422
427
|
{availablePlugins.length > 0 && (
|
|
423
|
-
<section className="mb-
|
|
428
|
+
<section className="mb-12">
|
|
424
429
|
<SectionTitle
|
|
425
430
|
icon={<Puzzle size={13} />}
|
|
426
431
|
count={availablePlugins.length}
|
|
@@ -445,7 +450,7 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
|
|
|
445
450
|
</section>
|
|
446
451
|
)}
|
|
447
452
|
|
|
448
|
-
{/* ── Section
|
|
453
|
+
{/* ── Section: Recently Edited ── */}
|
|
449
454
|
{recent.length > 0 && (
|
|
450
455
|
<section className="mb-12">
|
|
451
456
|
<SectionTitle
|
|
@@ -464,8 +469,30 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
|
|
|
464
469
|
{t.home.recentlyEdited}
|
|
465
470
|
</SectionTitle>
|
|
466
471
|
|
|
472
|
+
{/* Spotlight — latest file */}
|
|
473
|
+
<Link
|
|
474
|
+
href={`/view/${encodePath(recent[0].path)}`}
|
|
475
|
+
className="block mb-5 p-4 rounded-xl border border-border/50 bg-gradient-to-r from-[var(--amber-subtle)] to-transparent hover:border-[var(--amber)]/40 hover:shadow-sm transition-all group"
|
|
476
|
+
>
|
|
477
|
+
<div className="flex items-center gap-3">
|
|
478
|
+
<div className="w-10 h-10 rounded-lg bg-[var(--amber)]/10 flex items-center justify-center shrink-0">
|
|
479
|
+
{recent[0].path.endsWith('.csv')
|
|
480
|
+
? <Table size={18} className="text-[var(--amber)]" />
|
|
481
|
+
: <FileText size={18} className="text-[var(--amber)]" />}
|
|
482
|
+
</div>
|
|
483
|
+
<div className="flex-1 min-w-0">
|
|
484
|
+
<span className="text-base font-medium text-foreground block truncate">
|
|
485
|
+
{recent[0].path.split('/').pop()}
|
|
486
|
+
</span>
|
|
487
|
+
<span className="text-xs text-muted-foreground mt-0.5 block" suppressHydrationWarning>
|
|
488
|
+
{recent[0].path.split('/').slice(0, -1).join('/') || 'Root'} · {formatTime(recent[0].mtime)}
|
|
489
|
+
</span>
|
|
490
|
+
</div>
|
|
491
|
+
<ArrowRight size={16} className="text-muted-foreground group-hover:text-[var(--amber)] transition-colors shrink-0" />
|
|
492
|
+
</div>
|
|
493
|
+
</Link>
|
|
494
|
+
|
|
467
495
|
{groups.length > 0 ? (
|
|
468
|
-
/* Space-Grouped View */
|
|
469
496
|
<div className="flex flex-col gap-4">
|
|
470
497
|
{groups.map((group) => {
|
|
471
498
|
const visibleFiles = showAll ? group.files : group.files.slice(0, FILES_PER_GROUP);
|
|
@@ -504,7 +531,6 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
|
|
|
504
531
|
);
|
|
505
532
|
})}
|
|
506
533
|
|
|
507
|
-
{/* Root-level files (Other) */}
|
|
508
534
|
{rootFiles.length > 0 && (
|
|
509
535
|
<div>
|
|
510
536
|
<div className="flex items-center gap-2 px-1 py-1.5">
|
|
@@ -521,7 +547,6 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
|
|
|
521
547
|
</div>
|
|
522
548
|
)}
|
|
523
549
|
|
|
524
|
-
{/* Show more / less */}
|
|
525
550
|
{groups.some(g => g.files.length > FILES_PER_GROUP) && (
|
|
526
551
|
<ToggleButton
|
|
527
552
|
expanded={showAll}
|
|
@@ -533,7 +558,6 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
|
|
|
533
558
|
)}
|
|
534
559
|
</div>
|
|
535
560
|
) : (
|
|
536
|
-
/* Flat Timeline Fallback */
|
|
537
561
|
<div className="relative pl-4">
|
|
538
562
|
<div className="absolute left-0 top-1 bottom-1 w-px bg-border" />
|
|
539
563
|
<div className="flex flex-col gap-0.5">
|
|
@@ -547,8 +571,8 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
|
|
|
547
571
|
aria-hidden="true"
|
|
548
572
|
className={`absolute -left-4 top-1/2 -translate-y-1/2 rounded-full transition-all duration-150 group-hover:scale-150 ${
|
|
549
573
|
idx === 0
|
|
550
|
-
? 'w-2 h-2 bg-[var(--amber)]
|
|
551
|
-
: 'w-1.5 h-1.5 bg-
|
|
574
|
+
? 'w-2.5 h-2.5 bg-[var(--amber)] ring-2 ring-[var(--amber)]/20'
|
|
575
|
+
: 'w-1.5 h-1.5 bg-muted-foreground/30'
|
|
552
576
|
}`}
|
|
553
577
|
/>
|
|
554
578
|
<Link
|
|
@@ -586,7 +610,7 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
|
|
|
586
610
|
)}
|
|
587
611
|
|
|
588
612
|
{/* Footer */}
|
|
589
|
-
<div className="
|
|
613
|
+
<div className="py-6 border-t border-border/30 flex items-center gap-1.5 text-xs font-display text-muted-foreground opacity-40">
|
|
590
614
|
<Sparkles size={10} className="text-[var(--amber)]" />
|
|
591
615
|
<span>{t.app.footer}</span>
|
|
592
616
|
</div>
|
|
@@ -594,6 +618,54 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
|
|
|
594
618
|
);
|
|
595
619
|
}
|
|
596
620
|
|
|
621
|
+
/* ── Pinned Files Section ── */
|
|
622
|
+
function PinnedFilesSection({ formatTime }: { formatTime: (t: number) => string }) {
|
|
623
|
+
const { t } = useLocale();
|
|
624
|
+
const { pinnedFiles, removePin } = usePinnedFiles();
|
|
625
|
+
|
|
626
|
+
if (pinnedFiles.length === 0) return null;
|
|
627
|
+
|
|
628
|
+
return (
|
|
629
|
+
<section className="mb-12">
|
|
630
|
+
<SectionTitle icon={<Star size={13} />} count={pinnedFiles.length}>
|
|
631
|
+
{t.pinnedFiles.title}
|
|
632
|
+
</SectionTitle>
|
|
633
|
+
<div className="flex flex-col gap-0.5">
|
|
634
|
+
{pinnedFiles.map((filePath) => {
|
|
635
|
+
const name = filePath.split('/').pop() || filePath;
|
|
636
|
+
const dir = filePath.split('/').slice(0, -1).join('/');
|
|
637
|
+
const isCSV = filePath.endsWith('.csv');
|
|
638
|
+
return (
|
|
639
|
+
<div key={filePath} className="group/pin relative">
|
|
640
|
+
<Link
|
|
641
|
+
href={`/view/${encodePath(filePath)}`}
|
|
642
|
+
className="flex items-center gap-3 px-3 py-2 rounded-lg transition-all duration-100 hover:translate-x-0.5 hover:bg-muted"
|
|
643
|
+
>
|
|
644
|
+
<Star size={12} className="shrink-0 fill-[var(--amber)] text-[var(--amber)]" />
|
|
645
|
+
{isCSV
|
|
646
|
+
? <Table size={12} className="shrink-0 text-success" />
|
|
647
|
+
: <FileText size={12} className="shrink-0 text-muted-foreground" />
|
|
648
|
+
}
|
|
649
|
+
<div className="flex-1 min-w-0">
|
|
650
|
+
<span className="text-sm truncate block text-foreground" suppressHydrationWarning>{name}</span>
|
|
651
|
+
{dir && <span className="text-xs truncate block text-muted-foreground opacity-50" suppressHydrationWarning>{dir}</span>}
|
|
652
|
+
</div>
|
|
653
|
+
</Link>
|
|
654
|
+
<button
|
|
655
|
+
onClick={(e) => { e.preventDefault(); e.stopPropagation(); removePin(filePath); }}
|
|
656
|
+
className="absolute right-2 top-1/2 -translate-y-1/2 hidden group-hover/pin:flex p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
|
|
657
|
+
title={t.pinnedFiles.removedToast}
|
|
658
|
+
>
|
|
659
|
+
<X size={12} />
|
|
660
|
+
</button>
|
|
661
|
+
</div>
|
|
662
|
+
);
|
|
663
|
+
})}
|
|
664
|
+
</div>
|
|
665
|
+
</section>
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
|
|
597
669
|
/* ── Create Space: title-bar button ── */
|
|
598
670
|
function CreateSpaceButton({ t }: { t: ReturnType<typeof useLocale>['t'] }) {
|
|
599
671
|
return (
|
|
@@ -6,13 +6,17 @@ import rehypeHighlight from 'rehype-highlight';
|
|
|
6
6
|
import rehypeRaw from 'rehype-raw';
|
|
7
7
|
import rehypeSlug from 'rehype-slug';
|
|
8
8
|
import { useState, useCallback } from 'react';
|
|
9
|
-
import { Copy, Check } from 'lucide-react';
|
|
9
|
+
import { Copy, Check, X } from 'lucide-react';
|
|
10
10
|
import { copyToClipboard } from '@/lib/clipboard';
|
|
11
11
|
import { toast } from '@/lib/toast';
|
|
12
12
|
import type { Components } from 'react-markdown';
|
|
13
13
|
|
|
14
14
|
interface MarkdownViewProps {
|
|
15
15
|
content: string;
|
|
16
|
+
/** Lines changed by AI (1-indexed). Shows banner + fades after timeout. */
|
|
17
|
+
highlightLines?: number[];
|
|
18
|
+
/** Callback to dismiss the highlight banner */
|
|
19
|
+
onDismissHighlight?: () => void;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
function CopyButton({ code }: { code: string }) {
|
|
@@ -111,16 +115,47 @@ function extractText(node: React.ReactNode): string {
|
|
|
111
115
|
return '';
|
|
112
116
|
}
|
|
113
117
|
|
|
114
|
-
export default function MarkdownView({ content }: MarkdownViewProps) {
|
|
118
|
+
export default function MarkdownView({ content, highlightLines, onDismissHighlight }: MarkdownViewProps) {
|
|
119
|
+
const hasHighlights = highlightLines && highlightLines.length > 0;
|
|
120
|
+
|
|
115
121
|
return (
|
|
116
|
-
<div
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
<div>
|
|
123
|
+
{/* Change indicator banner */}
|
|
124
|
+
{hasHighlights && (
|
|
125
|
+
<div
|
|
126
|
+
className="mb-4 flex items-center gap-2 rounded-md border px-3 py-2 text-xs animate-in fade-in-0 duration-300"
|
|
127
|
+
style={{
|
|
128
|
+
borderColor: 'color-mix(in srgb, var(--amber) 40%, var(--border))',
|
|
129
|
+
background: 'color-mix(in srgb, var(--amber) 8%, var(--card))',
|
|
130
|
+
color: 'var(--amber)',
|
|
131
|
+
}}
|
|
132
|
+
data-highlight-line
|
|
133
|
+
>
|
|
134
|
+
<span className="w-1.5 h-1.5 rounded-full bg-[var(--amber)] animate-pulse shrink-0" />
|
|
135
|
+
<span className="font-display font-medium flex-1">
|
|
136
|
+
{highlightLines.length} line{highlightLines.length !== 1 ? 's' : ''} updated by AI
|
|
137
|
+
</span>
|
|
138
|
+
{onDismissHighlight && (
|
|
139
|
+
<button
|
|
140
|
+
type="button"
|
|
141
|
+
onClick={onDismissHighlight}
|
|
142
|
+
className="p-0.5 rounded hover:bg-[var(--amber)]/15 transition-colors shrink-0"
|
|
143
|
+
aria-label="Dismiss"
|
|
144
|
+
>
|
|
145
|
+
<X size={12} />
|
|
146
|
+
</button>
|
|
147
|
+
)}
|
|
148
|
+
</div>
|
|
149
|
+
)}
|
|
150
|
+
<div className="prose max-w-none">
|
|
151
|
+
<ReactMarkdown
|
|
152
|
+
remarkPlugins={[remarkGfm]}
|
|
153
|
+
rehypePlugins={[rehypeSlug, rehypeHighlight, rehypeRaw]}
|
|
154
|
+
components={components}
|
|
155
|
+
>
|
|
156
|
+
{content}
|
|
157
|
+
</ReactMarkdown>
|
|
158
|
+
</div>
|
|
124
159
|
</div>
|
|
125
160
|
);
|
|
126
161
|
}
|
package/app/components/Panel.tsx
CHANGED
|
@@ -10,11 +10,14 @@ const MIN_WIDTH = 300;
|
|
|
10
10
|
const MAX_WIDTH_ABS = 700;
|
|
11
11
|
const MAX_WIDTH_RATIO = 0.45;
|
|
12
12
|
|
|
13
|
+
import type { AcpAgentSelection } from '@/hooks/useAskModal';
|
|
14
|
+
|
|
13
15
|
interface RightAskPanelProps {
|
|
14
16
|
open: boolean;
|
|
15
17
|
onClose: () => void;
|
|
16
18
|
currentFile?: string;
|
|
17
19
|
initialMessage?: string;
|
|
20
|
+
initialAcpAgent?: AcpAgentSelection | null;
|
|
18
21
|
onFirstMessage?: () => void;
|
|
19
22
|
width: number;
|
|
20
23
|
onWidthChange: (w: number) => void;
|
|
@@ -28,7 +31,7 @@ interface RightAskPanelProps {
|
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
export default function RightAskPanel({
|
|
31
|
-
open, onClose, currentFile, initialMessage, onFirstMessage,
|
|
34
|
+
open, onClose, currentFile, initialMessage, initialAcpAgent, onFirstMessage,
|
|
32
35
|
width, onWidthChange, onWidthCommit, askMode, onModeSwitch,
|
|
33
36
|
maximized = false, onMaximize, sidebarOffset = 0,
|
|
34
37
|
}: RightAskPanelProps) {
|
|
@@ -73,6 +76,7 @@ export default function RightAskPanel({
|
|
|
73
76
|
variant="panel"
|
|
74
77
|
currentFile={open ? currentFile : undefined}
|
|
75
78
|
initialMessage={initialMessage}
|
|
79
|
+
initialAcpAgent={initialAcpAgent}
|
|
76
80
|
onFirstMessage={onFirstMessage}
|
|
77
81
|
onClose={onClose}
|
|
78
82
|
askMode={askMode}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
4
|
import Link from 'next/link';
|
|
5
5
|
import { useRouter, usePathname } from 'next/navigation';
|
|
6
|
-
import { Search, PanelLeftClose, PanelLeftOpen, Menu, X, Settings } from 'lucide-react';
|
|
6
|
+
import { Search, PanelLeftClose, PanelLeftOpen, Menu, X, Settings, Trash2 } from 'lucide-react';
|
|
7
7
|
import FileTree from './FileTree';
|
|
8
8
|
import SearchModal from './SearchModal';
|
|
9
9
|
import AskModal from './AskModal';
|
|
@@ -117,6 +117,15 @@ export default function Sidebar({ fileTree, collapsed = false, onCollapse, onExp
|
|
|
117
117
|
<div className="flex-1 overflow-y-auto min-h-0 px-2 py-2">
|
|
118
118
|
<FileTree nodes={fileTree} onNavigate={() => setMobileOpen(false)} />
|
|
119
119
|
</div>
|
|
120
|
+
<div className="px-2 pb-1">
|
|
121
|
+
<Link
|
|
122
|
+
href="/trash"
|
|
123
|
+
className="flex items-center gap-2 px-2 py-1.5 rounded-md text-xs text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
|
|
124
|
+
>
|
|
125
|
+
<Trash2 size={13} />
|
|
126
|
+
<span>{t.trash?.title ?? 'Trash'}</span>
|
|
127
|
+
</Link>
|
|
128
|
+
</div>
|
|
120
129
|
<SyncStatusBar
|
|
121
130
|
collapsed={collapsed}
|
|
122
131
|
onOpenSyncSettings={openSyncSettings}
|
|
@@ -13,6 +13,7 @@ import SearchPanel from './panels/SearchPanel';
|
|
|
13
13
|
import AgentsPanel from './panels/AgentsPanel';
|
|
14
14
|
import DiscoverPanel from './panels/DiscoverPanel';
|
|
15
15
|
import EchoPanel from './panels/EchoPanel';
|
|
16
|
+
import WorkflowsPanel from './panels/WorkflowsPanel';
|
|
16
17
|
|
|
17
18
|
import RightAskPanel from './RightAskPanel';
|
|
18
19
|
import RightAgentDetailPanel, {
|
|
@@ -407,6 +408,9 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
|
|
|
407
408
|
<div className={`flex flex-col h-full ${lp.activePanel === 'discover' ? '' : 'hidden'}`}>
|
|
408
409
|
<DiscoverPanel active={lp.activePanel === 'discover'} maximized={lp.panelMaximized} onMaximize={lp.handlePanelMaximize} />
|
|
409
410
|
</div>
|
|
411
|
+
<div className={`flex flex-col h-full ${lp.activePanel === 'workflows' ? '' : 'hidden'}`}>
|
|
412
|
+
<WorkflowsPanel active={lp.activePanel === 'workflows'} maximized={lp.panelMaximized} onMaximize={lp.handlePanelMaximize} />
|
|
413
|
+
</div>
|
|
410
414
|
</Panel>
|
|
411
415
|
|
|
412
416
|
{/* ── Right-side Ask AI Panel ── */}
|
|
@@ -415,6 +419,7 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
|
|
|
415
419
|
onClose={ap.closeAskPanel}
|
|
416
420
|
currentFile={currentFile}
|
|
417
421
|
initialMessage={ap.askInitialMessage}
|
|
422
|
+
initialAcpAgent={ap.askAcpAgent}
|
|
418
423
|
onFirstMessage={handleFirstMessage}
|
|
419
424
|
width={ap.askPanelWidth}
|
|
420
425
|
onWidthChange={ap.handleAskWidthChange}
|
|
@@ -441,6 +446,7 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
|
|
|
441
446
|
onClose={ap.closeDesktopAskPopup}
|
|
442
447
|
currentFile={currentFile}
|
|
443
448
|
initialMessage={ap.askInitialMessage}
|
|
449
|
+
initialAcpAgent={ap.askAcpAgent}
|
|
444
450
|
onFirstMessage={handleFirstMessage}
|
|
445
451
|
askMode={ap.askMode}
|
|
446
452
|
onModeSwitch={ap.handleAskModeSwitch}
|