@geminilight/mindos 0.5.64 → 0.5.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/README.md +4 -0
- package/README_zh.md +4 -0
- package/app/app/api/ask/route.ts +12 -0
- package/app/app/api/file/route.ts +9 -0
- package/app/app/api/mcp/agents/route.ts +27 -1
- package/app/app/api/skills/route.ts +18 -2
- package/app/app/api/tree-version/route.ts +8 -0
- package/app/components/ActivityBar.tsx +2 -2
- package/app/components/Backlinks.tsx +5 -5
- package/app/components/CreateSpaceModal.tsx +3 -2
- package/app/components/DirPicker.tsx +1 -1
- package/app/components/DirView.tsx +2 -3
- package/app/components/EditorWrapper.tsx +3 -3
- package/app/components/FileTree.tsx +25 -10
- package/app/components/GuideCard.tsx +4 -4
- package/app/components/HomeContent.tsx +6 -11
- package/app/components/MarkdownView.tsx +2 -2
- package/app/components/OnboardingView.tsx +1 -1
- package/app/components/Panel.tsx +1 -1
- package/app/components/RightAgentDetailPanel.tsx +1 -1
- package/app/components/RightAskPanel.tsx +1 -1
- package/app/components/SearchModal.tsx +10 -2
- package/app/components/SidebarLayout.tsx +35 -10
- package/app/components/ThemeToggle.tsx +1 -1
- package/app/components/agents/AgentDetailContent.tsx +454 -59
- package/app/components/agents/AgentsContentPage.tsx +70 -5
- package/app/components/agents/AgentsMcpSection.tsx +474 -159
- package/app/components/agents/AgentsOverviewSection.tsx +418 -59
- package/app/components/agents/AgentsPrimitives.tsx +335 -0
- package/app/components/agents/AgentsSkillsSection.tsx +739 -121
- package/app/components/agents/SkillDetailPopover.tsx +416 -0
- package/app/components/agents/agents-content-model.ts +292 -10
- package/app/components/ask/AskContent.tsx +34 -5
- package/app/components/ask/FileChip.tsx +1 -0
- package/app/components/ask/MentionPopover.tsx +13 -1
- package/app/components/ask/MessageList.tsx +5 -7
- package/app/components/ask/ToolCallBlock.tsx +4 -4
- package/app/components/changes/ChangesBanner.tsx +1 -2
- package/app/components/echo/EchoHero.tsx +10 -24
- package/app/components/echo/EchoInsightCollapsible.tsx +52 -43
- package/app/components/echo/EchoPageSections.tsx +13 -9
- package/app/components/echo/EchoSegmentNav.tsx +14 -11
- package/app/components/echo/EchoSegmentPageClient.tsx +64 -43
- package/app/components/explore/ExploreContent.tsx +3 -7
- package/app/components/explore/UseCaseCard.tsx +4 -15
- package/app/components/panels/AgentsPanel.tsx +12 -104
- package/app/components/panels/AgentsPanelAgentDetail.tsx +2 -2
- package/app/components/panels/AgentsPanelAgentGroups.tsx +3 -7
- package/app/components/panels/AgentsPanelAgentListRow.tsx +9 -11
- package/app/components/panels/EchoPanel.tsx +8 -10
- package/app/components/panels/PanelNavRow.tsx +9 -2
- package/app/components/panels/PluginsPanel.tsx +2 -2
- package/app/components/renderers/agent-inspector/AgentInspectorRenderer.tsx +30 -8
- package/app/components/renderers/agent-inspector/manifest.ts +3 -3
- package/app/components/renderers/todo/manifest.ts +1 -0
- package/app/components/settings/AiTab.tsx +3 -3
- package/app/components/settings/AppearanceTab.tsx +2 -2
- package/app/components/settings/KnowledgeTab.tsx +3 -3
- package/app/components/settings/McpAgentInstall.tsx +3 -6
- package/app/components/settings/McpSkillCreateForm.tsx +2 -3
- package/app/components/settings/McpSkillRow.tsx +2 -3
- package/app/components/settings/McpSkillsSection.tsx +2 -2
- package/app/components/settings/McpTab.tsx +12 -13
- package/app/components/settings/MonitoringTab.tsx +13 -13
- package/app/components/settings/PluginsTab.tsx +2 -2
- package/app/components/settings/Primitives.tsx +3 -4
- package/app/components/settings/SettingsContent.tsx +3 -3
- package/app/components/settings/SyncTab.tsx +11 -17
- package/app/components/settings/UpdateTab.tsx +18 -21
- package/app/components/settings/types.ts +14 -0
- package/app/components/setup/StepKB.tsx +1 -1
- package/app/hooks/useMcpData.tsx +4 -2
- package/app/hooks/useMention.ts +25 -8
- package/app/lib/agent/log.ts +15 -18
- package/app/lib/agent/stream-consumer.ts +3 -0
- package/app/lib/agent/to-agent-messages.ts +6 -4
- package/app/lib/core/agent-audit-log.ts +280 -0
- package/app/lib/core/index.ts +11 -0
- package/app/lib/fs.ts +9 -0
- package/app/lib/i18n-en.ts +259 -33
- package/app/lib/i18n-zh.ts +258 -32
- package/app/lib/mcp-agents.ts +231 -2
- package/app/lib/types.ts +2 -0
- package/package.json +1 -1
- package/scripts/migrate-agent-audit-log.js +170 -0
package/README.md
CHANGED
|
@@ -276,6 +276,10 @@ Join our WeChat group for early access, feedback, and AI workflow discussions:
|
|
|
276
276
|
<a href="https://github.com/USTChandsomeboy"><img src="https://github.com/USTChandsomeboy.png" width="60" style="border-radius:50%" alt="USTChandsomeboy" /></a>
|
|
277
277
|
<a href="https://github.com/ppsmk388"><img src="https://github.com/ppsmk388.png" width="60" style="border-radius:50%" alt="ppsmk388" /></a>
|
|
278
278
|
|
|
279
|
+
### 🙏 Acknowledgements
|
|
280
|
+
|
|
281
|
+
This project has been published on the [LINUX DO community](https://linux.do), and we deeply appreciate the community's support and feedback.
|
|
282
|
+
|
|
279
283
|
---
|
|
280
284
|
|
|
281
285
|
## 📄 License
|
package/README_zh.md
CHANGED
|
@@ -276,6 +276,10 @@ MindOS/
|
|
|
276
276
|
<a href="https://github.com/USTChandsomeboy"><img src="https://github.com/USTChandsomeboy.png" width="60" style="border-radius:50%" alt="USTChandsomeboy" /></a>
|
|
277
277
|
<a href="https://github.com/ppsmk388"><img src="https://github.com/ppsmk388.png" width="60" style="border-radius:50%" alt="ppsmk388" /></a>
|
|
278
278
|
|
|
279
|
+
### 🙏 致谢
|
|
280
|
+
|
|
281
|
+
本项目已在 [LINUX DO 社区](https://linux.do) 发布,感谢社区的支持与反馈。
|
|
282
|
+
|
|
279
283
|
---
|
|
280
284
|
|
|
281
285
|
## 📄 License
|
package/app/app/api/ask/route.ts
CHANGED
|
@@ -278,7 +278,19 @@ export async function POST(req: NextRequest) {
|
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
+
// Generate current time for the agent's context
|
|
282
|
+
const now = new Date();
|
|
283
|
+
const timeContext = `## Current Time Context
|
|
284
|
+
- Current UTC Time: ${now.toISOString()}
|
|
285
|
+
- System Local Time: ${new Intl.DateTimeFormat('en-US', { dateStyle: 'full', timeStyle: 'long' }).format(now)}
|
|
286
|
+
- Unix Timestamp: ${Math.floor(now.getTime() / 1000)}
|
|
287
|
+
|
|
288
|
+
*Note: The times listed above represent "NOW". The user may have sent messages hours or days ago in this same conversation thread. Each user message in the history contains its own specific timestamp which you should refer to when understanding historical context.*`;
|
|
289
|
+
|
|
281
290
|
const promptParts: string[] = [AGENT_SYSTEM_PROMPT];
|
|
291
|
+
|
|
292
|
+
promptParts.push(`---\n\n${timeContext}`);
|
|
293
|
+
|
|
282
294
|
promptParts.push(`---\n\nInitialization status (auto-loaded at request start):\n\n${initStatus}`);
|
|
283
295
|
|
|
284
296
|
if (initContextBlocks.length > 0) {
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
appendContentChange,
|
|
23
23
|
} from '@/lib/fs';
|
|
24
24
|
import { createSpaceFilesystem } from '@/lib/core/create-space';
|
|
25
|
+
import { appendAgentAuditEvent, parseAgentAuditJsonLines } from '@/lib/core/agent-audit-log';
|
|
25
26
|
|
|
26
27
|
function err(msg: string, status = 400) {
|
|
27
28
|
return NextResponse.json({ error: msg }, { status });
|
|
@@ -123,6 +124,14 @@ export async function POST(req: NextRequest) {
|
|
|
123
124
|
case 'append_to_file': {
|
|
124
125
|
const { content } = params as { content: string };
|
|
125
126
|
if (typeof content !== 'string') return err('missing content');
|
|
127
|
+
if (filePath === '.agent-log.json') {
|
|
128
|
+
const entries = parseAgentAuditJsonLines(content);
|
|
129
|
+
for (const entry of entries) {
|
|
130
|
+
appendAgentAuditEvent(getMindRoot(), entry);
|
|
131
|
+
}
|
|
132
|
+
resp = NextResponse.json({ ok: true, migratedEntries: entries.length });
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
126
135
|
const before = safeRead(filePath);
|
|
127
136
|
appendToFile(filePath, content);
|
|
128
137
|
changeEvent = {
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
export const dynamic = 'force-dynamic';
|
|
2
2
|
import { NextResponse } from 'next/server';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
MCP_AGENTS,
|
|
5
|
+
detectInstalled,
|
|
6
|
+
detectAgentPresence,
|
|
7
|
+
detectAgentRuntimeSignals,
|
|
8
|
+
detectAgentConfiguredMcpServers,
|
|
9
|
+
detectAgentInstalledSkills,
|
|
10
|
+
resolveSkillWorkspaceProfile,
|
|
11
|
+
} from '@/lib/mcp-agents';
|
|
4
12
|
|
|
5
13
|
export async function GET() {
|
|
6
14
|
try {
|
|
7
15
|
const agents = Object.entries(MCP_AGENTS).map(([key, agent]) => {
|
|
8
16
|
const status = detectInstalled(key);
|
|
9
17
|
const present = detectAgentPresence(key);
|
|
18
|
+
const skillProfile = resolveSkillWorkspaceProfile(key);
|
|
19
|
+
const runtime = detectAgentRuntimeSignals(key);
|
|
20
|
+
const configuredMcp = detectAgentConfiguredMcpServers(key);
|
|
21
|
+
const installedSkills = detectAgentInstalledSkills(key);
|
|
10
22
|
return {
|
|
11
23
|
key,
|
|
12
24
|
name: agent.name,
|
|
@@ -24,6 +36,20 @@ export async function GET() {
|
|
|
24
36
|
globalNestedKey: agent.globalNestedKey,
|
|
25
37
|
globalPath: agent.global,
|
|
26
38
|
projectPath: agent.project,
|
|
39
|
+
skillMode: skillProfile.mode,
|
|
40
|
+
skillAgentName: skillProfile.skillAgentName,
|
|
41
|
+
skillWorkspacePath: skillProfile.workspacePath,
|
|
42
|
+
hiddenRootPath: runtime.hiddenRootPath,
|
|
43
|
+
hiddenRootPresent: runtime.hiddenRootPresent,
|
|
44
|
+
runtimeConversationSignal: runtime.conversationSignal,
|
|
45
|
+
runtimeUsageSignal: runtime.usageSignal,
|
|
46
|
+
runtimeLastActivityAt: runtime.lastActivityAt,
|
|
47
|
+
configuredMcpServers: configuredMcp.servers,
|
|
48
|
+
configuredMcpServerCount: configuredMcp.servers.length,
|
|
49
|
+
configuredMcpSources: configuredMcp.sources,
|
|
50
|
+
installedSkillNames: installedSkills.skills,
|
|
51
|
+
installedSkillCount: installedSkills.skills.length,
|
|
52
|
+
installedSkillSourcePath: installedSkills.sourcePath,
|
|
27
53
|
};
|
|
28
54
|
});
|
|
29
55
|
|
|
@@ -128,12 +128,13 @@ export async function GET() {
|
|
|
128
128
|
export async function POST(req: NextRequest) {
|
|
129
129
|
try {
|
|
130
130
|
const body = await req.json();
|
|
131
|
-
const { action, name, description, content, enabled } = body as {
|
|
132
|
-
action: 'create' | 'update' | 'delete' | 'toggle' | 'read';
|
|
131
|
+
const { action, name, description, content, enabled, sourcePath } = body as {
|
|
132
|
+
action: 'create' | 'update' | 'delete' | 'toggle' | 'read' | 'read-native';
|
|
133
133
|
name?: string;
|
|
134
134
|
description?: string;
|
|
135
135
|
content?: string;
|
|
136
136
|
enabled?: boolean;
|
|
137
|
+
sourcePath?: string;
|
|
137
138
|
};
|
|
138
139
|
|
|
139
140
|
const settings = readSettings();
|
|
@@ -218,6 +219,21 @@ export async function POST(req: NextRequest) {
|
|
|
218
219
|
return NextResponse.json({ error: 'Skill not found' }, { status: 404 });
|
|
219
220
|
}
|
|
220
221
|
|
|
222
|
+
case 'read-native': {
|
|
223
|
+
if (!name || !sourcePath) return NextResponse.json({ error: 'name and sourcePath required' }, { status: 400 });
|
|
224
|
+
const nativeBase = path.resolve(sourcePath);
|
|
225
|
+
const nativeSkillFile = path.join(nativeBase, name, 'SKILL.md');
|
|
226
|
+
if (!nativeSkillFile.startsWith(nativeBase)) {
|
|
227
|
+
return NextResponse.json({ error: 'Invalid path' }, { status: 400 });
|
|
228
|
+
}
|
|
229
|
+
if (!fs.existsSync(nativeSkillFile)) {
|
|
230
|
+
return NextResponse.json({ error: 'Skill not found' }, { status: 404 });
|
|
231
|
+
}
|
|
232
|
+
const nativeContent = fs.readFileSync(nativeSkillFile, 'utf-8');
|
|
233
|
+
const { description: nativeDesc } = parseSkillMd(nativeContent);
|
|
234
|
+
return NextResponse.json({ content: nativeContent, description: nativeDesc });
|
|
235
|
+
}
|
|
236
|
+
|
|
221
237
|
default:
|
|
222
238
|
return NextResponse.json({ error: `Unknown action: ${action}` }, { status: 400 });
|
|
223
239
|
}
|
|
@@ -58,7 +58,7 @@ function RailButton({ icon, label, shortcut, active = false, expanded, onClick,
|
|
|
58
58
|
`}
|
|
59
59
|
>
|
|
60
60
|
{active && (
|
|
61
|
-
<span className="absolute left-0 top-1/2 -translate-y-1/2 w-[2px] h-[18px] rounded-r-full
|
|
61
|
+
<span className="absolute left-0 top-1/2 -translate-y-1/2 w-[2px] h-[18px] rounded-r-full bg-[var(--amber)]" />
|
|
62
62
|
)}
|
|
63
63
|
<span className="shrink-0 flex items-center justify-center w-[18px]">{icon}</span>
|
|
64
64
|
{badge}
|
|
@@ -147,7 +147,7 @@ export default function ActivityBar({
|
|
|
147
147
|
{/* ── Top: Logo ── */}
|
|
148
148
|
<Link
|
|
149
149
|
href="/"
|
|
150
|
-
className={`flex items-center ${expanded ? 'px-3 gap-2' : 'justify-center'} w-full py-
|
|
150
|
+
className={`flex items-center ${expanded ? 'px-3 gap-2' : 'justify-center'} w-full py-[13px] hover:opacity-80 transition-opacity`}
|
|
151
151
|
aria-label="MindOS Home"
|
|
152
152
|
>
|
|
153
153
|
<Logo id="rail" className="w-7 h-3.5 shrink-0" />
|
|
@@ -30,7 +30,7 @@ export default function Backlinks({ filePath }: { filePath: string }) {
|
|
|
30
30
|
return (
|
|
31
31
|
<div className="mt-12 pt-8 border-t border-border">
|
|
32
32
|
<div className="flex items-center gap-2 mb-6 text-muted-foreground">
|
|
33
|
-
<LinkIcon size={16} className="text-amber
|
|
33
|
+
<LinkIcon size={16} className="text-[var(--amber)]/70" />
|
|
34
34
|
<h3 className="text-sm font-semibold tracking-wider uppercase font-display">
|
|
35
35
|
{t.common?.relatedFiles || 'Related Files'}
|
|
36
36
|
</h3>
|
|
@@ -51,14 +51,14 @@ export default function Backlinks({ filePath }: { filePath: string }) {
|
|
|
51
51
|
<Link
|
|
52
52
|
key={link.filePath}
|
|
53
53
|
href={`/view/${link.filePath.split('/').map(encodeURIComponent).join('/')}`}
|
|
54
|
-
className="group block p-4 rounded-xl border border-border/50 bg-card/30 hover:bg-muted/30 hover:border-amber
|
|
54
|
+
className="group block p-4 rounded-xl border border-border/50 bg-card/30 hover:bg-muted/30 hover:border-[var(--amber)]/30 transition-all duration-200"
|
|
55
55
|
>
|
|
56
56
|
<div className="flex items-start gap-3">
|
|
57
|
-
<div className="mt-1 p-1.5 rounded-md bg-muted group-hover:bg-amber
|
|
58
|
-
<FileText size={14} className="text-muted-foreground group-hover:text-amber
|
|
57
|
+
<div className="mt-1 p-1.5 rounded-md bg-muted group-hover:bg-[var(--amber)]/10 transition-colors">
|
|
58
|
+
<FileText size={14} className="text-muted-foreground group-hover:text-[var(--amber)]" />
|
|
59
59
|
</div>
|
|
60
60
|
<div className="min-w-0 flex-1">
|
|
61
|
-
<div className="font-medium text-sm text-foreground group-hover:text-amber
|
|
61
|
+
<div className="font-medium text-sm text-foreground group-hover:text-[var(--amber)] transition-colors truncate mb-1">
|
|
62
62
|
{link.filePath}
|
|
63
63
|
</div>
|
|
64
64
|
<div className="text-xs text-muted-foreground line-clamp-2 leading-relaxed italic opacity-80 group-hover:opacity-100 transition-opacity">
|
|
@@ -108,6 +108,7 @@ export default function CreateSpaceModal({ t, dirPaths }: { t: ReturnType<typeof
|
|
|
108
108
|
|
|
109
109
|
close();
|
|
110
110
|
router.refresh();
|
|
111
|
+
window.dispatchEvent(new Event('mindos:files-changed'));
|
|
111
112
|
router.push(`/view/${encodePath(createdPath + '/')}`);
|
|
112
113
|
} else {
|
|
113
114
|
const msg = result.error ?? '';
|
|
@@ -200,7 +201,7 @@ export default function CreateSpaceModal({ t, dirPaths }: { t: ReturnType<typeof
|
|
|
200
201
|
disabled={!aiAvailable}
|
|
201
202
|
onClick={() => setUseAi(v => !v)}
|
|
202
203
|
className={`relative mt-0.5 inline-flex shrink-0 h-4 w-7 cursor-pointer rounded-full border-2 border-transparent transition-colors focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 ${
|
|
203
|
-
useAi ? 'bg-amber
|
|
204
|
+
useAi ? 'bg-[var(--amber)]' : 'bg-muted'
|
|
204
205
|
}`}
|
|
205
206
|
>
|
|
206
207
|
<span className={`pointer-events-none inline-block h-3 w-3 rounded-full bg-white shadow-sm transition-transform ${useAi ? 'translate-x-3' : 'translate-x-0'}`} />
|
|
@@ -212,7 +213,7 @@ export default function CreateSpaceModal({ t, dirPaths }: { t: ReturnType<typeof
|
|
|
212
213
|
</div>
|
|
213
214
|
{aiAvailable === false && (
|
|
214
215
|
<p className="text-2xs text-muted-foreground mt-0.5 flex items-center gap-1">
|
|
215
|
-
<AlertTriangle size={10} className="text-amber
|
|
216
|
+
<AlertTriangle size={10} className="text-[var(--amber)] shrink-0" />
|
|
216
217
|
{h.aiInitNoKey ?? 'Configure an API key in Settings → AI to enable'}
|
|
217
218
|
</p>
|
|
218
219
|
)}
|
|
@@ -57,7 +57,7 @@ export default function DirPicker({ dirPaths, value, onChange, rootLabel = 'Root
|
|
|
57
57
|
<button
|
|
58
58
|
type="button"
|
|
59
59
|
onClick={() => setExpanded(true)}
|
|
60
|
-
className="w-full flex items-center gap-2 px-3 py-2 text-sm rounded-lg border border-border bg-background text-foreground hover:border-amber
|
|
60
|
+
className="w-full flex items-center gap-2 px-3 py-2 text-sm rounded-lg border border-border bg-background text-foreground hover:border-[var(--amber)]/40 transition-colors text-left"
|
|
61
61
|
>
|
|
62
62
|
<Folder size={14} className="shrink-0 text-[var(--amber)]" />
|
|
63
63
|
<span className="flex-1 truncate">{displayLabel}</span>
|
|
@@ -84,8 +84,7 @@ function SpacePreviewCard({ icon, title, lines, viewAllHref, viewAllLabel }: {
|
|
|
84
84
|
<div className="flex justify-end mt-2">
|
|
85
85
|
<Link
|
|
86
86
|
href={viewAllHref}
|
|
87
|
-
className="text-xs hover:underline transition-colors"
|
|
88
|
-
style={{ color: 'var(--amber)' }}
|
|
87
|
+
className="text-xs hover:underline transition-colors text-[var(--amber)]"
|
|
89
88
|
>
|
|
90
89
|
{viewAllLabel}
|
|
91
90
|
</Link>
|
|
@@ -150,7 +149,7 @@ export default function DirView({ dirPath, entries, spacePreview }: DirViewProps
|
|
|
150
149
|
return (
|
|
151
150
|
<div className="flex flex-col min-h-screen">
|
|
152
151
|
{/* Topbar */}
|
|
153
|
-
<div className="sticky top-[52px] md:top-0 z-20 border-b border-border px-4 md:px-6 py-2.5
|
|
152
|
+
<div className="sticky top-[52px] md:top-0 z-20 border-b border-border px-4 md:px-6 py-2.5 bg-background">
|
|
154
153
|
<div className="max-w-[860px] mx-auto flex items-center justify-between gap-2">
|
|
155
154
|
<div className="min-w-0 flex-1">
|
|
156
155
|
<Breadcrumb filePath={dirPath} />
|
|
@@ -5,9 +5,9 @@ import dynamic from 'next/dynamic';
|
|
|
5
5
|
const Editor = dynamic(() => import('./Editor'), {
|
|
6
6
|
ssr: false,
|
|
7
7
|
loading: () => (
|
|
8
|
-
<div className="h-full w-full min-h-[400px] rounded-lg border border-
|
|
9
|
-
<div className="flex items-center gap-2 text-
|
|
10
|
-
<div className="w-4 h-4 border-2 border-
|
|
8
|
+
<div className="h-full w-full min-h-[400px] rounded-lg border border-border bg-background flex items-center justify-center">
|
|
9
|
+
<div className="flex items-center gap-2 text-muted-foreground text-sm">
|
|
10
|
+
<div className="w-4 h-4 border-2 border-border border-t-foreground rounded-full animate-spin" />
|
|
11
11
|
<span>Loading editor...</span>
|
|
12
12
|
</div>
|
|
13
13
|
</div>
|
|
@@ -11,6 +11,10 @@ import {
|
|
|
11
11
|
import { createFileAction, deleteFileAction, renameFileAction, renameSpaceAction, deleteSpaceAction, convertToSpaceAction, deleteFolderAction } from '@/lib/actions';
|
|
12
12
|
import { useLocale } from '@/lib/LocaleContext';
|
|
13
13
|
|
|
14
|
+
function notifyFilesChanged() {
|
|
15
|
+
window.dispatchEvent(new Event('mindos:files-changed'));
|
|
16
|
+
}
|
|
17
|
+
|
|
14
18
|
const SYSTEM_FILES = new Set(['INSTRUCTION.md', 'README.md']);
|
|
15
19
|
|
|
16
20
|
interface FileTreeProps {
|
|
@@ -112,7 +116,7 @@ function SpaceContextMenu({ x, y, node, onClose, onRename }: {
|
|
|
112
116
|
if (!confirm(t.fileTree.confirmDeleteSpace(node.name))) return;
|
|
113
117
|
startTransition(async () => {
|
|
114
118
|
const result = await deleteSpaceAction(node.path);
|
|
115
|
-
if (result.success) { router.push('/'); router.refresh(); }
|
|
119
|
+
if (result.success) { router.push('/'); router.refresh(); notifyFilesChanged(); }
|
|
116
120
|
onClose();
|
|
117
121
|
});
|
|
118
122
|
}}>
|
|
@@ -137,11 +141,11 @@ function FolderContextMenu({ x, y, node, onClose, onRename }: {
|
|
|
137
141
|
<button className={MENU_ITEM} disabled={isPending} onClick={() => {
|
|
138
142
|
startTransition(async () => {
|
|
139
143
|
const result = await convertToSpaceAction(node.path);
|
|
140
|
-
if (result.success) router.refresh();
|
|
144
|
+
if (result.success) { router.refresh(); notifyFilesChanged(); }
|
|
141
145
|
onClose();
|
|
142
146
|
});
|
|
143
147
|
}}>
|
|
144
|
-
<Layers size={14} className="shrink-0
|
|
148
|
+
<Layers size={14} className="shrink-0 text-[var(--amber)]" /> {t.fileTree.convertToSpace}
|
|
145
149
|
</button>
|
|
146
150
|
<button className={MENU_ITEM} onClick={() => { onRename(); onClose(); }}>
|
|
147
151
|
<Pencil size={14} className="shrink-0" /> {t.fileTree.rename}
|
|
@@ -151,7 +155,7 @@ function FolderContextMenu({ x, y, node, onClose, onRename }: {
|
|
|
151
155
|
if (!confirm(t.fileTree.confirmDeleteFolder(node.name))) return;
|
|
152
156
|
startTransition(async () => {
|
|
153
157
|
const result = await deleteFolderAction(node.path);
|
|
154
|
-
if (result.success) { router.push('/'); router.refresh(); }
|
|
158
|
+
if (result.success) { router.push('/'); router.refresh(); notifyFilesChanged(); }
|
|
155
159
|
onClose();
|
|
156
160
|
});
|
|
157
161
|
}}>
|
|
@@ -181,6 +185,7 @@ function NewFileInline({ dirPath, depth, onDone }: { dirPath: string; depth: num
|
|
|
181
185
|
onDone();
|
|
182
186
|
router.push(`/view/${encodePath(result.filePath)}`);
|
|
183
187
|
router.refresh();
|
|
188
|
+
notifyFilesChanged();
|
|
184
189
|
} else {
|
|
185
190
|
setError(result.error || t.fileTree.failed);
|
|
186
191
|
}
|
|
@@ -217,11 +222,11 @@ function NewFileInline({ dirPath, depth, onDone }: { dirPath: string; depth: num
|
|
|
217
222
|
"
|
|
218
223
|
/>
|
|
219
224
|
{isPending
|
|
220
|
-
? <Loader2 size={13} className="text-
|
|
225
|
+
? <Loader2 size={13} className="text-muted-foreground animate-spin shrink-0" />
|
|
221
226
|
: (
|
|
222
227
|
<button
|
|
223
228
|
onClick={handleSubmit}
|
|
224
|
-
className="text-xs text-
|
|
229
|
+
className="text-xs text-[var(--amber)] hover:text-foreground shrink-0 px-1"
|
|
225
230
|
>
|
|
226
231
|
{t.fileTree.create}
|
|
227
232
|
</button>
|
|
@@ -294,6 +299,7 @@ function DirectoryNode({ node, depth, currentPath, onNavigate, maxOpenDepth }: {
|
|
|
294
299
|
setRenaming(false);
|
|
295
300
|
router.push(`/view/${encodePath(result.newPath)}`);
|
|
296
301
|
router.refresh();
|
|
302
|
+
notifyFilesChanged();
|
|
297
303
|
} else {
|
|
298
304
|
setRenaming(false);
|
|
299
305
|
}
|
|
@@ -338,7 +344,7 @@ function DirectoryNode({ node, depth, currentPath, onNavigate, maxOpenDepth }: {
|
|
|
338
344
|
onBlur={commitRename}
|
|
339
345
|
className="w-full bg-muted border border-border rounded px-2 py-0.5 text-xs text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
340
346
|
/>
|
|
341
|
-
{isPending && <Loader2 size={12} className="absolute right-3 top-1/2 -translate-y-1/2 animate-spin text-
|
|
347
|
+
{isPending && <Loader2 size={12} className="absolute right-3 top-1/2 -translate-y-1/2 animate-spin text-muted-foreground" />}
|
|
342
348
|
</div>
|
|
343
349
|
);
|
|
344
350
|
}
|
|
@@ -353,7 +359,7 @@ function DirectoryNode({ node, depth, currentPath, onNavigate, maxOpenDepth }: {
|
|
|
353
359
|
>
|
|
354
360
|
<button
|
|
355
361
|
onClick={toggle}
|
|
356
|
-
className="shrink-0 p-1 rounded hover:bg-muted text-
|
|
362
|
+
className="shrink-0 p-1 rounded hover:bg-muted text-muted-foreground transition-colors"
|
|
357
363
|
style={{ marginLeft: `${depth * 12 + 4}px` }}
|
|
358
364
|
aria-label={open ? 'Collapse' : 'Expand'}
|
|
359
365
|
>
|
|
@@ -373,7 +379,7 @@ function DirectoryNode({ node, depth, currentPath, onNavigate, maxOpenDepth }: {
|
|
|
373
379
|
`}
|
|
374
380
|
>
|
|
375
381
|
{isSpace
|
|
376
|
-
? <Layers size={14} className="shrink-0
|
|
382
|
+
? <Layers size={14} className="shrink-0 text-[var(--amber)]" />
|
|
377
383
|
: open
|
|
378
384
|
? <FolderOpen size={14} className="text-yellow-400 shrink-0" />
|
|
379
385
|
: <Folder size={14} className="text-yellow-400 shrink-0" />
|
|
@@ -505,6 +511,7 @@ function FileNodeItem({ node, depth, currentPath, onNavigate }: {
|
|
|
505
511
|
setRenaming(false);
|
|
506
512
|
router.push(`/view/${encodePath(result.newPath)}`);
|
|
507
513
|
router.refresh();
|
|
514
|
+
notifyFilesChanged();
|
|
508
515
|
} else {
|
|
509
516
|
setRenaming(false);
|
|
510
517
|
}
|
|
@@ -518,9 +525,15 @@ function FileNodeItem({ node, depth, currentPath, onNavigate }: {
|
|
|
518
525
|
await deleteFileAction(node.path);
|
|
519
526
|
if (currentPath === node.path) router.push('/');
|
|
520
527
|
router.refresh();
|
|
528
|
+
notifyFilesChanged();
|
|
521
529
|
});
|
|
522
530
|
}, [node.name, node.path, currentPath, router, t]);
|
|
523
531
|
|
|
532
|
+
const handleDragStart = useCallback((e: React.DragEvent) => {
|
|
533
|
+
e.dataTransfer.setData('text/mindos-path', node.path);
|
|
534
|
+
e.dataTransfer.effectAllowed = 'copy';
|
|
535
|
+
}, [node.path]);
|
|
536
|
+
|
|
524
537
|
if (renaming) {
|
|
525
538
|
return (
|
|
526
539
|
<div className="relative px-2 py-0.5" style={{ paddingLeft: `${depth * 12 + 8}px` }}>
|
|
@@ -536,7 +549,7 @@ function FileNodeItem({ node, depth, currentPath, onNavigate }: {
|
|
|
536
549
|
onBlur={commitRename}
|
|
537
550
|
className="w-full bg-muted border border-border rounded px-2 py-0.5 text-xs text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
538
551
|
/>
|
|
539
|
-
{isPending && <Loader2 size={12} className="absolute right-3 top-1/2 -translate-y-1/2 animate-spin text-
|
|
552
|
+
{isPending && <Loader2 size={12} className="absolute right-3 top-1/2 -translate-y-1/2 animate-spin text-muted-foreground" />}
|
|
540
553
|
</div>
|
|
541
554
|
);
|
|
542
555
|
}
|
|
@@ -546,6 +559,8 @@ function FileNodeItem({ node, depth, currentPath, onNavigate }: {
|
|
|
546
559
|
<button
|
|
547
560
|
onClick={handleClick}
|
|
548
561
|
onDoubleClick={startRename}
|
|
562
|
+
draggable
|
|
563
|
+
onDragStart={handleDragStart}
|
|
549
564
|
data-filepath={node.path}
|
|
550
565
|
className={`
|
|
551
566
|
w-full flex items-center gap-1.5 px-2 py-1 rounded text-left
|
|
@@ -238,7 +238,7 @@ export default function GuideCard({ onNavigate, spaces = [], recentFiles = [] }:
|
|
|
238
238
|
const fileName = file.path.split('/').pop() || file.path;
|
|
239
239
|
return (
|
|
240
240
|
<button key={file.path} onClick={() => handleFileOpen(file.path)}
|
|
241
|
-
className="text-left text-xs px-3 py-2 rounded-lg border border-border text-foreground transition-colors hover:border-amber
|
|
241
|
+
className="text-left text-xs px-3 py-2 rounded-lg border border-border text-foreground transition-colors hover:border-[var(--amber)]/30 hover:bg-muted/50 truncate">
|
|
242
242
|
📄 {fileName}
|
|
243
243
|
</button>
|
|
244
244
|
);
|
|
@@ -255,7 +255,7 @@ export default function GuideCard({ onNavigate, spaces = [], recentFiles = [] }:
|
|
|
255
255
|
const label = stripEmoji(s.name);
|
|
256
256
|
return (
|
|
257
257
|
<button key={s.name} onClick={() => handleFileOpen(s.path)}
|
|
258
|
-
className="text-left text-xs px-3 py-2 rounded-lg border border-border text-foreground transition-colors hover:border-amber
|
|
258
|
+
className="text-left text-xs px-3 py-2 rounded-lg border border-border text-foreground transition-colors hover:border-[var(--amber)]/30 hover:bg-muted/50">
|
|
259
259
|
<span className="mr-1.5">{emoji || '📁'}</span>
|
|
260
260
|
<span>{label}</span>
|
|
261
261
|
<span className="block text-2xs mt-0.5 text-muted-foreground">
|
|
@@ -305,8 +305,8 @@ function TaskCard({ icon, title, cta, done, active, optional, onClick }: {
|
|
|
305
305
|
className={`
|
|
306
306
|
flex flex-col items-center gap-1.5 px-3 py-3 rounded-lg border text-center
|
|
307
307
|
transition-all duration-150
|
|
308
|
-
${done ? 'opacity-60' : 'hover:border-amber
|
|
309
|
-
${active ? 'border-amber
|
|
308
|
+
${done ? 'opacity-60' : 'hover:border-[var(--amber)]/30 hover:bg-muted/50 cursor-pointer'}
|
|
309
|
+
${active ? 'border-[var(--amber)]/40 bg-muted/50' : ''}
|
|
310
310
|
${done || active ? 'border-[var(--amber)]' : 'border-border'}
|
|
311
311
|
`}
|
|
312
312
|
>
|
|
@@ -115,25 +115,20 @@ export default function HomeContent({ recent, existingFiles, spaces, dirPaths }:
|
|
|
115
115
|
}, [suggestions.length]);
|
|
116
116
|
|
|
117
117
|
const existingSet = new Set(existingFiles ?? []);
|
|
118
|
+
const spaceList = spaces ?? [];
|
|
119
|
+
const { groups, rootFiles } = useMemo(() => groupBySpace(recent, spaceList), [recent, spaceList]);
|
|
118
120
|
|
|
119
|
-
// Empty knowledge base → show onboarding
|
|
120
121
|
if (recent.length === 0) {
|
|
121
122
|
return <OnboardingView />;
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
const formatTime = (mtime: number) => relativeTime(mtime, t.home.relativeTime);
|
|
125
126
|
|
|
126
|
-
// User-manageable plugins: only show available entry files
|
|
127
127
|
const availablePlugins = getPluginRenderers().filter(r => r.entryPath && existingSet.has(r.entryPath));
|
|
128
|
-
// App-builtin features: always visible, with active/inactive state
|
|
129
128
|
const builtinFeatures = getAllRenderers().filter((r) => r.appBuiltinFeature && r.id !== 'csv');
|
|
130
129
|
|
|
131
130
|
const lastFile = recent[0];
|
|
132
131
|
|
|
133
|
-
// Group recent files by Space
|
|
134
|
-
const spaceList = spaces ?? [];
|
|
135
|
-
const { groups, rootFiles } = useMemo(() => groupBySpace(recent, spaceList), [recent, spaceList]);
|
|
136
|
-
|
|
137
132
|
return (
|
|
138
133
|
<div className="content-width px-4 md:px-6 py-8 md:py-12">
|
|
139
134
|
<GuideCard
|
|
@@ -160,7 +155,7 @@ export default function HomeContent({ recent, existingFiles, spaces, dirPaths }:
|
|
|
160
155
|
onClick={triggerAsk}
|
|
161
156
|
title="⌘/"
|
|
162
157
|
data-walkthrough="ask-button"
|
|
163
|
-
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-amber
|
|
158
|
+
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)]/8"
|
|
164
159
|
>
|
|
165
160
|
<Sparkles size={15} className="shrink-0 text-[var(--amber)]" />
|
|
166
161
|
<span className="text-sm flex-1 text-left text-foreground">
|
|
@@ -237,7 +232,7 @@ export default function HomeContent({ recent, existingFiles, spaces, dirPaths }:
|
|
|
237
232
|
className={`flex items-start gap-3 px-3.5 py-3 rounded-xl border transition-all duration-150 hover:translate-x-0.5 ${
|
|
238
233
|
isEmpty
|
|
239
234
|
? 'border-dashed border-border/50 opacity-50 hover:opacity-70'
|
|
240
|
-
: 'border-border hover:border-amber
|
|
235
|
+
: 'border-border hover:border-[var(--amber)]/30 hover:bg-muted/40'
|
|
241
236
|
}`}
|
|
242
237
|
>
|
|
243
238
|
{emoji ? (
|
|
@@ -288,7 +283,7 @@ export default function HomeContent({ recent, existingFiles, spaces, dirPaths }:
|
|
|
288
283
|
if (active && r.entryPath) {
|
|
289
284
|
return (
|
|
290
285
|
<Link key={r.id} href={`/view/${encodePath(r.entryPath)}`}>
|
|
291
|
-
<span className="inline-flex items-center gap-2 px-3 py-2 rounded-lg border border-border text-xs transition-all duration-150 hover:border-amber
|
|
286
|
+
<span className="inline-flex items-center gap-2 px-3 py-2 rounded-lg border border-border text-xs transition-all duration-150 hover:border-[var(--amber)]/30 hover:bg-muted/60">
|
|
292
287
|
<span className="text-sm leading-none" suppressHydrationWarning>{r.icon}</span>
|
|
293
288
|
<span className="font-medium text-foreground">{r.name}</span>
|
|
294
289
|
</span>
|
|
@@ -335,7 +330,7 @@ export default function HomeContent({ recent, existingFiles, spaces, dirPaths }:
|
|
|
335
330
|
<Link
|
|
336
331
|
key={r.id}
|
|
337
332
|
href={`/view/${encodePath(r.entryPath!)}`}
|
|
338
|
-
className="inline-flex items-center gap-2 px-3 py-2 rounded-lg border border-border text-xs transition-all duration-150 hover:border-amber
|
|
333
|
+
className="inline-flex items-center gap-2 px-3 py-2 rounded-lg border border-border text-xs transition-all duration-150 hover:border-[var(--amber)]/30 hover:bg-muted/60"
|
|
339
334
|
>
|
|
340
335
|
<span className="text-sm leading-none" suppressHydrationWarning>{r.icon}</span>
|
|
341
336
|
<span className="font-medium text-foreground">{r.name}</span>
|
|
@@ -31,8 +31,8 @@ function CopyButton({ code }: { code: string }) {
|
|
|
31
31
|
className="
|
|
32
32
|
absolute top-2.5 right-2.5
|
|
33
33
|
p-1.5 rounded-md
|
|
34
|
-
bg-
|
|
35
|
-
text-
|
|
34
|
+
bg-muted hover:bg-accent
|
|
35
|
+
text-muted-foreground hover:text-foreground
|
|
36
36
|
transition-colors duration-100
|
|
37
37
|
opacity-0 group-hover:opacity-100
|
|
38
38
|
"
|
|
@@ -100,7 +100,7 @@ export default function OnboardingView() {
|
|
|
100
100
|
key={tpl.id}
|
|
101
101
|
disabled={isDisabled}
|
|
102
102
|
onClick={() => handleSelect(tpl.id)}
|
|
103
|
-
className="group relative flex flex-col items-start gap-3 p-5 rounded-xl border border-border bg-card text-left transition-all duration-150 hover:border-amber
|
|
103
|
+
className="group relative flex flex-col items-start gap-3 p-5 rounded-xl border border-border bg-card text-left transition-all duration-150 hover:border-[var(--amber)]/50 hover:bg-[var(--amber)]/5 disabled:opacity-60 disabled:cursor-not-allowed"
|
|
104
104
|
>
|
|
105
105
|
{/* Icon + title */}
|
|
106
106
|
<div className="flex items-center gap-2.5 w-full">
|
package/app/components/Panel.tsx
CHANGED
|
@@ -149,7 +149,7 @@ export default function Panel({
|
|
|
149
149
|
className="absolute top-0 -right-[3px] w-[6px] h-full cursor-col-resize z-40 group hidden md:block"
|
|
150
150
|
onMouseDown={handleMouseDown}
|
|
151
151
|
>
|
|
152
|
-
<div className="absolute right-[2px] top-0 w-[2px] h-full opacity-0 group-hover:opacity-100 bg-amber
|
|
152
|
+
<div className="absolute right-[2px] top-0 w-[2px] h-full opacity-0 group-hover:opacity-100 bg-[var(--amber)]/60 transition-opacity" />
|
|
153
153
|
</div>
|
|
154
154
|
)}
|
|
155
155
|
</aside>
|
|
@@ -96,7 +96,7 @@ export default function RightAgentDetailPanel({
|
|
|
96
96
|
aria-hidden={!open || !resolved}
|
|
97
97
|
>
|
|
98
98
|
{resolved && (
|
|
99
|
-
<div className="flex flex-col flex-1 min-h-0 overflow-
|
|
99
|
+
<div className="flex flex-col flex-1 min-h-0 overflow-y-auto">
|
|
100
100
|
<AgentsPanelAgentDetail
|
|
101
101
|
agent={resolved.agent}
|
|
102
102
|
agentStatus={resolved.status}
|
|
@@ -81,7 +81,7 @@ export default function RightAskPanel({
|
|
|
81
81
|
className="absolute top-0 -left-[3px] w-[6px] h-full cursor-col-resize z-40 group hidden md:block"
|
|
82
82
|
onMouseDown={handleMouseDown}
|
|
83
83
|
>
|
|
84
|
-
<div className="absolute left-[2px] top-0 w-[2px] h-full opacity-0 group-hover:opacity-100 bg-amber
|
|
84
|
+
<div className="absolute left-[2px] top-0 w-[2px] h-full opacity-0 group-hover:opacity-100 bg-[var(--amber)]/60 transition-opacity" />
|
|
85
85
|
</div>
|
|
86
86
|
</aside>
|
|
87
87
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
3
|
+
import { useState, useEffect, useCallback, useRef, useLayoutEffect } from 'react';
|
|
4
4
|
import { useRouter } from 'next/navigation';
|
|
5
5
|
import { Search, X, FileText, Table } from 'lucide-react';
|
|
6
6
|
import { SearchResult } from '@/lib/types';
|
|
@@ -32,6 +32,7 @@ export default function SearchModal({ open, onClose }: SearchModalProps) {
|
|
|
32
32
|
const [loading, setLoading] = useState(false);
|
|
33
33
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
34
34
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
35
|
+
const resultsRef = useRef<HTMLDivElement>(null);
|
|
35
36
|
const router = useRouter();
|
|
36
37
|
const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
37
38
|
const { t } = useLocale();
|
|
@@ -99,6 +100,13 @@ export default function SearchModal({ open, onClose }: SearchModalProps) {
|
|
|
99
100
|
return () => window.removeEventListener('keydown', handler);
|
|
100
101
|
}, [open, onClose, results, selectedIndex, navigate]);
|
|
101
102
|
|
|
103
|
+
useLayoutEffect(() => {
|
|
104
|
+
const container = resultsRef.current;
|
|
105
|
+
if (!container) return;
|
|
106
|
+
const selected = container.children[selectedIndex] as HTMLElement | undefined;
|
|
107
|
+
selected?.scrollIntoView({ block: 'nearest' });
|
|
108
|
+
}, [selectedIndex]);
|
|
109
|
+
|
|
102
110
|
if (!open) return null;
|
|
103
111
|
|
|
104
112
|
return (
|
|
@@ -134,7 +142,7 @@ export default function SearchModal({ open, onClose }: SearchModalProps) {
|
|
|
134
142
|
</div>
|
|
135
143
|
|
|
136
144
|
{/* Results */}
|
|
137
|
-
<div className="max-h-[50vh] md:max-h-80 overflow-y-auto flex-1">
|
|
145
|
+
<div ref={resultsRef} className="max-h-[50vh] md:max-h-80 overflow-y-auto flex-1">
|
|
138
146
|
{results.length === 0 && query && !loading && (
|
|
139
147
|
<div className="px-4 py-8 text-center text-sm text-muted-foreground">{t.search.noResults}</div>
|
|
140
148
|
)}
|