@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
|
@@ -103,7 +103,7 @@ export function EchoInsightCollapsible({
|
|
|
103
103
|
const generateDisabled = aiLoading || !aiReady || streaming;
|
|
104
104
|
|
|
105
105
|
return (
|
|
106
|
-
<div className="mt-10 overflow-hidden rounded-xl border border-border bg-card shadow-sm transition-[border-color,box-shadow] duration-150 ease-out hover:border-[var(--amber)]/15 hover:shadow
|
|
106
|
+
<div className="mt-10 overflow-hidden rounded-xl border border-border bg-card shadow-sm transition-[border-color,box-shadow] duration-150 ease-out hover:border-[var(--amber)]/15 hover:shadow">
|
|
107
107
|
<button
|
|
108
108
|
id={btnId}
|
|
109
109
|
type="button"
|
|
@@ -129,56 +129,65 @@ export function EchoInsightCollapsible({
|
|
|
129
129
|
/>
|
|
130
130
|
<span className="sr-only">{open ? hideLabel : showLabel}</span>
|
|
131
131
|
</button>
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
className="inline-flex items-center gap-2 rounded-lg bg-[var(--amber)] px-3 py-2 font-sans text-sm font-medium text-white transition-opacity duration-150 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
146
|
-
>
|
|
147
|
-
{streaming ? <Loader2 size={16} className="animate-spin shrink-0" aria-hidden /> : null}
|
|
148
|
-
{streaming ? generatingLabel : generateLabel}
|
|
149
|
-
</button>
|
|
150
|
-
{err ? (
|
|
132
|
+
<div
|
|
133
|
+
id={panelId}
|
|
134
|
+
role="region"
|
|
135
|
+
aria-labelledby={btnId}
|
|
136
|
+
className={cn(
|
|
137
|
+
'grid transition-[grid-template-rows] duration-250 ease-out',
|
|
138
|
+
open ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',
|
|
139
|
+
)}
|
|
140
|
+
>
|
|
141
|
+
<div className="overflow-hidden">
|
|
142
|
+
<div className="border-t border-border/60 px-5 pb-5 pt-4">
|
|
143
|
+
<p className="font-sans text-sm leading-relaxed text-muted-foreground">{hint}</p>
|
|
144
|
+
<div className="mt-4 flex flex-wrap items-center gap-2">
|
|
151
145
|
<button
|
|
152
146
|
type="button"
|
|
147
|
+
disabled={generateDisabled}
|
|
153
148
|
onClick={runGenerate}
|
|
154
|
-
|
|
155
|
-
className="font-sans text-sm text-[var(--amber)] underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
|
|
149
|
+
className="inline-flex items-center gap-2 rounded-lg bg-[var(--amber)] px-3 py-2 font-sans text-sm font-medium text-[var(--amber-foreground)] transition-opacity duration-150 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
156
150
|
>
|
|
157
|
-
{
|
|
151
|
+
{streaming ? (
|
|
152
|
+
<Loader2 size={16} className="animate-spin shrink-0" aria-hidden />
|
|
153
|
+
) : (
|
|
154
|
+
<Sparkles size={15} className="shrink-0" aria-hidden />
|
|
155
|
+
)}
|
|
156
|
+
{streaming ? generatingLabel : generateLabel}
|
|
158
157
|
</button>
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
) : null}
|
|
169
|
-
{insightMd ? (
|
|
170
|
-
<div className={cn(proseInsight, 'mt-4 border-t border-border/60 pt-4')}>
|
|
171
|
-
<ReactMarkdown remarkPlugins={[remarkGfm]}>{insightMd}</ReactMarkdown>
|
|
172
|
-
{streaming ? (
|
|
173
|
-
<span
|
|
174
|
-
className="ml-0.5 inline-block h-3.5 w-1 animate-pulse rounded-sm bg-[var(--amber)] align-middle"
|
|
175
|
-
aria-hidden
|
|
176
|
-
/>
|
|
158
|
+
{err ? (
|
|
159
|
+
<button
|
|
160
|
+
type="button"
|
|
161
|
+
onClick={runGenerate}
|
|
162
|
+
disabled={streaming || !aiReady}
|
|
163
|
+
className="font-sans text-sm text-[var(--amber)] underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
|
|
164
|
+
>
|
|
165
|
+
{retryLabel}
|
|
166
|
+
</button>
|
|
177
167
|
) : null}
|
|
178
168
|
</div>
|
|
179
|
-
|
|
169
|
+
{!aiLoading && !aiReady ? (
|
|
170
|
+
<p className="mt-2 font-sans text-2xs text-muted-foreground">{noAiHint}</p>
|
|
171
|
+
) : null}
|
|
172
|
+
{err ? (
|
|
173
|
+
<p className="mt-3 font-sans text-sm text-error" role="alert">
|
|
174
|
+
{errorPrefix} {err}
|
|
175
|
+
</p>
|
|
176
|
+
) : null}
|
|
177
|
+
{insightMd ? (
|
|
178
|
+
<div className={cn(proseInsight, 'mt-4 border-t border-border/60 pt-4')}>
|
|
179
|
+
<ReactMarkdown remarkPlugins={[remarkGfm]}>{insightMd}</ReactMarkdown>
|
|
180
|
+
{streaming ? (
|
|
181
|
+
<span
|
|
182
|
+
className="ml-0.5 inline-block h-3.5 w-1 animate-pulse rounded-sm bg-[var(--amber)] align-middle"
|
|
183
|
+
aria-hidden
|
|
184
|
+
/>
|
|
185
|
+
) : null}
|
|
186
|
+
</div>
|
|
187
|
+
) : null}
|
|
188
|
+
</div>
|
|
180
189
|
</div>
|
|
181
|
-
|
|
190
|
+
</div>
|
|
182
191
|
</div>
|
|
183
192
|
);
|
|
184
193
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import type { ReactNode } from 'react';
|
|
4
|
-
import { Library } from 'lucide-react';
|
|
4
|
+
import { Library, FileText, CircleCheck } from 'lucide-react';
|
|
5
5
|
|
|
6
6
|
export function EchoFactSnapshot({
|
|
7
7
|
headingId,
|
|
@@ -9,6 +9,7 @@ export function EchoFactSnapshot({
|
|
|
9
9
|
snapshotBadge,
|
|
10
10
|
emptyTitle,
|
|
11
11
|
emptyBody,
|
|
12
|
+
icon,
|
|
12
13
|
actions,
|
|
13
14
|
}: {
|
|
14
15
|
headingId: string;
|
|
@@ -16,12 +17,12 @@ export function EchoFactSnapshot({
|
|
|
16
17
|
snapshotBadge: string;
|
|
17
18
|
emptyTitle: string;
|
|
18
19
|
emptyBody: string;
|
|
19
|
-
|
|
20
|
+
icon?: ReactNode;
|
|
20
21
|
actions?: ReactNode;
|
|
21
22
|
}) {
|
|
22
23
|
return (
|
|
23
24
|
<section
|
|
24
|
-
className="rounded-xl border border-border bg-card p-5 shadow-sm transition-[border-color,box-shadow] duration-150 ease-out hover:border-[var(--amber)]/20 hover:shadow
|
|
25
|
+
className="rounded-xl border border-border bg-card p-5 shadow-sm transition-[border-color,box-shadow] duration-150 ease-out hover:border-[var(--amber)]/20 hover:shadow sm:p-6"
|
|
25
26
|
aria-labelledby={headingId}
|
|
26
27
|
>
|
|
27
28
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
|
@@ -30,7 +31,7 @@ export function EchoFactSnapshot({
|
|
|
30
31
|
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[var(--amber-dim)] text-[var(--amber)]"
|
|
31
32
|
aria-hidden
|
|
32
33
|
>
|
|
33
|
-
<Library size={18} strokeWidth={1.75} />
|
|
34
|
+
{icon ?? <Library size={18} strokeWidth={1.75} />}
|
|
34
35
|
</span>
|
|
35
36
|
<div>
|
|
36
37
|
<h2
|
|
@@ -67,9 +68,12 @@ export function EchoContinuedGroups({
|
|
|
67
68
|
subEmptyHint: string;
|
|
68
69
|
footer?: ReactNode;
|
|
69
70
|
}) {
|
|
70
|
-
const cell = (label: string) => (
|
|
71
|
-
<div className="flex min-h-[5.75rem] flex-col justify-center rounded-xl border border-dashed border-border/80 bg-muted/10 px-4 py-4">
|
|
72
|
-
<
|
|
71
|
+
const cell = (label: string, icon: ReactNode) => (
|
|
72
|
+
<div className="flex min-h-[5.75rem] flex-col justify-center rounded-xl border border-dashed border-border/80 bg-muted/10 px-4 py-4 transition-colors duration-150 hover:border-[var(--amber)]/25 hover:bg-[var(--amber-dim)]/15">
|
|
73
|
+
<div className="flex items-center gap-2">
|
|
74
|
+
<span className="shrink-0 text-muted-foreground" aria-hidden>{icon}</span>
|
|
75
|
+
<h3 className="font-sans text-sm font-medium text-foreground">{label}</h3>
|
|
76
|
+
</div>
|
|
73
77
|
<p className="mt-2 font-sans text-2xs leading-relaxed text-muted-foreground">{subEmptyHint}</p>
|
|
74
78
|
</div>
|
|
75
79
|
);
|
|
@@ -77,8 +81,8 @@ export function EchoContinuedGroups({
|
|
|
77
81
|
return (
|
|
78
82
|
<div className="space-y-4">
|
|
79
83
|
<div className="grid gap-3 sm:grid-cols-2">
|
|
80
|
-
{cell(draftsLabel)}
|
|
81
|
-
{cell(todosLabel)}
|
|
84
|
+
{cell(draftsLabel, <FileText size={15} strokeWidth={1.75} />)}
|
|
85
|
+
{cell(todosLabel, <CircleCheck size={15} strokeWidth={1.75} />)}
|
|
82
86
|
</div>
|
|
83
87
|
{footer ? <div className="border-t border-border/60 pt-4">{footer}</div> : null}
|
|
84
88
|
</div>
|
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
3
4
|
import Link from 'next/link';
|
|
5
|
+
import { UserRound, Bookmark, Sun, History, Brain } from 'lucide-react';
|
|
4
6
|
import { useLocale } from '@/lib/LocaleContext';
|
|
5
7
|
import { cn } from '@/lib/utils';
|
|
6
8
|
import { ECHO_SEGMENT_HREF, ECHO_SEGMENT_ORDER, type EchoSegment } from '@/lib/echo-segments';
|
|
7
9
|
|
|
8
|
-
function
|
|
10
|
+
function segmentMeta(
|
|
9
11
|
segment: EchoSegment,
|
|
10
12
|
echo: ReturnType<typeof useLocale>['t']['panels']['echo'],
|
|
11
|
-
): string {
|
|
13
|
+
): { label: string; icon: ReactNode } {
|
|
12
14
|
switch (segment) {
|
|
13
15
|
case 'about-you':
|
|
14
|
-
return echo.aboutYouTitle;
|
|
16
|
+
return { label: echo.aboutYouTitle, icon: <UserRound size={13} /> };
|
|
15
17
|
case 'continued':
|
|
16
|
-
return echo.continuedTitle;
|
|
18
|
+
return { label: echo.continuedTitle, icon: <Bookmark size={13} /> };
|
|
17
19
|
case 'daily':
|
|
18
|
-
return echo.dailyEchoTitle;
|
|
20
|
+
return { label: echo.dailyEchoTitle, icon: <Sun size={13} /> };
|
|
19
21
|
case 'past-you':
|
|
20
|
-
return echo.pastYouTitle;
|
|
22
|
+
return { label: echo.pastYouTitle, icon: <History size={13} /> };
|
|
21
23
|
case 'growth':
|
|
22
|
-
return echo.intentGrowthTitle;
|
|
24
|
+
return { label: echo.intentGrowthTitle, icon: <Brain size={13} /> };
|
|
23
25
|
}
|
|
24
26
|
}
|
|
25
27
|
|
|
@@ -29,11 +31,11 @@ export default function EchoSegmentNav({ activeSegment }: { activeSegment: EchoS
|
|
|
29
31
|
const aria = t.echoPages.segmentNavAria;
|
|
30
32
|
|
|
31
33
|
return (
|
|
32
|
-
<nav aria-label={aria} className="mt-
|
|
33
|
-
<ul className="
|
|
34
|
+
<nav aria-label={aria} className="mt-5 border-t border-border/30 pt-4 font-sans">
|
|
35
|
+
<ul className="flex snap-x snap-mandatory gap-1.5 overflow-x-auto pb-0.5 [scrollbar-width:thin]">
|
|
34
36
|
{ECHO_SEGMENT_ORDER.map((segment) => {
|
|
35
37
|
const href = ECHO_SEGMENT_HREF[segment];
|
|
36
|
-
const label =
|
|
38
|
+
const { label, icon } = segmentMeta(segment, echo);
|
|
37
39
|
const isActive = segment === activeSegment;
|
|
38
40
|
return (
|
|
39
41
|
<li key={segment} className="snap-start shrink-0">
|
|
@@ -41,12 +43,13 @@ export default function EchoSegmentNav({ activeSegment }: { activeSegment: EchoS
|
|
|
41
43
|
href={href}
|
|
42
44
|
aria-current={isActive ? 'page' : undefined}
|
|
43
45
|
className={cn(
|
|
44
|
-
'inline-flex min-h-9 max-w-[11rem] items-center rounded-full border px-3 py-1.5 text-sm transition-[background-color,border-color,color] duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
46
|
+
'inline-flex min-h-9 max-w-[11rem] items-center gap-1.5 rounded-full border px-3 py-1.5 text-sm transition-[background-color,border-color,color] duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
45
47
|
isActive
|
|
46
48
|
? 'border-[var(--amber)]/45 bg-[var(--amber-dim)]/50 font-medium text-foreground'
|
|
47
49
|
: 'border-transparent bg-muted/35 text-muted-foreground hover:bg-muted/55 hover:text-foreground',
|
|
48
50
|
)}
|
|
49
51
|
>
|
|
52
|
+
<span className="shrink-0" aria-hidden>{icon}</span>
|
|
50
53
|
<span className="truncate">{label}</span>
|
|
51
54
|
</Link>
|
|
52
55
|
</li>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
5
|
+
import { ArrowUpRight, Bookmark, Brain, Check, History, Sun, UserRound } from 'lucide-react';
|
|
4
6
|
import type { EchoSegment } from '@/lib/echo-segments';
|
|
5
7
|
import { buildEchoInsightUserPrompt } from '@/lib/echo-insight-prompt';
|
|
6
8
|
import type { Locale, Messages } from '@/lib/i18n';
|
|
@@ -44,12 +46,20 @@ function segmentLead(segment: EchoSegment, p: ReturnType<typeof useLocale>['t'][
|
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
const SEGMENT_ICON: Record<EchoSegment, ReactNode> = {
|
|
50
|
+
'about-you': <UserRound size={18} strokeWidth={1.75} />,
|
|
51
|
+
continued: <Bookmark size={18} strokeWidth={1.75} />,
|
|
52
|
+
daily: <Sun size={18} strokeWidth={1.75} />,
|
|
53
|
+
'past-you': <History size={18} strokeWidth={1.75} />,
|
|
54
|
+
growth: <Brain size={18} strokeWidth={1.75} />,
|
|
55
|
+
};
|
|
56
|
+
|
|
47
57
|
const fieldLabelClass =
|
|
48
58
|
'block font-sans text-2xs font-semibold uppercase tracking-wide text-muted-foreground';
|
|
49
59
|
const inputClass =
|
|
50
|
-
'mt-2 w-full min-h-[5rem] resize-y rounded-lg border border-border bg-background px-3 py-2.5 font-sans text-sm text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring';
|
|
60
|
+
'mt-2 w-full min-h-[5rem] resize-y rounded-lg border border-border bg-background px-3 py-2.5 font-sans text-sm text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:border-[var(--amber)]/40';
|
|
51
61
|
const cardSectionClass =
|
|
52
|
-
'rounded-xl border border-border bg-card p-5 shadow-sm transition-[border-color,box-shadow] duration-150 ease-out hover:border-[var(--amber)]/20 hover:shadow
|
|
62
|
+
'rounded-xl border border-border bg-card p-5 shadow-sm transition-[border-color,box-shadow] duration-150 ease-out hover:border-[var(--amber)]/20 hover:shadow sm:p-6';
|
|
53
63
|
|
|
54
64
|
function echoSnapshotCopy(segment: EchoSegment, p: Messages['echoPages']): { title: string; body: string } {
|
|
55
65
|
switch (segment) {
|
|
@@ -77,6 +87,15 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
|
|
|
77
87
|
|
|
78
88
|
const [dailyLine, setDailyLine] = useState('');
|
|
79
89
|
const [growthIntent, setGrowthIntent] = useState('');
|
|
90
|
+
const [dailySaved, setDailySaved] = useState(false);
|
|
91
|
+
const [growthSaved, setGrowthSaved] = useState(false);
|
|
92
|
+
const dailySavedTimer = useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
93
|
+
const growthSavedTimer = useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
94
|
+
|
|
95
|
+
useEffect(() => () => {
|
|
96
|
+
clearTimeout(dailySavedTimer.current);
|
|
97
|
+
clearTimeout(growthSavedTimer.current);
|
|
98
|
+
}, []);
|
|
80
99
|
|
|
81
100
|
const snapshot = useMemo(() => echoSnapshotCopy(segment, p), [segment, p]);
|
|
82
101
|
|
|
@@ -97,6 +116,9 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
|
|
|
97
116
|
} catch {
|
|
98
117
|
/* ignore */
|
|
99
118
|
}
|
|
119
|
+
clearTimeout(dailySavedTimer.current);
|
|
120
|
+
setDailySaved(true);
|
|
121
|
+
dailySavedTimer.current = setTimeout(() => setDailySaved(false), 1800);
|
|
100
122
|
}, [dailyLine]);
|
|
101
123
|
|
|
102
124
|
const persistGrowth = useCallback(() => {
|
|
@@ -105,6 +127,9 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
|
|
|
105
127
|
} catch {
|
|
106
128
|
/* ignore */
|
|
107
129
|
}
|
|
130
|
+
clearTimeout(growthSavedTimer.current);
|
|
131
|
+
setGrowthSaved(true);
|
|
132
|
+
growthSavedTimer.current = setTimeout(() => setGrowthSaved(false), 1800);
|
|
108
133
|
}, [growthIntent]);
|
|
109
134
|
|
|
110
135
|
const openDailyAsk = useCallback(() => {
|
|
@@ -150,7 +175,14 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
|
|
|
150
175
|
);
|
|
151
176
|
|
|
152
177
|
const secondaryBtnClass =
|
|
153
|
-
'inline-flex items-center rounded-lg border border-border bg-background px-4 py-2.5 font-sans text-sm font-medium text-foreground transition-colors duration-150 hover:border-[var(--amber)]/35 hover:bg-[var(--amber-dim)]/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring';
|
|
178
|
+
'inline-flex items-center gap-1.5 rounded-lg border border-border bg-background px-4 py-2.5 font-sans text-sm font-medium text-foreground transition-colors duration-150 hover:border-[var(--amber)]/35 hover:bg-[var(--amber-dim)]/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring';
|
|
179
|
+
|
|
180
|
+
const agentBtn = (onClick: () => void) => (
|
|
181
|
+
<button type="button" onClick={onClick} className={secondaryBtnClass}>
|
|
182
|
+
{p.continueAgent}
|
|
183
|
+
<ArrowUpRight size={14} className="shrink-0 text-muted-foreground" aria-hidden />
|
|
184
|
+
</button>
|
|
185
|
+
);
|
|
154
186
|
|
|
155
187
|
return (
|
|
156
188
|
<article
|
|
@@ -158,16 +190,13 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
|
|
|
158
190
|
aria-labelledby={pageTitleId}
|
|
159
191
|
>
|
|
160
192
|
<EchoHero
|
|
161
|
-
breadcrumbNav={p.breadcrumbNav}
|
|
162
|
-
parentHref="/echo/about-you"
|
|
163
|
-
parent={p.parent}
|
|
164
193
|
heroKicker={p.heroKicker}
|
|
165
194
|
pageTitle={title}
|
|
166
195
|
lead={lead}
|
|
167
196
|
titleId={pageTitleId}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
197
|
+
>
|
|
198
|
+
<EchoSegmentNav activeSegment={segment} />
|
|
199
|
+
</EchoHero>
|
|
171
200
|
|
|
172
201
|
<div className="mt-6 space-y-6 sm:mt-8">
|
|
173
202
|
<EchoFactSnapshot
|
|
@@ -176,24 +205,15 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
|
|
|
176
205
|
snapshotBadge={p.snapshotBadge}
|
|
177
206
|
emptyTitle={snapshot.title}
|
|
178
207
|
emptyBody={snapshot.body}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
<button type="button" onClick={openSegmentAsk} className={secondaryBtnClass}>
|
|
182
|
-
{p.continueAgent}
|
|
183
|
-
</button>
|
|
184
|
-
) : undefined
|
|
185
|
-
}
|
|
208
|
+
icon={SEGMENT_ICON[segment]}
|
|
209
|
+
actions={segment === 'about-you' ? agentBtn(openSegmentAsk) : undefined}
|
|
186
210
|
/>
|
|
187
211
|
{segment === 'continued' ? (
|
|
188
212
|
<EchoContinuedGroups
|
|
189
213
|
draftsLabel={p.continuedDrafts}
|
|
190
214
|
todosLabel={p.continuedTodos}
|
|
191
215
|
subEmptyHint={p.subEmptyHint}
|
|
192
|
-
footer={
|
|
193
|
-
<button type="button" onClick={openSegmentAsk} className={secondaryBtnClass}>
|
|
194
|
-
{p.continueAgent}
|
|
195
|
-
</button>
|
|
196
|
-
}
|
|
216
|
+
footer={agentBtn(openSegmentAsk)}
|
|
197
217
|
/>
|
|
198
218
|
) : null}
|
|
199
219
|
</div>
|
|
@@ -212,11 +232,14 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
|
|
|
212
232
|
placeholder={p.dailyLinePlaceholder}
|
|
213
233
|
className={inputClass}
|
|
214
234
|
/>
|
|
215
|
-
<p className="mt-3 font-sans text-2xs text-muted-foreground">
|
|
235
|
+
<p className="mt-3 flex items-center gap-2 font-sans text-2xs text-muted-foreground">
|
|
236
|
+
<span>{p.dailySavedNote}</span>
|
|
237
|
+
<span className="inline-flex items-center gap-1 text-[var(--success)]" aria-live="polite">
|
|
238
|
+
{dailySaved ? <><Check size={12} aria-hidden /> {p.savedFlash}</> : null}
|
|
239
|
+
</span>
|
|
240
|
+
</p>
|
|
216
241
|
<div className="mt-4">
|
|
217
|
-
|
|
218
|
-
{p.continueAgent}
|
|
219
|
-
</button>
|
|
242
|
+
{agentBtn(openDailyAsk)}
|
|
220
243
|
</div>
|
|
221
244
|
</section>
|
|
222
245
|
) : null}
|
|
@@ -235,31 +258,29 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
|
|
|
235
258
|
placeholder={p.growthIntentPlaceholder}
|
|
236
259
|
className={`${inputClass} min-h-[6.5rem]`}
|
|
237
260
|
/>
|
|
238
|
-
<p className="mt-3 font-sans text-2xs text-muted-foreground">
|
|
261
|
+
<p className="mt-3 flex items-center gap-2 font-sans text-2xs text-muted-foreground">
|
|
262
|
+
<span>{p.growthSavedNote}</span>
|
|
263
|
+
<span className="inline-flex items-center gap-1 text-[var(--success)]" aria-live="polite">
|
|
264
|
+
{growthSaved ? <><Check size={12} aria-hidden /> {p.savedFlash}</> : null}
|
|
265
|
+
</span>
|
|
266
|
+
</p>
|
|
239
267
|
<div className="mt-4 border-t border-border/60 pt-4">
|
|
240
|
-
|
|
241
|
-
{p.continueAgent}
|
|
242
|
-
</button>
|
|
268
|
+
{agentBtn(openSegmentAsk)}
|
|
243
269
|
</div>
|
|
244
270
|
</section>
|
|
245
271
|
) : null}
|
|
246
272
|
|
|
247
273
|
{segment === 'past-you' ? (
|
|
248
274
|
<section className={`${cardSectionClass} mt-6`}>
|
|
249
|
-
<
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
>
|
|
256
|
-
{p.pastYouAnother}
|
|
257
|
-
</button>
|
|
258
|
-
<p className="mt-3 font-sans text-2xs text-muted-foreground">{p.pastYouDisabledHint}</p>
|
|
275
|
+
<div className="flex items-center gap-3">
|
|
276
|
+
<span className={fieldLabelClass}>{p.pastYouDrawLabel}</span>
|
|
277
|
+
<span className="rounded-full bg-muted px-2 py-0.5 font-sans text-2xs font-medium text-muted-foreground">
|
|
278
|
+
{p.pastYouComingSoon}
|
|
279
|
+
</span>
|
|
280
|
+
</div>
|
|
281
|
+
<p className="mt-3 font-sans text-sm leading-relaxed text-muted-foreground">{p.pastYouDisabledHint}</p>
|
|
259
282
|
<div className="mt-4 border-t border-border/60 pt-4">
|
|
260
|
-
|
|
261
|
-
{p.continueAgent}
|
|
262
|
-
</button>
|
|
283
|
+
{agentBtn(openSegmentAsk)}
|
|
263
284
|
</div>
|
|
264
285
|
</section>
|
|
265
286
|
) : null}
|
|
@@ -31,17 +31,13 @@ export default function ExploreContent() {
|
|
|
31
31
|
{/* Header */}
|
|
32
32
|
<div className="mb-8">
|
|
33
33
|
<div className="flex items-center gap-2 mb-3">
|
|
34
|
-
<div className="w-1 h-5 rounded-full
|
|
35
|
-
<h1
|
|
36
|
-
className="text-2xl font-semibold tracking-tight font-display"
|
|
37
|
-
style={{ color: 'var(--foreground)' }}
|
|
38
|
-
>
|
|
34
|
+
<div className="w-1 h-5 rounded-full bg-[var(--amber)]" />
|
|
35
|
+
<h1 className="text-2xl font-semibold tracking-tight font-display text-foreground">
|
|
39
36
|
{e.title}
|
|
40
37
|
</h1>
|
|
41
38
|
</div>
|
|
42
39
|
<p
|
|
43
|
-
className="text-sm leading-relaxed"
|
|
44
|
-
style={{ color: 'var(--muted-foreground)', paddingLeft: '1rem' }}
|
|
40
|
+
className="text-sm leading-relaxed text-muted-foreground pl-4"
|
|
45
41
|
>
|
|
46
42
|
{e.subtitle}
|
|
47
43
|
</p>
|
|
@@ -13,35 +13,24 @@ interface UseCaseCardProps {
|
|
|
13
13
|
export default function UseCaseCard({ icon, title, description, prompt, tryItLabel }: UseCaseCardProps) {
|
|
14
14
|
return (
|
|
15
15
|
<div
|
|
16
|
-
className="group flex flex-col gap-3 p-4 rounded-xl border transition-all duration-150 hover:border-amber
|
|
17
|
-
style={{ borderColor: 'var(--border)', background: 'var(--card)' }}
|
|
16
|
+
className="group flex flex-col gap-3 p-4 rounded-xl border border-border bg-card transition-all duration-150 hover:border-[var(--amber)]/30 hover:bg-muted/50"
|
|
18
17
|
>
|
|
19
18
|
<div className="flex items-start gap-3">
|
|
20
19
|
<span className="text-xl leading-none shrink-0 mt-0.5" suppressHydrationWarning>
|
|
21
20
|
{icon}
|
|
22
21
|
</span>
|
|
23
22
|
<div className="flex-1 min-w-0">
|
|
24
|
-
<h3
|
|
25
|
-
className="text-sm font-semibold font-display truncate"
|
|
26
|
-
style={{ color: 'var(--foreground)' }}
|
|
27
|
-
>
|
|
23
|
+
<h3 className="text-sm font-semibold font-display truncate text-foreground">
|
|
28
24
|
{title}
|
|
29
25
|
</h3>
|
|
30
|
-
<p
|
|
31
|
-
className="text-xs leading-relaxed mt-1 line-clamp-2"
|
|
32
|
-
style={{ color: 'var(--muted-foreground)' }}
|
|
33
|
-
>
|
|
26
|
+
<p className="text-xs leading-relaxed mt-1 line-clamp-2 text-muted-foreground">
|
|
34
27
|
{description}
|
|
35
28
|
</p>
|
|
36
29
|
</div>
|
|
37
30
|
</div>
|
|
38
31
|
<button
|
|
39
32
|
onClick={() => openAskModal(prompt, 'user')}
|
|
40
|
-
className="self-start inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-all duration-150 hover:opacity-80 cursor-pointer"
|
|
41
|
-
style={{
|
|
42
|
-
background: 'var(--amber-dim)',
|
|
43
|
-
color: 'var(--amber)',
|
|
44
|
-
}}
|
|
33
|
+
className="self-start inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-all duration-150 hover:opacity-80 cursor-pointer bg-[var(--amber-dim)] text-[var(--amber)]"
|
|
45
34
|
>
|
|
46
35
|
{tryItLabel} →
|
|
47
36
|
</button>
|