@elizaos/app-core 2.0.0-alpha.348 → 2.0.0-alpha.349

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elizaos/app-core",
3
- "version": "2.0.0-alpha.348",
3
+ "version": "2.0.0-alpha.349",
4
4
  "description": "Shared application core for elizaOS white-label agent apps.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -469,7 +469,7 @@
469
469
  "@capacitor/preferences": "^8.0.1",
470
470
  "@capacitor/push-notifications": "^8.0.0",
471
471
  "@clack/prompts": "^1.0.0",
472
- "@elizaos/agent": "^2.0.0-alpha.348",
472
+ "@elizaos/agent": "^2.0.0-alpha.349",
473
473
  "@elizaos/app-companion": "^0.0.0",
474
474
  "@elizaos/app-elizamaker": "^0.0.0",
475
475
  "@elizaos/app-lifeops": "^0.0.0",
@@ -478,11 +478,11 @@
478
478
  "@elizaos/app-task-coordinator": "^0.0.0",
479
479
  "@elizaos/app-training": "^0.0.1",
480
480
  "@elizaos/app-vincent": "^0.0.0",
481
- "@elizaos/core": "^2.0.0-alpha.348",
481
+ "@elizaos/core": "^2.0.0-alpha.349",
482
482
  "@elizaos/plugin-browser-bridge": "^0.1.0",
483
483
  "@elizaos/plugin-wechat": "^0.1.0",
484
- "@elizaos/shared": "^2.0.0-alpha.348",
485
- "@elizaos/ui": "^2.0.0-alpha.348",
484
+ "@elizaos/shared": "^2.0.0-alpha.349",
485
+ "@elizaos/ui": "^2.0.0-alpha.349",
486
486
  "@radix-ui/react-checkbox": "^1.3.3",
487
487
  "@radix-ui/react-dialog": "^1.1.15",
488
488
  "@radix-ui/react-dropdown-menu": "^2.1.16",
@@ -1 +1 @@
1
- {"version":3,"file":"CharacterExperienceWorkspace.d.ts","sourceRoot":"","sources":["../../../../../../src/components/character/CharacterExperienceWorkspace.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,wBAAwB,EACxB,yBAAyB,EAC1B,MAAM,uBAAuB,CAAC;AAqK/B,wBAAgB,4BAA4B,CAAC,EAC3C,WAAW,EACX,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,GACrB,EAAE;IACD,WAAW,EAAE,yBAAyB,EAAE,CAAC;IACzC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,kBAAkB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,gBAAgB,CAAC,EAAE,CACjB,UAAU,EAAE,yBAAyB,EACrC,KAAK,EAAE,wBAAwB,KAC5B,IAAI,CAAC;IACV,kBAAkB,CAAC,EAAE,CAAC,UAAU,EAAE,yBAAyB,KAAK,IAAI,CAAC;IACrE,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC,2CAiqBA"}
1
+ {"version":3,"file":"CharacterExperienceWorkspace.d.ts","sourceRoot":"","sources":["../../../../../../src/components/character/CharacterExperienceWorkspace.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,wBAAwB,EACxB,yBAAyB,EAC1B,MAAM,uBAAuB,CAAC;AAqK/B,wBAAgB,4BAA4B,CAAC,EAC3C,WAAW,EACX,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,GACrB,EAAE;IACD,WAAW,EAAE,yBAAyB,EAAE,CAAC;IACzC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,kBAAkB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,gBAAgB,CAAC,EAAE,CACjB,UAAU,EAAE,yBAAyB,EACrC,KAAK,EAAE,wBAAwB,KAC5B,IAAI,CAAC;IACV,kBAAkB,CAAC,EAAE,CAAC,UAAU,EAAE,yBAAyB,KAAK,IAAI,CAAC;IACrE,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC,2CAkqBA"}
@@ -175,7 +175,7 @@ export function CharacterExperienceWorkspace({ experiences, selectedExperienceId
175
175
  ? experiences.find((experience) => experience.id === visibleSelectedExperience.supersedes)
176
176
  : null, [experiences, visibleSelectedExperience]);
177
177
  if (experiences.length === 0) {
178
- return (_jsxs("section", { className: "rounded-2xl border border-dashed border-border/40 bg-bg-muted/20 px-5 py-8 text-sm text-muted", children: [_jsx("div", { className: "text-base font-semibold text-txt", children: "No experiences recorded yet." }), _jsx("p", { className: "mt-1 max-w-xl", children: "Learned outcomes will appear here with confidence, importance, source context, and review controls once the agent captures experience." })] }));
178
+ return (_jsxs("section", { className: "rounded-2xl border border-dashed border-border/40 bg-bg-muted/20 px-5 py-8 text-sm text-muted", children: [_jsx("div", { className: "text-base font-semibold text-txt", children: "I haven\u2019t learned anything yet." }), _jsx("p", { className: "mt-1 max-w-xl", children: "As we work together I\u2019ll keep notes here \u2014 what worked, what didn\u2019t, things I want to remember next time. Each lesson lands with the context that produced it so you can review or correct me." })] }));
179
179
  }
180
180
  return (_jsxs("section", { className: "flex min-w-0 flex-col gap-4", children: [_jsxs("div", { className: "rounded-2xl border border-border/40 bg-bg/70 p-4", children: [_jsxs("div", { className: "flex min-w-0 flex-wrap items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("h3", { className: "text-base font-semibold text-txt", children: "Experience" }), _jsx("p", { className: "text-sm text-muted", children: "Triage learned outcomes by priority, confidence, evidence, and correction history." })] }), _jsxs("div", { className: "rounded-full border border-border/40 px-3 py-1 text-xs font-semibold text-muted", children: [filteredExperiences.length, " of ", experiences.length, " shown"] })] }), _jsxs("div", { className: "mt-4 grid gap-3 md:grid-cols-4", children: [_jsx(StatTile, { label: "Captured", value: String(stats.total), detail: `${stats.reviewCount} need review` }), _jsx(StatTile, { label: "Avg importance", value: formatPercent(stats.averageImportance), detail: "Ranking weight" }), _jsx(StatTile, { label: "Avg confidence", value: formatPercent(stats.averageConfidence), detail: "Evidence strength" }), _jsx(StatTile, { label: "Corrections", value: String(stats.corrections), detail: "Beliefs revised" })] }), _jsxs("div", { className: "mt-4 grid gap-3 lg:grid-cols-[minmax(14rem,1.4fr)_repeat(5,minmax(8rem,1fr))]", children: [_jsxs("label", { htmlFor: "experience-search", className: "min-w-0", children: [_jsx("span", { className: "sr-only", children: "Search experiences" }), _jsx(Input, { id: "experience-search", type: "search", value: searchQuery, onChange: (event) => setSearchQuery(event.target.value), placeholder: "Search learning, evidence, tags...", className: "h-9 rounded-xl border-border/40 bg-bg-muted/15" })] }), _jsxs("label", { className: "min-w-0", children: [_jsx("span", { className: "sr-only", children: "Outcome filter" }), _jsxs("select", { "aria-label": "Outcome filter", value: outcomeFilter, onChange: (event) => setOutcomeFilter(event.target.value), className: "h-9 w-full rounded-xl border border-border/40 bg-bg-muted/15 px-3 text-sm text-txt", children: [_jsx("option", { value: "all", children: "All outcomes" }), filters.outcomes.map((outcome) => (_jsx("option", { value: outcome, children: outcome }, outcome)))] })] }), _jsxs("label", { className: "min-w-0", children: [_jsx("span", { className: "sr-only", children: "Domain filter" }), _jsxs("select", { "aria-label": "Domain filter", value: domainFilter, onChange: (event) => setDomainFilter(event.target.value), className: "h-9 w-full rounded-xl border border-border/40 bg-bg-muted/15 px-3 text-sm text-txt", children: [_jsx("option", { value: "all", children: "All domains" }), filters.domains.map((domain) => (_jsx("option", { value: domain, children: domain }, domain)))] })] }), _jsxs("label", { className: "min-w-0", children: [_jsx("span", { className: "sr-only", children: "Tag filter" }), _jsxs("select", { "aria-label": "Tag filter", value: tagFilter, onChange: (event) => setTagFilter(event.target.value), className: "h-9 w-full rounded-xl border border-border/40 bg-bg-muted/15 px-3 text-sm text-txt", children: [_jsx("option", { value: "all", children: "All tags" }), filters.tags.map((tag) => (_jsx("option", { value: tag, children: tag }, tag)))] })] }), _jsxs("label", { className: "min-w-0", children: [_jsx("span", { className: "sr-only", children: "Review filter" }), _jsxs("select", { "aria-label": "Review filter", value: reviewFilter, onChange: (event) => setReviewFilter(event.target.value), className: "h-9 w-full rounded-xl border border-border/40 bg-bg-muted/15 px-3 text-sm text-txt", children: [_jsx("option", { value: "all", children: "All review states" }), _jsx("option", { value: "needs-review", children: "Needs review" }), _jsx("option", { value: "corrected", children: "Corrected belief" }), _jsx("option", { value: "superseded", children: "Supersedes prior" })] })] }), _jsxs("label", { className: "min-w-0", children: [_jsx("span", { className: "sr-only", children: "Sort experiences" }), _jsxs("select", { "aria-label": "Sort experiences", value: sortMode, onChange: (event) => setSortMode(event.target.value), className: "h-9 w-full rounded-xl border border-border/40 bg-bg-muted/15 px-3 text-sm text-txt", children: [_jsx("option", { value: "priority", children: "Priority" }), _jsx("option", { value: "newest", children: "Newest" }), _jsx("option", { value: "importance", children: "Importance" }), _jsx("option", { value: "confidence", children: "Lowest confidence" })] })] })] })] }), _jsxs("div", { className: "grid min-w-0 gap-4 xl:grid-cols-[minmax(19rem,25rem)_minmax(0,1fr)]", children: [_jsxs("div", { className: "flex min-h-[28rem] min-w-0 flex-col overflow-hidden rounded-2xl border border-border/40 bg-bg/70", children: [_jsxs("div", { className: "border-b border-border/30 px-4 py-3", children: [_jsx("div", { className: "text-[0.65rem] font-semibold uppercase tracking-[0.08em] text-muted", children: "Review queue" }), _jsx("p", { className: "mt-1 text-sm text-muted", children: "Priority favors high importance, low confidence, and corrected beliefs." })] }), _jsx("div", { className: "custom-scrollbar flex min-w-0 flex-1 flex-col overflow-y-auto", children: filteredExperiences.length === 0 ? (_jsx("div", { className: "px-4 py-8 text-sm text-muted", children: "No experiences match the current filters." })) : (filteredExperiences.map((experience) => {
181
181
  const isSelected = experience.id === visibleSelectedExperience?.id;
@@ -1 +1 @@
1
- {"version":3,"file":"CharacterHubView.d.ts","sourceRoot":"","sources":["../../../../../../src/components/character/CharacterHubView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AA0BzD,OAAO,KAAK,EACV,aAAa,EAKd,MAAM,wBAAwB,CAAC;AA2BhC,KAAK,qBAAqB,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAiHrD,wBAAgB,gBAAgB,CAAC,EAC/B,CAAC,EACD,OAAO,EACP,yBAAyB,EACzB,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,6BAA6B,EAC7B,cAAc,EACd,2BAA2B,EAC3B,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,EACjB,MAAM,GACP,EAAE;IACD,CAAC,EAAE,aAAa,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,yBAAyB,EAAE,mBAAmB,EAAE,CAAC;IACjD,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC3C,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACzD,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACxD,6BAA6B,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACpE,cAAc,EAAE,CAAC,GAAG,EAAE,qBAAqB,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACpE,2BAA2B,EAAE,CAC3B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,KACV,IAAI,CAAC;IACV,eAAe,EAAE,OAAO,CAAC;IACzB,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CAChC,2CAoxBA"}
1
+ {"version":3,"file":"CharacterHubView.d.ts","sourceRoot":"","sources":["../../../../../../src/components/character/CharacterHubView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AA0BzD,OAAO,KAAK,EACV,aAAa,EAKd,MAAM,wBAAwB,CAAC;AA2BhC,KAAK,qBAAqB,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAqIrD,wBAAgB,gBAAgB,CAAC,EAC/B,CAAC,EACD,OAAO,EACP,yBAAyB,EACzB,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,6BAA6B,EAC7B,cAAc,EACd,2BAA2B,EAC3B,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,EACjB,MAAM,GACP,EAAE;IACD,CAAC,EAAE,aAAa,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,yBAAyB,EAAE,mBAAmB,EAAE,CAAC;IACjD,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC3C,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACzD,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACxD,6BAA6B,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACpE,cAAc,EAAE,CAAC,GAAG,EAAE,qBAAqB,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACpE,2BAA2B,EAAE,CAC3B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,KACV,IAAI,CAAC;IACV,eAAe,EAAE,OAAO,CAAC;IACzB,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CAChC,2CAm0BA"}
@@ -95,10 +95,33 @@ function latestTimestamp(value) {
95
95
  const date = new Date(value);
96
96
  return Number.isNaN(date.getTime()) ? 0 : date.getTime();
97
97
  }
98
- function ratio(value, max) {
99
- if (max <= 0)
100
- return 0;
101
- return Math.max(0, Math.min(1, value / max));
98
+ function formatRelativeTime(value) {
99
+ if (value === null || value === undefined)
100
+ return "";
101
+ const time = typeof value === "number" ? value : new Date(value).getTime();
102
+ if (Number.isNaN(time) || time <= 0)
103
+ return "";
104
+ const diff = Date.now() - time;
105
+ if (diff < 0)
106
+ return "just now";
107
+ const minute = 60_000;
108
+ const hour = 60 * minute;
109
+ const day = 24 * hour;
110
+ if (diff < minute)
111
+ return "just now";
112
+ if (diff < hour) {
113
+ const value = Math.round(diff / minute);
114
+ return `${value}m ago`;
115
+ }
116
+ if (diff < day) {
117
+ const value = Math.round(diff / hour);
118
+ return `${value}h ago`;
119
+ }
120
+ if (diff < 7 * day) {
121
+ const value = Math.round(diff / day);
122
+ return `${value}d ago`;
123
+ }
124
+ return new Date(time).toLocaleDateString();
102
125
  }
103
126
  export function CharacterHubView({ d, bioText, normalizedMessageExamples, pendingStyleEntries, styleEntryDrafts, handleFieldEdit, applyFieldEdit, handlePendingStyleEntryChange, applyStyleEdit, handleStyleEntryDraftChange, characterSaving, characterSaveSuccess, characterSaveError, hasPendingChanges, onSave, }) {
104
127
  const { setActionNotice, setTab, tab, t } = useApp();
@@ -275,109 +298,81 @@ export function CharacterHubView({ d, bioText, normalizedMessageExamples, pendin
275
298
  };
276
299
  }, []);
277
300
  const customKnowledgeDocuments = useMemo(() => knowledgeDocuments.filter((document) => !isDefaultKnowledgeDocument(document)), [knowledgeDocuments]);
278
- const starterOverviewWidgets = useMemo(() => [
279
- {
280
- section: "personality",
281
- title: "Personality",
282
- caption: "Add bio, style, and examples",
283
- score: 0,
284
- },
285
- {
286
- section: "knowledge",
287
- title: "Knowledge",
288
- caption: "Upload or teach source material",
289
- score: 0,
290
- },
291
- {
292
- section: "skills",
293
- title: "Skills",
294
- caption: "Let the agent learn useful abilities",
295
- score: 0,
296
- },
297
- {
298
- section: "experience",
299
- title: "Experience",
300
- caption: "Record outcomes and lessons",
301
- score: 0,
302
- },
303
- {
304
- section: "relationships",
305
- title: "Relationships",
306
- caption: "Build identity and preference memory",
307
- score: 0,
308
- },
309
- ], []);
310
301
  const overviewWidgets = useMemo(() => {
311
302
  const styleItems = Object.values(d.style ?? {}).reduce((count, values) => count + (Array.isArray(values) ? values.length : 0), 0);
303
+ const exampleCount = normalizedMessageExamples.length;
312
304
  const latestPersonalityUpdate = historyEntries.reduce((latest, entry) => Math.max(latest, latestTimestamp(entry.timestamp)), 0);
313
305
  const activeSkills = learnedSkills.filter((skill) => skill.status !== "disabled");
314
- const averageExperienceConfidence = experienceRecords.length > 0
315
- ? experienceRecords.reduce((total, experience) => total + experience.confidence, 0) / experienceRecords.length
316
- : 0;
317
- const averageExperienceImportance = experienceRecords.length > 0
318
- ? experienceRecords.reduce((total, experience) => total + experience.importance, 0) / experienceRecords.length
319
- : 0;
320
- const relationshipNames = relationshipActivity
321
- .map((item) => item.personName)
322
- .filter(Boolean);
306
+ const recentDocs = [...customKnowledgeDocuments]
307
+ .sort((left, right) => latestTimestamp(right.createdAt) - latestTimestamp(left.createdAt))
308
+ .slice(0, 3);
309
+ const recentExperience = [...experienceRecords]
310
+ .sort((left, right) => latestTimestamp(right.updatedAt ?? right.createdAt) -
311
+ latestTimestamp(left.updatedAt ?? left.createdAt))[0];
312
+ const relationshipNames = Array.from(new Set(relationshipActivity
313
+ .map((item) => item.personName?.trim())
314
+ .filter((name) => Boolean(name))));
315
+ const trimmedBio = bioText.trim();
316
+ const personalityHasContent = trimmedBio.length > 0 || styleItems > 0 || exampleCount > 0;
317
+ const personalityBody = trimmedBio ? (_jsx("p", { className: "line-clamp-3 text-xs leading-relaxed text-muted", children: trimmedBio })) : (_jsxs("div", { className: "flex flex-wrap gap-1.5 text-2xs", children: [styleItems > 0 ? (_jsxs("span", { className: "rounded-full border border-border/30 bg-bg-muted/30 px-2 py-0.5 text-muted", children: [styleItems, " style rule", styleItems === 1 ? "" : "s"] })) : null, exampleCount > 0 ? (_jsxs("span", { className: "rounded-full border border-border/30 bg-bg-muted/30 px-2 py-0.5 text-muted", children: [exampleCount, " example", exampleCount === 1 ? "" : "s"] })) : null] }));
323
318
  return [
324
319
  {
325
320
  section: "personality",
326
321
  title: "Personality",
327
- caption: latestPersonalityUpdate > 0
328
- ? `Updated ${new Date(latestPersonalityUpdate).toLocaleDateString()}`
329
- : "No personality edits yet",
330
- score: ratio((bioText.trim() ? 1 : 0) + styleItems + historyEntries.length, 12),
331
- bars: [
332
- { label: "Bio", value: bioText.trim() ? 1 : 0 },
333
- { label: "Style", value: ratio(styleItems, 8) },
334
- { label: "History", value: ratio(historyEntries.length, 8) },
335
- ],
322
+ meta: latestPersonalityUpdate > 0
323
+ ? `Updated ${formatRelativeTime(latestPersonalityUpdate)}`
324
+ : null,
325
+ body: personalityBody,
326
+ isEmpty: !personalityHasContent,
336
327
  },
337
328
  {
338
329
  section: "knowledge",
339
330
  title: "Knowledge",
340
- caption: `${customKnowledgeDocuments.length} custom document${customKnowledgeDocuments.length === 1 ? "" : "s"}`,
341
- score: ratio(customKnowledgeDocuments.length, 8),
342
- bars: [
343
- { label: "Custom", value: ratio(customKnowledgeDocuments.length, 8) },
344
- { label: "Total", value: ratio(knowledgeDocuments.length, 12) },
345
- ],
331
+ meta: customKnowledgeDocuments.length > 0
332
+ ? `${customKnowledgeDocuments.length} doc${customKnowledgeDocuments.length === 1 ? "" : "s"}`
333
+ : null,
334
+ body: recentDocs.length > 0 ? (_jsx("ul", { className: "flex flex-col gap-1 text-xs text-muted", children: recentDocs.map((doc) => (_jsx("li", { className: "truncate", children: doc.filename }, doc.id))) })) : null,
335
+ isEmpty: customKnowledgeDocuments.length === 0,
346
336
  },
347
337
  {
348
338
  section: "skills",
349
339
  title: "Skills",
350
- caption: `${activeSkills.length} learned skill${activeSkills.length === 1 ? "" : "s"}`,
351
- score: ratio(activeSkills.length, 8),
352
- nodes: activeSkills.map((skill) => skill.name),
340
+ meta: activeSkills.length > 0
341
+ ? `${activeSkills.length} active`
342
+ : null,
343
+ body: activeSkills.length > 0 ? (_jsxs("div", { className: "flex flex-wrap gap-1.5", children: [activeSkills.slice(0, 5).map((skill) => (_jsx("span", { className: "truncate rounded-full border border-border/30 bg-bg-muted/30 px-2 py-0.5 text-2xs text-muted", title: skill.name, children: skill.name }, skill.name))), activeSkills.length > 5 ? (_jsxs("span", { className: "text-2xs text-muted", children: ["+", activeSkills.length - 5, " more"] })) : null] })) : null,
344
+ isEmpty: activeSkills.length === 0,
353
345
  },
354
346
  {
355
347
  section: "experience",
356
348
  title: "Experience",
357
- caption: `${experienceRecords.length} recorded experience${experienceRecords.length === 1 ? "" : "s"}`,
358
- score: ratio(experienceRecords.length, 10),
359
- bars: [
360
- { label: "Confidence", value: averageExperienceConfidence },
361
- { label: "Importance", value: averageExperienceImportance },
362
- { label: "Records", value: ratio(experienceRecords.length, 10) },
363
- ],
349
+ meta: experienceRecords.length > 0
350
+ ? `${experienceRecords.length} lesson${experienceRecords.length === 1 ? "" : "s"}`
351
+ : null,
352
+ body: recentExperience ? (_jsx("p", { className: "line-clamp-3 text-xs italic leading-relaxed text-muted", children: recentExperience.learning ||
353
+ recentExperience.result ||
354
+ recentExperience.context ||
355
+ recentExperience.type })) : null,
356
+ isEmpty: experienceRecords.length === 0,
364
357
  },
365
358
  {
366
359
  section: "relationships",
367
360
  title: "Relationships",
368
- caption: `${relationshipActivity.length} recent signal${relationshipActivity.length === 1 ? "" : "s"}`,
369
- score: ratio(relationshipActivity.length, 10),
370
- nodes: relationshipNames,
361
+ meta: relationshipNames.length > 0
362
+ ? `${relationshipNames.length} ${relationshipNames.length === 1 ? "person" : "people"}`
363
+ : null,
364
+ body: relationshipNames.length > 0 ? (_jsxs("div", { className: "flex flex-wrap gap-1.5", children: [relationshipNames.slice(0, 4).map((name) => (_jsx("span", { className: "truncate rounded-full border border-border/30 bg-bg-muted/30 px-2 py-0.5 text-2xs text-muted", title: name, children: name }, name))), relationshipNames.length > 4 ? (_jsxs("span", { className: "text-2xs text-muted", children: ["+", relationshipNames.length - 4, " more"] })) : null] })) : null,
365
+ isEmpty: relationshipNames.length === 0,
371
366
  },
372
367
  ];
373
368
  }, [
374
369
  bioText,
375
- customKnowledgeDocuments.length,
370
+ customKnowledgeDocuments,
376
371
  d.style,
377
372
  experienceRecords,
378
373
  historyEntries,
379
- knowledgeDocuments.length,
380
374
  learnedSkills,
375
+ normalizedMessageExamples.length,
381
376
  relationshipActivity,
382
377
  ]);
383
378
  const timelineItems = useMemo(() => historyEntries.map(mapHistoryEntryToTimelineItem), [historyEntries]);
@@ -508,14 +503,14 @@ export function CharacterHubView({ d, bioText, normalizedMessageExamples, pendin
508
503
  })()) }) }));
509
504
  const renderSection = () => {
510
505
  if (activeSection === "overview") {
511
- return (_jsx(CharacterOverviewSection, { starterWidgets: starterOverviewWidgets, widgets: overviewWidgets, onOpenSection: handleOverviewOpenSection }));
506
+ return (_jsx(CharacterOverviewSection, { characterName: d.name, widgets: overviewWidgets, onOpenSection: handleOverviewOpenSection }));
512
507
  }
513
508
  if (activeSection === "personality") {
514
509
  return (_jsxs("div", { className: "flex min-w-0 flex-col gap-8", children: [_jsxs("section", { className: "rounded-2xl border border-border/40 bg-bg/70 px-4 py-4", children: [_jsxs("div", { className: "mb-4", children: [_jsx("h2", { className: "text-lg font-semibold text-txt", children: "Personality" }), _jsx("p", { className: "text-sm text-muted", children: "Save your bio manually. Style rules and examples autosave as you edit them." })] }), _jsx(CharacterIdentityPanel, { bioText: bioText, handleFieldEdit: handleFieldEdit, t: t }), _jsxs("div", { className: "mt-4 flex flex-wrap items-center justify-between gap-3 border-t border-border/30 pt-4", children: [_jsxs("div", { className: "flex flex-col gap-1", children: [characterSaveSuccess ? (_jsx("span", { className: "rounded-sm border border-status-success/20 bg-status-success-bg px-2 py-1 text-2xs font-semibold text-status-success", children: characterSaveSuccess })) : null, characterSaveError ? (_jsx("span", { className: "rounded-sm border border-status-danger/20 bg-status-danger-bg px-2 py-1 text-2xs font-medium text-status-danger", children: characterSaveError })) : null] }), _jsx(Button, { type: "button", className: "h-9 rounded-sm px-4 text-sm font-semibold tracking-[0.02em]", disabled: characterSaving || !hasPendingChanges, onClick: () => {
515
510
  void handleManualSave();
516
511
  }, children: characterSaving
517
512
  ? t("charactereditor.Saving", { defaultValue: "saving..." })
518
- : t("charactereditor.Save", { defaultValue: "Save" }) })] })] }), _jsx("section", { className: "rounded-2xl border border-border/40 bg-bg/70 px-4 py-4", children: _jsx(CharacterStylePanel, { d: d, pendingStyleEntries: pendingStyleEntries, styleEntryDrafts: styleEntryDrafts, handlePendingStyleEntryChange: handlePendingStyleEntryChange, handleAddStyleEntry: handleAutoAddStyleEntry, handleRemoveStyleEntry: handleAutoRemoveStyleEntry, handleStyleEntryDraftChange: handleStyleEntryDraftChange, handleCommitStyleEntry: handleAutoCommitStyleEntry, handleReorderStyleEntries: handleAutoReorderStyleEntries, t: t }) }), _jsx("section", { className: "rounded-2xl border border-border/40 bg-bg/70 px-4 py-4", children: _jsx(CharacterExamplesPanel, { d: d, normalizedMessageExamples: normalizedMessageExamples, handleFieldEdit: handleAutoSavedExamplesEdit, t: t }) }), _jsxs("section", { className: "rounded-2xl border border-border/40 bg-bg/70 px-4 py-4", children: [historyError ? (_jsx("div", { className: "mb-4 rounded-xl border border-danger/30 bg-danger/10 px-4 py-3 text-sm text-danger", children: historyError })) : null, historyLoading ? (_jsx("div", { className: "text-sm text-muted", children: "Loading personality history\u2026" })) : (_jsx(CharacterPersonalityTimeline, { entries: timelineItems }))] })] }));
513
+ : t("charactereditor.Save", { defaultValue: "Save" }) })] })] }), _jsx("section", { className: "rounded-2xl border border-border/40 bg-bg/70 px-4 py-4", children: _jsx(CharacterStylePanel, { d: d, pendingStyleEntries: pendingStyleEntries, styleEntryDrafts: styleEntryDrafts, handlePendingStyleEntryChange: handlePendingStyleEntryChange, handleAddStyleEntry: handleAutoAddStyleEntry, handleRemoveStyleEntry: handleAutoRemoveStyleEntry, handleStyleEntryDraftChange: handleStyleEntryDraftChange, handleCommitStyleEntry: handleAutoCommitStyleEntry, handleReorderStyleEntries: handleAutoReorderStyleEntries, t: t }) }), _jsx("section", { className: "rounded-2xl border border-border/40 bg-bg/70 px-4 py-4", children: _jsx(CharacterExamplesPanel, { d: d, normalizedMessageExamples: normalizedMessageExamples, handleFieldEdit: handleAutoSavedExamplesEdit, t: t }) }), historyLoading || historyError || timelineItems.length > 0 ? (_jsx("section", { className: "rounded-2xl border border-border/40 bg-bg/70 px-4 py-4", children: historyError ? (_jsx("div", { className: "rounded-xl border border-danger/30 bg-danger/10 px-4 py-3 text-sm text-danger", children: historyError })) : historyLoading ? (_jsx("div", { className: "text-sm text-muted", children: "Loading personality history\u2026" })) : (_jsx(CharacterPersonalityTimeline, { entries: timelineItems })) })) : null] }));
519
514
  }
520
515
  if (activeSection === "knowledge") {
521
516
  return (_jsx("section", { className: "rounded-2xl border border-border/40 bg-bg/70 p-0", children: _jsx(KnowledgeView, { embedded: true, fileInputId: "character-hub-knowledge-upload", onDocumentsChange: setKnowledgeDocuments, onSelectedDocumentIdChange: setSelectedKnowledgeDocumentId, selectedDocumentId: selectedKnowledgeDocumentId, showSelectorRail: false }) }));
@@ -1 +1 @@
1
- {"version":3,"file":"CharacterLearnedSkillsSection.d.ts","sourceRoot":"","sources":["../../../../../../src/components/character/CharacterLearnedSkillsSection.tsx"],"names":[],"mappings":"AAkCA,wBAAgB,6BAA6B,4CAuI5C"}
1
+ {"version":3,"file":"CharacterLearnedSkillsSection.d.ts","sourceRoot":"","sources":["../../../../../../src/components/character/CharacterLearnedSkillsSection.tsx"],"names":[],"mappings":"AAkCA,wBAAgB,6BAA6B,4CAwI5C"}
@@ -69,7 +69,7 @@ export function CharacterLearnedSkillsSection() {
69
69
  ? "Loading"
70
70
  : `${grouped.proposed.length} proposed · ${grouped.active.length} active · ${grouped.disabled.length} disabled` })] }), _jsx(Button, { variant: "outline", size: "icon", className: "h-8 w-8 rounded-lg", onClick: () => {
71
71
  void refresh();
72
- }, disabled: loading, "aria-label": "Refresh learned skills", title: "Refresh", children: _jsx(RefreshCw, { className: `h-3.5 w-3.5 ${loading ? "animate-spin" : ""}` }) })] }), _jsxs("div", { className: "flex flex-col gap-4", children: [errorMessage ? (_jsx("div", { className: "rounded-xl border border-danger/40 bg-danger/10 px-3 py-2.5 text-xs-tight leading-5 text-danger", children: errorMessage })) : null, grouped.proposed.length > 0 ? (_jsx(SkillSection, { title: "Pending proposals", skills: grouped.proposed, busyName: busyName, onPromote: (name) => performAction(name, "POST", "promote"), onDelete: (name) => performAction(name, "DELETE", "delete") })) : null, grouped.active.length > 0 ? (_jsx(SkillSection, { title: "Active learned skills", skills: grouped.active, busyName: busyName, onDisable: (name) => performAction(name, "POST", "disable"), onDelete: (name) => performAction(name, "DELETE", "delete") })) : null, grouped.disabled.length > 0 ? (_jsx(SkillSection, { title: "Disabled", skills: grouped.disabled, busyName: busyName, onDelete: (name) => performAction(name, "DELETE", "delete") })) : null, isEmpty ? (_jsx("div", { className: "rounded-xl border border-dashed border-border/60 bg-bg-hover/40 px-3 py-3 text-xs-tight leading-5 text-muted", children: "No learned skills yet." })) : null] })] }));
72
+ }, disabled: loading, "aria-label": "Refresh learned skills", title: "Refresh", children: _jsx(RefreshCw, { className: `h-3.5 w-3.5 ${loading ? "animate-spin" : ""}` }) })] }), _jsxs("div", { className: "flex flex-col gap-4", children: [errorMessage ? (_jsx("div", { className: "rounded-xl border border-danger/40 bg-danger/10 px-3 py-2.5 text-xs-tight leading-5 text-danger", children: errorMessage })) : null, grouped.proposed.length > 0 ? (_jsx(SkillSection, { title: "Pending proposals", skills: grouped.proposed, busyName: busyName, onPromote: (name) => performAction(name, "POST", "promote"), onDelete: (name) => performAction(name, "DELETE", "delete") })) : null, grouped.active.length > 0 ? (_jsx(SkillSection, { title: "Active learned skills", skills: grouped.active, busyName: busyName, onDisable: (name) => performAction(name, "POST", "disable"), onDelete: (name) => performAction(name, "DELETE", "delete") })) : null, grouped.disabled.length > 0 ? (_jsx(SkillSection, { title: "Disabled", skills: grouped.disabled, busyName: busyName, onDelete: (name) => performAction(name, "DELETE", "delete") })) : null, isEmpty ? (_jsx("div", { className: "rounded-xl border border-dashed border-border/60 bg-bg-hover/40 px-3 py-3 text-xs-tight leading-5 text-muted", children: "I haven\u2019t picked up any abilities yet. Browse the catalog or add one by example, and I\u2019ll start using it." })) : null] })] }));
73
73
  }
74
74
  function SkillSection({ title, skills, busyName, onPromote, onDisable, onDelete, }) {
75
75
  return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("div", { className: "text-2xs font-semibold uppercase tracking-wide text-muted", children: title }), _jsx("ul", { className: "flex flex-col gap-2", children: skills.map((skill) => (_jsx("li", { children: _jsx(Card, { className: "border-border/50 bg-bg-hover/60 shadow-none", children: _jsx(CardContent, { className: "flex flex-col gap-2 px-3 py-3 text-xs-tight", children: _jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("div", { className: "font-mono text-sm font-semibold text-txt", children: skill.name }), _jsx("div", { className: "text-xs-tight text-muted", children: skill.description }), _jsxs("div", { className: "flex flex-wrap gap-x-2 gap-y-1 text-2xs uppercase tracking-wide text-muted", children: [_jsx("span", { children: skill.source }), _jsxs("span", { children: [skill.refinedCount, " refinements"] }), _jsxs("span", { children: [formatScore(skill.lastEvalScore), " score"] }), _jsx("span", { children: formatDate(skill.createdAt) })] }), skill.derivedFromTrajectory ? (_jsxs("div", { className: "text-2xs text-muted", children: ["Derived from trajectory:", " ", _jsxs("a", { href: `/trajectories/${skill.derivedFromTrajectory}`, className: "underline", children: [skill.derivedFromTrajectory.slice(0, 8), "\u2026"] })] })) : null] }), _jsxs("div", { className: "flex shrink-0 flex-col gap-1", children: [onPromote ? (_jsx(Button, { size: "sm", variant: "default", disabled: busyName === skill.name, onClick: () => onPromote(skill.name), children: "Promote" })) : null, onDisable ? (_jsx(Button, { size: "sm", variant: "outline", disabled: busyName === skill.name, onClick: () => onDisable(skill.name), children: "Disable" })) : null, _jsx(Button, { size: "sm", variant: "ghost", disabled: busyName === skill.name, onClick: () => onDelete(skill.name), children: "Delete" })] })] }) }) }) }, skill.name))) })] }));
@@ -1,25 +1,21 @@
1
+ import type { ReactNode } from "react";
1
2
  import type { CharacterHubSection } from "./character-hub-helpers";
2
3
  type OverviewSection = Exclude<CharacterHubSection, "overview">;
3
- export interface CharacterOverviewBar {
4
- label: string;
5
- value: number;
6
- }
7
- export interface CharacterOverviewSlice {
8
- label: string;
9
- value: number;
10
- }
11
4
  export interface CharacterOverviewWidget {
12
- bars?: CharacterOverviewBar[];
13
- caption: string;
14
- nodes?: string[];
15
- pie?: CharacterOverviewSlice[];
16
- score?: number;
5
+ /** Section the widget links to. */
17
6
  section: OverviewSection;
7
+ /** Header title. */
18
8
  title: string;
9
+ /** Optional small text on the right side of the header (count or "Updated 2h ago"). */
10
+ meta?: string | null;
11
+ /** Real preview content rendered in the widget body. */
12
+ body?: ReactNode | null;
13
+ /** True when no real content exists. Empty widgets are hidden. */
14
+ isEmpty: boolean;
19
15
  }
20
- export declare function CharacterOverviewSection({ onOpenSection, starterWidgets, widgets, }: {
16
+ export declare function CharacterOverviewSection({ characterName, onOpenSection, widgets, }: {
17
+ characterName?: string | null;
21
18
  onOpenSection: (section: OverviewSection) => void;
22
- starterWidgets: CharacterOverviewWidget[];
23
19
  widgets: CharacterOverviewWidget[];
24
20
  }): import("react/jsx-runtime").JSX.Element;
25
21
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"CharacterOverviewSection.d.ts","sourceRoot":"","sources":["../../../../../../src/components/character/CharacterOverviewSection.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAEnE,KAAK,eAAe,GAAG,OAAO,CAAC,mBAAmB,EAAE,UAAU,CAAC,CAAC;AAEhE,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,GAAG,CAAC,EAAE,sBAAsB,EAAE,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,eAAe,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;CACf;AAyND,wBAAgB,wBAAwB,CAAC,EACvC,aAAa,EACb,cAAc,EACd,OAAO,GACR,EAAE;IACD,aAAa,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IAClD,cAAc,EAAE,uBAAuB,EAAE,CAAC;IAC1C,OAAO,EAAE,uBAAuB,EAAE,CAAC;CACpC,2CAmBA"}
1
+ {"version":3,"file":"CharacterOverviewSection.d.ts","sourceRoot":"","sources":["../../../../../../src/components/character/CharacterOverviewSection.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAEnE,KAAK,eAAe,GAAG,OAAO,CAAC,mBAAmB,EAAE,UAAU,CAAC,CAAC;AAEhE,MAAM,WAAW,uBAAuB;IACtC,mCAAmC;IACnC,OAAO,EAAE,eAAe,CAAC;IACzB,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,uFAAuF;IACvF,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,wDAAwD;IACxD,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IACxB,kEAAkE;IAClE,OAAO,EAAE,OAAO,CAAC;CAClB;AAyLD,wBAAgB,wBAAwB,CAAC,EACvC,aAAa,EACb,aAAa,EACb,OAAO,GACR,EAAE;IACD,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,aAAa,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IAClD,OAAO,EAAE,uBAAuB,EAAE,CAAC;CACpC,2CAkDA"}
@@ -1,6 +1,5 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Button } from "@elizaos/ui";
3
- import { ArrowRight, BookOpen, Brain, Network, PencilLine, Sparkles, } from "lucide-react";
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { ArrowRight, BookOpen, Brain, MessageCircle, Network, PencilLine, Sparkles, Zap, } from "lucide-react";
4
3
  const WIDGET_ICONS = {
5
4
  personality: PencilLine,
6
5
  knowledge: BookOpen,
@@ -8,69 +7,65 @@ const WIDGET_ICONS = {
8
7
  experience: Brain,
9
8
  relationships: Network,
10
9
  };
11
- const WIDGET_ACCENTS = {
12
- personality: "bg-[rgba(var(--accent-rgb,240,185,11),0.16)] text-accent",
13
- knowledge: "bg-status-info-bg text-status-info",
14
- skills: "bg-[rgba(var(--accent-rgb,240,185,11),0.16)] text-accent",
15
- experience: "bg-status-success-bg text-status-success",
16
- relationships: "bg-status-warning-bg text-status-warning",
10
+ const WIDGET_TONE = {
11
+ personality: "text-accent",
12
+ knowledge: "text-status-info",
13
+ skills: "text-accent",
14
+ experience: "text-status-success",
15
+ relationships: "text-status-warning",
17
16
  };
18
- const PIE_COLORS = [
19
- "var(--accent)",
20
- "rgb(47, 192, 144)",
21
- "rgb(104, 153, 255)",
22
- "rgb(251, 146, 60)",
17
+ const GETTING_STARTED_SUGGESTIONS = [
18
+ {
19
+ section: "personality",
20
+ icon: PencilLine,
21
+ label: "Tell me who I am",
22
+ hint: "Write a short bio so I know how to show up.",
23
+ },
24
+ {
25
+ section: "knowledge",
26
+ icon: BookOpen,
27
+ label: "Give me something to read",
28
+ hint: "Upload notes, docs, or links I can study.",
29
+ },
30
+ {
31
+ section: "experience",
32
+ icon: MessageCircle,
33
+ label: "Talk with me for a bit",
34
+ hint: "I learn from our conversations as we go.",
35
+ },
36
+ {
37
+ section: "skills",
38
+ icon: Zap,
39
+ label: "Turn on some abilities",
40
+ hint: "Pick the skills I should be good at.",
41
+ },
23
42
  ];
24
- function clampRatio(value) {
25
- if (!Number.isFinite(value))
26
- return 0;
27
- return Math.max(0, Math.min(1, value));
43
+ function GettingStarted({ characterName, onOpenSection, }) {
44
+ const name = characterName?.trim() ? characterName.trim() : "your character";
45
+ return (_jsxs("section", { className: "flex flex-col gap-5 rounded-2xl border border-border/30 bg-card/40 p-6", "aria-label": "Get started with your character", children: [_jsxs("div", { className: "flex flex-col gap-1", children: [_jsxs("h2", { className: "text-lg font-semibold text-txt", children: ["Let\u2019s shape ", name] }), _jsxs("p", { className: "max-w-xl text-sm text-muted", children: [name, " is a blank slate right now. Pick anything below to start \u2014 there\u2019s no wrong order, and you can always change your mind."] })] }), _jsx("div", { className: "grid gap-2 md:grid-cols-2", children: GETTING_STARTED_SUGGESTIONS.map((suggestion) => {
46
+ const Icon = suggestion.icon;
47
+ return (_jsxs("button", { type: "button", onClick: () => onOpenSection(suggestion.section), className: "group flex items-start gap-3 rounded-xl border border-border/30 bg-bg/40 p-3 text-left transition-colors hover:border-border/60 hover:bg-bg/70 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50", children: [_jsx("span", { className: "mt-0.5 inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-accent/12 text-accent", children: _jsx(Icon, { className: "h-4 w-4", "aria-hidden": true }) }), _jsxs("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [_jsx("span", { className: "truncate text-sm font-semibold text-txt", children: suggestion.label }), _jsx("span", { className: "text-xs text-muted", children: suggestion.hint })] }), _jsx(ArrowRight, { className: "mt-1 h-4 w-4 shrink-0 text-muted transition-colors group-hover:text-txt", "aria-hidden": true })] }, suggestion.section));
48
+ }) })] }));
28
49
  }
29
- function MiniPie({ slices }) {
30
- const total = slices.reduce((sum, slice) => sum + Math.max(slice.value, 0), 0);
31
- const radius = 22;
32
- const circumference = 2 * Math.PI * radius;
33
- let offset = 0;
34
- if (total <= 0) {
35
- return (_jsx("div", { className: "h-16 w-16 rounded-full border border-dashed border-border/50" }));
36
- }
37
- return (_jsxs("svg", { className: "h-16 w-16 -rotate-90", viewBox: "0 0 64 64", "aria-hidden": true, children: [_jsx("title", { children: "Overview distribution" }), _jsx("circle", { cx: "32", cy: "32", r: radius, fill: "none", stroke: "rgba(255,255,255,0.08)", strokeWidth: "10" }), slices.map((slice, index) => {
38
- const length = (Math.max(slice.value, 0) / total) * circumference;
39
- const element = (_jsx("circle", { cx: "32", cy: "32", r: radius, fill: "none", stroke: PIE_COLORS[index % PIE_COLORS.length], strokeDasharray: `${length} ${circumference - length}`, strokeDashoffset: -offset, strokeLinecap: "butt", strokeWidth: "10" }, `${slice.label}-${slice.value}`));
40
- offset += length;
41
- return element;
42
- })] }));
43
- }
44
- function ScoreRing({ score }) {
45
- const radius = 22;
46
- const circumference = 2 * Math.PI * radius;
47
- const progress = clampRatio(score) * circumference;
48
- return (_jsxs("svg", { className: "h-16 w-16 -rotate-90", viewBox: "0 0 64 64", "aria-hidden": true, children: [_jsx("title", { children: "Overview score" }), _jsx("circle", { cx: "32", cy: "32", r: radius, fill: "none", stroke: "rgba(255,255,255,0.08)", strokeWidth: "10" }), _jsx("circle", { cx: "32", cy: "32", r: radius, fill: "none", stroke: "var(--accent)", strokeDasharray: `${progress} ${circumference - progress}`, strokeLinecap: "round", strokeWidth: "10" })] }));
49
- }
50
- function MiniBars({ bars }) {
51
- return (_jsx("div", { className: "flex min-w-0 flex-col gap-2", children: bars.slice(0, 4).map((bar) => (_jsxs("div", { className: "grid grid-cols-[4.25rem_1fr] gap-2", children: [_jsx("span", { className: "truncate text-2xs font-medium uppercase tracking-[0.08em] text-muted", children: bar.label }), _jsx("div", { className: "h-2 overflow-hidden bg-bg-muted/35", children: _jsx("div", { className: "h-full bg-accent", style: { width: `${clampRatio(bar.value) * 100}%` } }) })] }, bar.label))) }));
52
- }
53
- function NodeGraph({ nodes }) {
54
- const visible = Array.from(new Set(nodes.filter(Boolean))).slice(0, 5);
55
- return (_jsxs("div", { className: "relative h-24 overflow-hidden border border-border/30 bg-bg-muted/15", children: [_jsx("div", { className: "absolute left-1/2 top-1/2 h-8 w-8 -translate-x-1/2 -translate-y-1/2 bg-accent/20 text-accent", children: _jsx(Network, { className: "m-2 h-4 w-4", "aria-hidden": true }) }), visible.map((node, index) => {
56
- const angle = -Math.PI / 2 + (index / Math.max(visible.length, 1)) * 6.28;
57
- const x = 50 + Math.cos(angle) * 34;
58
- const y = 50 + Math.sin(angle) * 30;
59
- return (_jsx("div", { className: "absolute h-7 w-7 -translate-x-1/2 -translate-y-1/2 border border-status-warning/45 bg-status-warning-bg text-center text-[0.68rem] font-semibold leading-7 text-status-warning", style: { left: `${x}%`, top: `${y}%` }, title: node, children: node
60
- .split(/\s+/)
61
- .slice(0, 2)
62
- .map((part) => part[0]?.toUpperCase() ?? "")
63
- .join("") || "?" }, node));
64
- })] }));
50
+ function NextUpHint({ suggestion, onOpenSection, }) {
51
+ const Icon = suggestion.icon;
52
+ return (_jsxs("button", { type: "button", onClick: () => onOpenSection(suggestion.section), className: "group flex w-full items-center gap-3 rounded-xl border border-border/30 bg-card/30 px-4 py-3 text-left transition-colors hover:border-border/60 hover:bg-card/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50", children: [_jsx("span", { className: "inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-accent/12 text-accent", children: _jsx(Icon, { className: "h-3.5 w-3.5", "aria-hidden": true }) }), _jsxs("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [_jsx("span", { className: "text-2xs font-semibold uppercase tracking-[0.12em] text-muted", children: "Next up" }), _jsx("span", { className: "truncate text-sm font-medium text-txt", children: suggestion.label })] }), _jsx("span", { className: "text-xs text-muted group-hover:text-txt", children: suggestion.hint }), _jsx(ArrowRight, { className: "h-4 w-4 shrink-0 text-muted transition-colors group-hover:text-txt", "aria-hidden": true })] }));
65
53
  }
66
54
  function OverviewWidget({ onOpenSection, widget, }) {
67
55
  const Icon = WIDGET_ICONS[widget.section];
68
- const bars = widget.bars?.filter((bar) => bar.label.trim()) ?? [];
69
- const pie = widget.pie?.filter((slice) => slice.value > 0) ?? [];
70
- const nodes = widget.nodes?.filter(Boolean) ?? [];
71
- return (_jsxs("section", { className: "flex min-h-[15rem] min-w-0 flex-col border border-border/35 bg-bg/70 p-4", children: [_jsxs("div", { className: "flex min-w-0 items-start justify-between gap-3", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx("span", { className: `inline-flex h-9 w-9 shrink-0 items-center justify-center ${WIDGET_ACCENTS[widget.section]}`, children: _jsx(Icon, { className: "h-5 w-5", "aria-hidden": true }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("h3", { className: "truncate text-sm font-semibold text-txt", children: widget.title }), _jsx("p", { className: "truncate text-xs text-muted", children: widget.caption })] })] }), pie.length > 0 ? (_jsx(MiniPie, { slices: pie })) : (_jsx(ScoreRing, { score: widget.score ?? 0 }))] }), _jsxs("div", { className: "mt-4 flex min-h-0 flex-1 flex-col justify-center gap-4", children: [nodes.length > 0 ? _jsx(NodeGraph, { nodes: nodes }) : null, bars.length > 0 ? _jsx(MiniBars, { bars: bars }) : null] }), _jsx("div", { className: "mt-4 flex justify-end", children: _jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-8 px-2", onClick: () => onOpenSection(widget.section), "aria-label": `Open ${widget.title}`, children: _jsx(ArrowRight, { className: "h-4 w-4", "aria-hidden": true }) }) })] }));
56
+ const accent = WIDGET_TONE[widget.section];
57
+ return (_jsxs("button", { type: "button", onClick: () => onOpenSection(widget.section), className: "group flex h-44 min-w-0 flex-col gap-3 rounded-2xl border border-border/30 bg-card/40 p-4 text-left transition-colors hover:border-border/55 hover:bg-card/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50", "aria-label": `Open ${widget.title}`, children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx("span", { className: `inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-bg-muted/40 ${accent}`, children: _jsx(Icon, { className: "h-4 w-4", "aria-hidden": true }) }), _jsx("h3", { className: "min-w-0 flex-1 truncate text-sm font-semibold text-txt", children: widget.title }), widget.meta ? (_jsx("span", { className: "shrink-0 text-2xs font-medium text-muted", children: widget.meta })) : null] }), _jsx("div", { className: "flex min-h-0 flex-1 flex-col", children: widget.body ?? null }), _jsx("div", { className: "flex justify-end", children: _jsx(ArrowRight, { className: "h-4 w-4 text-muted transition-colors group-hover:text-txt", "aria-hidden": true }) })] }));
72
58
  }
73
- export function CharacterOverviewSection({ onOpenSection, starterWidgets, widgets, }) {
74
- const visibleWidgets = widgets.length > 0 ? widgets : starterWidgets;
75
- return (_jsx("section", { className: "flex min-w-0 flex-col gap-4", "aria-label": "Character overview", children: _jsx("div", { className: "grid items-stretch gap-4 md:grid-cols-2 xl:grid-cols-3", children: visibleWidgets.map((widget) => (_jsx(OverviewWidget, { widget: widget, onOpenSection: onOpenSection }, widget.section))) }) }));
59
+ export function CharacterOverviewSection({ characterName, onOpenSection, widgets, }) {
60
+ const visibleWidgets = widgets.filter((widget) => !widget.isEmpty);
61
+ const allEmpty = visibleWidgets.length === 0;
62
+ // Pick a "next up" hint when some content exists but most sections are empty.
63
+ // Cycles through missing sections in canonical order.
64
+ const missingSuggestion = !allEmpty
65
+ ? GETTING_STARTED_SUGGESTIONS.find((suggestion) => widgets.some((widget) => widget.section === suggestion.section && widget.isEmpty))
66
+ : null;
67
+ if (allEmpty) {
68
+ return (_jsx("section", { className: "flex min-w-0 flex-col gap-4", "aria-label": "Character overview", children: _jsx(GettingStarted, { characterName: characterName, onOpenSection: onOpenSection }) }));
69
+ }
70
+ return (_jsxs("section", { className: "flex min-w-0 flex-col gap-3", "aria-label": "Character overview", children: [missingSuggestion ? (_jsx(NextUpHint, { suggestion: missingSuggestion, onOpenSection: onOpenSection })) : null, _jsx("div", { className: "grid items-stretch gap-3 md:grid-cols-2 xl:grid-cols-3", children: visibleWidgets.map((widget) => (_jsx(OverviewWidget, { widget: widget, onOpenSection: onOpenSection }, widget.section))) })] }));
76
71
  }
@@ -1 +1 @@
1
- {"version":3,"file":"wallet-status.d.ts","sourceRoot":"","sources":["../../../../../../../src/components/chat/widgets/wallet-status.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,2BAA2B,EAC3B,sBAAsB,EACvB,MAAM,SAAS,CAAC;AAkIjB,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,sBAAsB,kDAwKvE;AAED,eAAO,MAAM,oBAAoB,EAAE,2BAMlC,CAAC"}
1
+ {"version":3,"file":"wallet-status.d.ts","sourceRoot":"","sources":["../../../../../../../src/components/chat/widgets/wallet-status.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,2BAA2B,EAC3B,sBAAsB,EACvB,MAAM,SAAS,CAAC;AA4JjB,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,sBAAsB,kDAwKvE;AAED,eAAO,MAAM,oBAAoB,EAAE,2BAMlC,CAAC"}
@@ -2,7 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Check, Copy, Wallet } from "lucide-react";
3
3
  import { useEffect, useMemo, useState } from "react";
4
4
  import { useApp } from "../../../state";
5
- import { resolveChainKey } from "../../inventory/chainConfig";
5
+ import { getNativeLogoUrl, resolveChainKey, } from "../../inventory/chainConfig";
6
+ import { normalizeInventoryImageUrl } from "../../inventory/media-url";
6
7
  import { EmptyWidgetState, WidgetSection } from "./shared";
7
8
  const DUST_THRESHOLD_USD = 0.01;
8
9
  const COPY_FEEDBACK_MS = 1200;
@@ -16,15 +17,15 @@ const EVM_CHAIN_ORDER = [
16
17
  "avax",
17
18
  ];
18
19
  const EVM_CHAIN_KEYS = new Set(EVM_CHAIN_ORDER);
19
- const CHAIN_BADGE_LABELS = {
20
- ethereum: "ETH",
21
- base: "BASE",
22
- arbitrum: "ARB",
23
- optimism: "OP",
24
- polygon: "POL",
25
- bsc: "BSC",
26
- avax: "AVAX",
27
- solana: "SOL",
20
+ const CHAIN_DISPLAY_LABELS = {
21
+ ethereum: "Ethereum",
22
+ base: "Base",
23
+ arbitrum: "Arbitrum",
24
+ optimism: "Optimism",
25
+ polygon: "Polygon",
26
+ bsc: "BNB Chain",
27
+ avax: "Avalanche",
28
+ solana: "Solana",
28
29
  };
29
30
  function shortenAddress(value) {
30
31
  if (!value)
@@ -65,7 +66,21 @@ function normalizeEvmChainKeys(chainNames) {
65
66
  return EVM_CHAIN_ORDER.filter((chainKey) => seen.has(chainKey));
66
67
  }
67
68
  function ChainBadge({ chain }) {
68
- return (_jsx("span", { className: "inline-flex h-4 shrink-0 items-center rounded-full border border-border/35 bg-bg/40 px-1.5 font-mono text-[0.52rem] font-semibold leading-none text-muted", title: chain, children: CHAIN_BADGE_LABELS[chain] }));
69
+ // Use the same per-chain logo URLs the wallet page uses these are real
70
+ // raster logos pulled from the trustwallet/assets repo (see
71
+ // CHAIN_CONFIGS[*].nativeLogoUrl) and cover every chain we register,
72
+ // including Arbitrum / Optimism / Polygon that the SVG-only ChainIcon
73
+ // doesn't have paths for.
74
+ const [errored, setErrored] = useState(false);
75
+ const label = CHAIN_DISPLAY_LABELS[chain];
76
+ const url = errored
77
+ ? null
78
+ : (normalizeInventoryImageUrl(getNativeLogoUrl(chain)) ?? null);
79
+ if (url) {
80
+ return (_jsx("img", { src: url, alt: label, title: label, width: 16, height: 16, className: "inline-flex h-4 w-4 shrink-0 rounded-full bg-bg/40 object-cover", onError: () => setErrored(true) }));
81
+ }
82
+ // Tiny initials fallback when the logo URL fails or is missing.
83
+ return (_jsx("span", { className: "inline-flex h-4 shrink-0 items-center rounded-full border border-border/35 bg-bg/40 px-1.5 font-mono text-[0.52rem] font-semibold leading-none text-muted", title: label, "aria-label": label, children: label.slice(0, 3).toUpperCase() }));
69
84
  }
70
85
  function ChainBadges({ chains }) {
71
86
  return (_jsx("span", { className: "flex min-w-0 flex-wrap items-center gap-1", children: chains.map((chain) => (_jsx(ChainBadge, { chain: chain }, chain))) }));