@geminilight/mindos 0.5.52 → 0.5.55

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.
Files changed (32) hide show
  1. package/README.md +7 -7
  2. package/README_zh.md +5 -5
  3. package/app/app/echo/[segment]/page.tsx +15 -0
  4. package/app/app/echo/page.tsx +6 -0
  5. package/app/components/ActivityBar.tsx +3 -2
  6. package/app/components/Panel.tsx +1 -0
  7. package/app/components/RightAgentDetailPanel.tsx +121 -0
  8. package/app/components/RightAskPanel.tsx +14 -11
  9. package/app/components/SidebarLayout.tsx +69 -5
  10. package/app/components/ask/AskContent.tsx +10 -2
  11. package/app/components/echo/EchoHero.tsx +55 -0
  12. package/app/components/echo/EchoInsightCollapsible.tsx +184 -0
  13. package/app/components/echo/EchoPageSections.tsx +86 -0
  14. package/app/components/echo/EchoSegmentNav.tsx +58 -0
  15. package/app/components/echo/EchoSegmentPageClient.tsx +265 -0
  16. package/app/components/panels/AgentsPanel.tsx +156 -178
  17. package/app/components/panels/AgentsPanelAgentDetail.tsx +193 -0
  18. package/app/components/panels/AgentsPanelAgentGroups.tsx +116 -0
  19. package/app/components/panels/AgentsPanelAgentListRow.tsx +101 -0
  20. package/app/components/panels/AgentsPanelHubNav.tsx +48 -0
  21. package/app/components/panels/DiscoverPanel.tsx +6 -46
  22. package/app/components/panels/EchoPanel.tsx +49 -0
  23. package/app/components/panels/PanelNavRow.tsx +68 -0
  24. package/app/components/panels/agents-panel-resolve-status.ts +13 -0
  25. package/app/hooks/useSettingsAiAvailable.ts +29 -0
  26. package/app/lib/echo-insight-prompt.ts +44 -0
  27. package/app/lib/echo-segments.ts +27 -0
  28. package/app/lib/i18n-en.ts +62 -2
  29. package/app/lib/i18n-zh.ts +59 -2
  30. package/app/lib/settings-ai-client.ts +26 -0
  31. package/app/next-env.d.ts +1 -1
  32. package/package.json +1 -1
@@ -0,0 +1,184 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useEffect, useId, useRef, useState } from 'react';
4
+ import { ChevronDown, Loader2, Sparkles } from 'lucide-react';
5
+ import ReactMarkdown from 'react-markdown';
6
+ import remarkGfm from 'remark-gfm';
7
+ import { cn } from '@/lib/utils';
8
+ import { consumeUIMessageStream } from '@/lib/agent/stream-consumer';
9
+ import { useSettingsAiAvailable } from '@/hooks/useSettingsAiAvailable';
10
+
11
+ const proseInsight =
12
+ 'prose prose-sm prose-panel dark:prose-invert max-w-none text-foreground ' +
13
+ 'prose-p:my-1 prose-p:leading-relaxed ' +
14
+ 'prose-headings:font-semibold prose-headings:my-2 prose-headings:text-[13px] ' +
15
+ 'prose-ul:my-1 prose-li:my-0.5 prose-ol:my-1 ' +
16
+ 'prose-code:text-[0.8em] prose-code:bg-muted prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-code:before:content-none prose-code:after:content-none ' +
17
+ 'prose-pre:bg-muted prose-pre:text-foreground prose-pre:text-xs ' +
18
+ 'prose-blockquote:border-l-[var(--amber)] prose-blockquote:text-muted-foreground ' +
19
+ 'prose-a:text-[var(--amber)] prose-a:no-underline hover:prose-a:underline ' +
20
+ 'prose-strong:text-foreground prose-strong:font-semibold';
21
+
22
+ export function EchoInsightCollapsible({
23
+ title,
24
+ showLabel,
25
+ hideLabel,
26
+ hint,
27
+ generateLabel,
28
+ noAiHint,
29
+ generatingLabel,
30
+ errorPrefix,
31
+ retryLabel,
32
+ userPrompt,
33
+ }: {
34
+ title: string;
35
+ showLabel: string;
36
+ hideLabel: string;
37
+ hint: string;
38
+ generateLabel: string;
39
+ noAiHint: string;
40
+ generatingLabel: string;
41
+ errorPrefix: string;
42
+ retryLabel: string;
43
+ userPrompt: string;
44
+ }) {
45
+ const [open, setOpen] = useState(false);
46
+ const [streaming, setStreaming] = useState(false);
47
+ const [insightMd, setInsightMd] = useState('');
48
+ const [err, setErr] = useState('');
49
+ const panelId = useId();
50
+ const btnId = `${panelId}-btn`;
51
+ const abortRef = useRef<AbortController | null>(null);
52
+ const { ready: aiReady, loading: aiLoading } = useSettingsAiAvailable();
53
+
54
+ useEffect(() => () => abortRef.current?.abort(), []);
55
+
56
+ const runGenerate = useCallback(async () => {
57
+ abortRef.current?.abort();
58
+ const ctrl = new AbortController();
59
+ abortRef.current = ctrl;
60
+ setErr('');
61
+ setInsightMd('');
62
+ setStreaming(true);
63
+ try {
64
+ const res = await fetch('/api/ask', {
65
+ method: 'POST',
66
+ headers: { 'Content-Type': 'application/json' },
67
+ body: JSON.stringify({
68
+ messages: [{ role: 'user', content: userPrompt }],
69
+ maxSteps: 16,
70
+ }),
71
+ signal: ctrl.signal,
72
+ });
73
+
74
+ if (!res.ok) {
75
+ let msg = `HTTP ${res.status}`;
76
+ try {
77
+ const j = (await res.json()) as { error?: { message?: string }; message?: string };
78
+ msg = j?.error?.message ?? j?.message ?? msg;
79
+ } catch {
80
+ /* ignore */
81
+ }
82
+ throw new Error(msg);
83
+ }
84
+
85
+ if (!res.body) throw new Error('No response body');
86
+
87
+ await consumeUIMessageStream(
88
+ res.body,
89
+ (msg) => {
90
+ setInsightMd(msg.content ?? '');
91
+ },
92
+ ctrl.signal,
93
+ );
94
+ } catch (e) {
95
+ if (e instanceof Error && e.name === 'AbortError') return;
96
+ setErr(e instanceof Error ? e.message : String(e));
97
+ } finally {
98
+ setStreaming(false);
99
+ abortRef.current = null;
100
+ }
101
+ }, [userPrompt]);
102
+
103
+ const generateDisabled = aiLoading || !aiReady || streaming;
104
+
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-md">
107
+ <button
108
+ id={btnId}
109
+ type="button"
110
+ className="flex w-full items-center gap-3 px-5 py-4 text-left transition-colors duration-200 hover:bg-muted/25 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
111
+ aria-expanded={open}
112
+ aria-controls={panelId}
113
+ onClick={() => setOpen((v) => !v)}
114
+ >
115
+ <span
116
+ className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[var(--amber-dim)] text-[var(--amber)]"
117
+ aria-hidden
118
+ >
119
+ <Sparkles size={17} strokeWidth={1.75} />
120
+ </span>
121
+ <span className="flex-1 font-sans text-sm font-medium text-foreground">{title}</span>
122
+ <ChevronDown
123
+ size={16}
124
+ className={cn(
125
+ 'shrink-0 text-muted-foreground transition-transform duration-200',
126
+ open && 'rotate-180',
127
+ )}
128
+ aria-hidden
129
+ />
130
+ <span className="sr-only">{open ? hideLabel : showLabel}</span>
131
+ </button>
132
+ {open ? (
133
+ <div
134
+ id={panelId}
135
+ role="region"
136
+ aria-labelledby={btnId}
137
+ className="border-t border-border/60 px-5 pb-5 pt-4"
138
+ >
139
+ <p className="font-sans text-sm leading-relaxed text-muted-foreground">{hint}</p>
140
+ <div className="mt-4 flex flex-wrap items-center gap-2">
141
+ <button
142
+ type="button"
143
+ disabled={generateDisabled}
144
+ onClick={runGenerate}
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 ? (
151
+ <button
152
+ type="button"
153
+ onClick={runGenerate}
154
+ disabled={streaming || !aiReady}
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"
156
+ >
157
+ {retryLabel}
158
+ </button>
159
+ ) : null}
160
+ </div>
161
+ {!aiLoading && !aiReady ? (
162
+ <p className="mt-2 font-sans text-2xs text-muted-foreground">{noAiHint}</p>
163
+ ) : null}
164
+ {err ? (
165
+ <p className="mt-3 font-sans text-sm text-error" role="alert">
166
+ {errorPrefix} {err}
167
+ </p>
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
+ />
177
+ ) : null}
178
+ </div>
179
+ ) : null}
180
+ </div>
181
+ ) : null}
182
+ </div>
183
+ );
184
+ }
@@ -0,0 +1,86 @@
1
+ 'use client';
2
+
3
+ import type { ReactNode } from 'react';
4
+ import { Library } from 'lucide-react';
5
+
6
+ export function EchoFactSnapshot({
7
+ headingId,
8
+ heading,
9
+ snapshotBadge,
10
+ emptyTitle,
11
+ emptyBody,
12
+ actions,
13
+ }: {
14
+ headingId: string;
15
+ heading: string;
16
+ snapshotBadge: string;
17
+ emptyTitle: string;
18
+ emptyBody: string;
19
+ /** e.g. continue-in-Agent CTA — inside this card so it is not orphaned between sections */
20
+ actions?: ReactNode;
21
+ }) {
22
+ return (
23
+ <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-md sm:p-6"
25
+ aria-labelledby={headingId}
26
+ >
27
+ <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
28
+ <div className="flex items-start gap-3">
29
+ <span
30
+ className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[var(--amber-dim)] text-[var(--amber)]"
31
+ aria-hidden
32
+ >
33
+ <Library size={18} strokeWidth={1.75} />
34
+ </span>
35
+ <div>
36
+ <h2
37
+ id={headingId}
38
+ className="font-sans text-xs font-semibold uppercase tracking-wide text-muted-foreground"
39
+ >
40
+ {heading}
41
+ </h2>
42
+ <p className="mt-2 font-sans font-medium text-foreground">{emptyTitle}</p>
43
+ </div>
44
+ </div>
45
+ <span className="font-sans text-2xs font-medium uppercase tracking-wide text-[var(--amber)] sm:mt-0.5 sm:shrink-0 rounded-md bg-[var(--amber-dim)] px-2 py-1">
46
+ {snapshotBadge}
47
+ </span>
48
+ </div>
49
+ <p className="mt-4 border-t border-border/60 pt-4 font-sans text-sm leading-relaxed text-muted-foreground">
50
+ {emptyBody}
51
+ </p>
52
+ {actions ? (
53
+ <div className="mt-4 border-t border-border/60 pt-4">{actions}</div>
54
+ ) : null}
55
+ </section>
56
+ );
57
+ }
58
+
59
+ export function EchoContinuedGroups({
60
+ draftsLabel,
61
+ todosLabel,
62
+ subEmptyHint,
63
+ footer,
64
+ }: {
65
+ draftsLabel: string;
66
+ todosLabel: string;
67
+ subEmptyHint: string;
68
+ footer?: ReactNode;
69
+ }) {
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
+ <h3 className="font-sans text-sm font-medium text-foreground">{label}</h3>
73
+ <p className="mt-2 font-sans text-2xs leading-relaxed text-muted-foreground">{subEmptyHint}</p>
74
+ </div>
75
+ );
76
+
77
+ return (
78
+ <div className="space-y-4">
79
+ <div className="grid gap-3 sm:grid-cols-2">
80
+ {cell(draftsLabel)}
81
+ {cell(todosLabel)}
82
+ </div>
83
+ {footer ? <div className="border-t border-border/60 pt-4">{footer}</div> : null}
84
+ </div>
85
+ );
86
+ }
@@ -0,0 +1,58 @@
1
+ 'use client';
2
+
3
+ import Link from 'next/link';
4
+ import { useLocale } from '@/lib/LocaleContext';
5
+ import { cn } from '@/lib/utils';
6
+ import { ECHO_SEGMENT_HREF, ECHO_SEGMENT_ORDER, type EchoSegment } from '@/lib/echo-segments';
7
+
8
+ function labelForSegment(
9
+ segment: EchoSegment,
10
+ echo: ReturnType<typeof useLocale>['t']['panels']['echo'],
11
+ ): string {
12
+ switch (segment) {
13
+ case 'about-you':
14
+ return echo.aboutYouTitle;
15
+ case 'continued':
16
+ return echo.continuedTitle;
17
+ case 'daily':
18
+ return echo.dailyEchoTitle;
19
+ case 'past-you':
20
+ return echo.pastYouTitle;
21
+ case 'growth':
22
+ return echo.intentGrowthTitle;
23
+ }
24
+ }
25
+
26
+ export default function EchoSegmentNav({ activeSegment }: { activeSegment: EchoSegment }) {
27
+ const { t } = useLocale();
28
+ const echo = t.panels.echo;
29
+ const aria = t.echoPages.segmentNavAria;
30
+
31
+ return (
32
+ <nav aria-label={aria} className="mt-6 font-sans">
33
+ <ul className="-mx-1 flex snap-x snap-mandatory gap-1.5 overflow-x-auto px-1 pb-1 [scrollbar-width:thin]">
34
+ {ECHO_SEGMENT_ORDER.map((segment) => {
35
+ const href = ECHO_SEGMENT_HREF[segment];
36
+ const label = labelForSegment(segment, echo);
37
+ const isActive = segment === activeSegment;
38
+ return (
39
+ <li key={segment} className="snap-start shrink-0">
40
+ <Link
41
+ href={href}
42
+ aria-current={isActive ? 'page' : undefined}
43
+ 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',
45
+ isActive
46
+ ? 'border-[var(--amber)]/45 bg-[var(--amber-dim)]/50 font-medium text-foreground'
47
+ : 'border-transparent bg-muted/35 text-muted-foreground hover:bg-muted/55 hover:text-foreground',
48
+ )}
49
+ >
50
+ <span className="truncate">{label}</span>
51
+ </Link>
52
+ </li>
53
+ );
54
+ })}
55
+ </ul>
56
+ </nav>
57
+ );
58
+ }
@@ -0,0 +1,265 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useEffect, useId, useMemo, useState } from 'react';
4
+ import type { EchoSegment } from '@/lib/echo-segments';
5
+ import { buildEchoInsightUserPrompt } from '@/lib/echo-insight-prompt';
6
+ import type { Locale } from '@/lib/i18n';
7
+ import { useLocale } from '@/lib/LocaleContext';
8
+ import { openAskModal } from '@/hooks/useAskModal';
9
+ import { EchoHero } from './EchoHero';
10
+ import EchoSegmentNav from './EchoSegmentNav';
11
+ import { EchoInsightCollapsible } from './EchoInsightCollapsible';
12
+ import { EchoContinuedGroups, EchoFactSnapshot } from './EchoPageSections';
13
+
14
+ const STORAGE_DAILY = 'mindos-echo-daily-line';
15
+ const STORAGE_GROWTH = 'mindos-echo-growth-intent';
16
+
17
+ function segmentTitle(segment: EchoSegment, echo: ReturnType<typeof useLocale>['t']['panels']['echo']): string {
18
+ switch (segment) {
19
+ case 'about-you':
20
+ return echo.aboutYouTitle;
21
+ case 'continued':
22
+ return echo.continuedTitle;
23
+ case 'daily':
24
+ return echo.dailyEchoTitle;
25
+ case 'past-you':
26
+ return echo.pastYouTitle;
27
+ case 'growth':
28
+ return echo.intentGrowthTitle;
29
+ }
30
+ }
31
+
32
+ function segmentLead(segment: EchoSegment, p: ReturnType<typeof useLocale>['t']['echoPages']): string {
33
+ switch (segment) {
34
+ case 'about-you':
35
+ return p.aboutYouLead;
36
+ case 'continued':
37
+ return p.continuedLead;
38
+ case 'daily':
39
+ return p.dailyLead;
40
+ case 'past-you':
41
+ return p.pastYouLead;
42
+ case 'growth':
43
+ return p.growthLead;
44
+ }
45
+ }
46
+
47
+ const fieldLabelClass =
48
+ 'block font-sans text-2xs font-semibold uppercase tracking-wide text-muted-foreground';
49
+ 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';
51
+ 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-md sm:p-6';
53
+
54
+ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegment }) {
55
+ const { t, locale } = useLocale();
56
+ const p = t.echoPages;
57
+ const echo = t.panels.echo;
58
+ const title = segmentTitle(segment, echo);
59
+ const lead = segmentLead(segment, p);
60
+ const factsHeadingId = useId();
61
+ const pageTitleId = 'echo-page-title';
62
+
63
+ const [dailyLine, setDailyLine] = useState('');
64
+ const [growthIntent, setGrowthIntent] = useState('');
65
+
66
+ useEffect(() => {
67
+ try {
68
+ const d = localStorage.getItem(STORAGE_DAILY);
69
+ if (d) setDailyLine(d);
70
+ const g = localStorage.getItem(STORAGE_GROWTH);
71
+ if (g) setGrowthIntent(g);
72
+ } catch {
73
+ /* ignore */
74
+ }
75
+ }, []);
76
+
77
+ const persistDaily = useCallback(() => {
78
+ try {
79
+ localStorage.setItem(STORAGE_DAILY, dailyLine);
80
+ } catch {
81
+ /* ignore */
82
+ }
83
+ }, [dailyLine]);
84
+
85
+ const persistGrowth = useCallback(() => {
86
+ try {
87
+ localStorage.setItem(STORAGE_GROWTH, growthIntent);
88
+ } catch {
89
+ /* ignore */
90
+ }
91
+ }, [growthIntent]);
92
+
93
+ const openDailyAsk = useCallback(() => {
94
+ persistDaily();
95
+ openAskModal(p.dailyAskPrefill(dailyLine), 'user');
96
+ }, [dailyLine, p, persistDaily]);
97
+
98
+ const openSegmentAsk = useCallback(() => {
99
+ openAskModal(`${p.parent} / ${title}\n\n`, 'user');
100
+ }, [p.parent, title]);
101
+
102
+ const insightUserPrompt = useMemo(
103
+ () =>
104
+ buildEchoInsightUserPrompt({
105
+ locale: locale as Locale,
106
+ segment,
107
+ segmentTitle: title,
108
+ factsHeading: p.factsHeading,
109
+ emptyTitle: p.emptyFactsTitle,
110
+ emptyBody: p.emptyFactsBody,
111
+ continuedDrafts: p.continuedDrafts,
112
+ continuedTodos: p.continuedTodos,
113
+ subEmptyHint: p.subEmptyHint,
114
+ dailyLineLabel: p.dailyLineLabel,
115
+ dailyLine,
116
+ growthIntentLabel: p.growthIntentLabel,
117
+ growthIntent,
118
+ }),
119
+ [
120
+ locale,
121
+ segment,
122
+ title,
123
+ p.factsHeading,
124
+ p.emptyFactsTitle,
125
+ p.emptyFactsBody,
126
+ p.continuedDrafts,
127
+ p.continuedTodos,
128
+ p.subEmptyHint,
129
+ p.dailyLineLabel,
130
+ dailyLine,
131
+ p.growthIntentLabel,
132
+ growthIntent,
133
+ ],
134
+ );
135
+
136
+ const primaryBtnClass =
137
+ 'inline-flex items-center rounded-lg bg-primary px-4 py-2.5 font-sans text-sm font-medium text-primary-foreground transition-opacity duration-150 hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring';
138
+ const secondaryBtnClass =
139
+ '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';
140
+
141
+ return (
142
+ <article
143
+ className="mx-auto max-w-3xl px-4 py-6 sm:px-6 md:py-11"
144
+ aria-labelledby={pageTitleId}
145
+ >
146
+ <EchoHero
147
+ breadcrumbNav={p.breadcrumbNav}
148
+ parentHref="/echo/about-you"
149
+ parent={p.parent}
150
+ heroKicker={p.heroKicker}
151
+ pageTitle={title}
152
+ lead={lead}
153
+ titleId={pageTitleId}
154
+ />
155
+
156
+ <EchoSegmentNav activeSegment={segment} />
157
+
158
+ <div className="mt-6 space-y-6 sm:mt-8">
159
+ <EchoFactSnapshot
160
+ headingId={factsHeadingId}
161
+ heading={p.factsHeading}
162
+ snapshotBadge={p.snapshotBadge}
163
+ emptyTitle={p.emptyFactsTitle}
164
+ emptyBody={p.emptyFactsBody}
165
+ actions={
166
+ segment === 'about-you' ? (
167
+ <button type="button" onClick={openSegmentAsk} className={secondaryBtnClass}>
168
+ {p.continueAgent}
169
+ </button>
170
+ ) : undefined
171
+ }
172
+ />
173
+ {segment === 'continued' ? (
174
+ <EchoContinuedGroups
175
+ draftsLabel={p.continuedDrafts}
176
+ todosLabel={p.continuedTodos}
177
+ subEmptyHint={p.subEmptyHint}
178
+ footer={
179
+ <button type="button" onClick={openSegmentAsk} className={secondaryBtnClass}>
180
+ {p.continueAgent}
181
+ </button>
182
+ }
183
+ />
184
+ ) : null}
185
+ </div>
186
+
187
+ {segment === 'daily' ? (
188
+ <section className={`${cardSectionClass} mt-6`}>
189
+ <label htmlFor="echo-daily-line" className={fieldLabelClass}>
190
+ {p.dailyLineLabel}
191
+ </label>
192
+ <textarea
193
+ id="echo-daily-line"
194
+ value={dailyLine}
195
+ onChange={(e) => setDailyLine(e.target.value)}
196
+ onBlur={persistDaily}
197
+ rows={3}
198
+ placeholder={p.dailyLinePlaceholder}
199
+ className={inputClass}
200
+ />
201
+ <div className="mt-4">
202
+ <button type="button" onClick={openDailyAsk} className={primaryBtnClass}>
203
+ {p.continueAgent}
204
+ </button>
205
+ </div>
206
+ </section>
207
+ ) : null}
208
+
209
+ {segment === 'growth' ? (
210
+ <section className={`${cardSectionClass} mt-6`}>
211
+ <label htmlFor="echo-growth-intent" className={fieldLabelClass}>
212
+ {p.growthIntentLabel}
213
+ </label>
214
+ <textarea
215
+ id="echo-growth-intent"
216
+ value={growthIntent}
217
+ onChange={(e) => setGrowthIntent(e.target.value)}
218
+ onBlur={persistGrowth}
219
+ rows={4}
220
+ placeholder={p.growthIntentPlaceholder}
221
+ className={`${inputClass} min-h-[6.5rem]`}
222
+ />
223
+ <p className="mt-3 font-sans text-2xs text-muted-foreground">{p.growthSavedNote}</p>
224
+ <div className="mt-4 border-t border-border/60 pt-4">
225
+ <button type="button" onClick={openSegmentAsk} className={secondaryBtnClass}>
226
+ {p.continueAgent}
227
+ </button>
228
+ </div>
229
+ </section>
230
+ ) : null}
231
+
232
+ {segment === 'past-you' ? (
233
+ <section className={`${cardSectionClass} mt-6`}>
234
+ <button
235
+ type="button"
236
+ disabled
237
+ title={p.pastYouDisabledHint}
238
+ className="inline-flex cursor-not-allowed items-center rounded-lg border border-dashed border-border bg-muted/20 px-4 py-2.5 font-sans text-sm text-muted-foreground opacity-85"
239
+ >
240
+ {p.pastYouAnother}
241
+ </button>
242
+ <p className="mt-3 font-sans text-2xs text-muted-foreground">{p.pastYouDisabledHint}</p>
243
+ <div className="mt-4 border-t border-border/60 pt-4">
244
+ <button type="button" onClick={openSegmentAsk} className={secondaryBtnClass}>
245
+ {p.continueAgent}
246
+ </button>
247
+ </div>
248
+ </section>
249
+ ) : null}
250
+
251
+ <EchoInsightCollapsible
252
+ title={p.insightTitle}
253
+ showLabel={p.insightShow}
254
+ hideLabel={p.insightHide}
255
+ hint={p.insightHint}
256
+ generateLabel={p.generateInsight}
257
+ noAiHint={p.generateInsightNoAi}
258
+ generatingLabel={p.insightGenerating}
259
+ errorPrefix={p.insightErrorPrefix}
260
+ retryLabel={p.insightRetry}
261
+ userPrompt={insightUserPrompt}
262
+ />
263
+ </article>
264
+ );
265
+ }