Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 30 additions & 31 deletions frontend/src/features/assets/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -802,24 +802,26 @@ export function ProfileCatalog({
{copy.profiles.create}
</button>
</div>
<AssetCatalog locale={locale} loading={loading} emptyHint={copy.profiles.empty}>
{profiles.map((profile) => (
<AssetRow
key={profile.profile_name}
name={profile.profile_name}
detail={`${profile.provider} · ${profile.model || "—"}`}
createdAt={profile.created_at}
usageCount={usage?.get(profile.profile_name) ?? 0}
locale={locale}
onClick={() => {
setSettings(profile);
setOpen(true);
}}
onDelete={() => onDelete(profile.profile_name)}
deleteLabel={`${copy.profiles.delete} ${profile.profile_name}`}
/>
<div className="settings-section-content">
<AssetCatalog locale={locale} loading={loading} emptyHint={copy.profiles.empty}>
{profiles.map((profile) => (
<AssetRow
key={profile.profile_name}
name={profile.profile_name}
detail={`${profile.provider} · ${profile.model || "—"}`}
createdAt={profile.created_at}
usageCount={usage?.get(profile.profile_name) ?? 0}
locale={locale}
onClick={() => {
setSettings(profile);
setOpen(true);
}}
onDelete={() => onDelete(profile.profile_name)}
deleteLabel={`${copy.profiles.delete} ${profile.profile_name}`}
/>
))}
</AssetCatalog>
</AssetCatalog>
</div>
<Modal
open={open}
title={
Expand Down Expand Up @@ -1925,19 +1927,24 @@ function EnvironmentCatalog({
);
}

function PersonaCatalog({ builds, onRefresh, locale }: { builds: Build[]; onRefresh: () => Promise<unknown>; locale: Locale }) {
function PersonaCatalog({ builds, onRefresh, locale, loading }: { builds: Build[]; onRefresh: () => Promise<unknown>; locale: Locale; loading: boolean }) {
const t = text[locale];
const [items, setItems] = useState<Persona[]>([]), [draft, setDraft] = useState<Persona | null>(null), [error, setError] = useState("");
const load = () => api<Persona[]>("/api/personas").then(setItems);
useEffect(() => { void load(); }, []);
const [items, setItems] = useState<Persona[]>([]), [draft, setDraft] = useState<Persona | null>(null), [error, setError] = useState(""), [itemsLoading, setItemsLoading] = useState(true);
const load = () => {
setItemsLoading(true);
return api<Persona[]>("/api/personas").then(setItems).finally(() => setItemsLoading(false));
};
useEffect(() => {
void api<Persona[]>("/api/personas").then(setItems).finally(() => setItemsLoading(false));
}, []);
const save = () => {
if (!draft) return;
const exists = items.some((item) => item.id === draft.id);
api<Persona>(exists ? `/api/personas/${draft.id}` : "/api/personas", exists ? "PUT" : "POST", draft)
.then(() => { setDraft(null); setError(""); return Promise.all([load(), onRefresh()]); })
.catch((value) => setError(value.message));
};
return <section className="panel app-settings"><div className="panel-title-action"><div className="panel-title-action__copy"><PanelHeader title={<SectionInfo title={t.personas} description={t.personaDescription} />} /><p className="hint section-description">{t.personaDescription}</p></div><button className="approve" onClick={() => setDraft({ id: `persona-${Date.now()}`, name: "", locale: "en-US", timezone: "UTC", activity_windows: [], goals: [""], constraints: [], context: {} })}><Plus size={14} />{locales[locale].ui.create}</button></div><AssetCatalog locale={locale} emptyHint={t.emptyPersonas}>{items.map((item) => <AssetRow key={item.id} name={item.name} detail={`${item.locale} · ${item.timezone} · ${item.goals.length} ${t.goals.toLowerCase()}`} usageCount={builds.filter((build) => build.persona_ids?.includes(item.id)).length} locale={locale} onClick={() => setDraft(item)} onDelete={() => api(`/api/personas/${item.id}`, "DELETE").then(() => Promise.all([load(), onRefresh()]).then(() => undefined)).catch((value) => setError(value.message))} />)}</AssetCatalog>{draft && <Modal open title={t.persona} onClose={() => setDraft(null)}><div className="modal-form"><label className="modal-setting-row"><span>{t.id}</span><input disabled={items.some((item) => item.id === draft.id)} value={draft.id} onChange={(event) => setDraft({ ...draft, id: event.target.value })} /></label><label className="modal-setting-row"><span>{t.name}</span><input value={draft.name} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></label><label className="modal-setting-row"><span>{t.locale}</span><input value={draft.locale} onChange={(event) => setDraft({ ...draft, locale: event.target.value })} /></label><label className="modal-setting-row"><span>{t.timezone}</span><input value={draft.timezone} onChange={(event) => setDraft({ ...draft, timezone: event.target.value })} /></label><label className="modal-setting-row"><span>{t.goals} ({t.goalsHint})</span><textarea value={draft.goals.join("\n")} onChange={(event) => setDraft({ ...draft, goals: event.target.value.split("\n").filter(Boolean) })} /></label><label className="modal-setting-row"><span>{t.constraints} ({t.constraintsHint})</span><textarea value={draft.constraints.join("\n")} onChange={(event) => setDraft({ ...draft, constraints: event.target.value.split("\n").filter(Boolean) })} /></label><label className="modal-setting-row"><span>{t.activityContext} ({t.activityContextHint})</span><textarea value={JSON.stringify({ activity_windows: draft.activity_windows, context: draft.context }, null, 2)} onChange={(event) => { try { const value = JSON.parse(event.target.value); setDraft({ ...draft, activity_windows: value.activity_windows ?? [], context: value.context ?? {} }); setError(""); } catch { setError(t.invalidPersonaJson); } }} /></label><div className="modal-actions"><small>{error}</small><button className="approve" onClick={save}>{t.save}</button></div></div></Modal>}</section>;
return <section className="panel app-settings"><div className="panel-title-action"><div className="panel-title-action__copy"><PanelHeader title={<SectionInfo title={t.personas} description={t.personaDescription} />} /><p className="hint section-description">{t.personaDescription}</p></div><button className="approve" onClick={() => setDraft({ id: `persona-${Date.now()}`, name: "", locale: "en-US", timezone: "UTC", activity_windows: [], goals: [""], constraints: [], context: {} })}><Plus size={14} />{locales[locale].ui.create}</button></div><AssetCatalog locale={locale} loading={loading || itemsLoading} emptyHint={t.emptyPersonas}>{items.map((item) => <AssetRow key={item.id} name={item.name} detail={`${item.locale} · ${item.timezone} · ${item.goals.length} ${t.goals.toLowerCase()}`} usageCount={builds.filter((build) => build.persona_ids?.includes(item.id)).length} locale={locale} onClick={() => setDraft(item)} onDelete={() => api(`/api/personas/${item.id}`, "DELETE").then(() => Promise.all([load(), onRefresh()]).then(() => undefined)).catch((value) => setError(value.message))} />)}</AssetCatalog>{draft && <Modal open title={t.persona} onClose={() => setDraft(null)}><div className="modal-form"><label className="modal-setting-row"><span>{t.id}</span><input disabled={items.some((item) => item.id === draft.id)} value={draft.id} onChange={(event) => setDraft({ ...draft, id: event.target.value })} /></label><label className="modal-setting-row"><span>{t.name}</span><input value={draft.name} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></label><label className="modal-setting-row"><span>{t.locale}</span><input value={draft.locale} onChange={(event) => setDraft({ ...draft, locale: event.target.value })} /></label><label className="modal-setting-row"><span>{t.timezone}</span><input value={draft.timezone} onChange={(event) => setDraft({ ...draft, locale: draft.locale, timezone: event.target.value })} /></label><label className="modal-setting-row"><span>{t.goals} ({t.goalsHint})</span><textarea value={draft.goals.join("\n")} onChange={(event) => setDraft({ ...draft, goals: event.target.value.split("\n").filter(Boolean) })} /></label><label className="modal-setting-row"><span>{t.constraints} ({t.constraintsHint})</span><textarea value={draft.constraints.join("\n")} onChange={(event) => setDraft({ ...draft, constraints: event.target.value.split("\n").filter(Boolean) })} /></label><label className="modal-setting-row"><span>{t.activityContext} ({t.activityContextHint})</span><textarea value={JSON.stringify({ activity_windows: draft.activity_windows, context: draft.context }, null, 2)} onChange={(event) => { try { const value = JSON.parse(event.target.value); setDraft({ ...draft, activity_windows: value.activity_windows ?? [], context: value.context ?? {} }); setError(""); } catch { setError(t.invalidPersonaJson); } }} /></label><div className="modal-actions"><small>{error}</small><button className="approve" onClick={save}>{t.save}</button></div></div></Modal>}</section>;
}

export function AssetsPage({
Expand Down Expand Up @@ -2008,14 +2015,6 @@ export function AssetsPage({
const index = tabs.findIndex((tab) => tab.id === activeTab);
selectTab(tabs[(index + offset + tabs.length) % tabs.length].id);
};
if (loading) return <>
<SectionSkeleton rows={3} />
<SectionSkeleton rows={3} />
<SectionSkeleton rows={3} />
<SectionSkeleton rows={3} />
<SectionSkeleton rows={3} />
<SectionSkeleton rows={4} />
</>;
return (
<>
<div className="asset-tabs" role="tablist" aria-label={text[locale].tabsLabel}>
Expand Down Expand Up @@ -2056,7 +2055,7 @@ export function AssetsPage({
<LegacyAssetsPage {...legacy} locale={locale} loading={loading} onDelete={onDelete} usage={usage} activeTab="ai" />
</>}
{activeTab === "test-design" && <>
<PersonaCatalog builds={builds} onRefresh={legacy.onRefresh} locale={locale} />
<PersonaCatalog builds={builds} onRefresh={legacy.onRefresh} locale={locale} loading={loading} />
<LegacyAssetsPage {...legacy} locale={locale} loading={loading} onDelete={onDelete} usage={usage} activeTab="test-design" />
</>}
{activeTab === "run-setup" && <>
Expand Down
18 changes: 10 additions & 8 deletions frontend/src/features/settings/orbit-logs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ export function OrbitLogs({ logs, locale }: { logs: OrbitLog[]; locale: Locale }
<p className="hint section-description">{t.description}</p>
</div>
</div>
<div className="orbit-log-output">
{logs.length ? logs.map((log, index) => (
<div key={`${log.time}-${index}`} className={log.status === "ERROR" ? "error-log" : ""}>
<time>{log.time ? new Date(log.time).toLocaleTimeString(intlLocales[locale]) : "—"}</time>
<strong>{log.name}</strong>
<span>{log.message || log.status}</span>
</div>
)) : <p className="hint">{t.empty}</p>}
<div className="settings-section-content">
<div className="orbit-log-output">
{logs.length ? logs.map((log, index) => (
<div key={`${log.time}-${index}`} className={log.status === "ERROR" ? "error-log" : ""}>
<time>{log.time ? new Date(log.time).toLocaleTimeString(intlLocales[locale]) : "—"}</time>
<strong>{log.name}</strong>
<span>{log.message || log.status}</span>
</div>
)) : <p className="hint">{t.empty}</p>}
</div>
</div>
</section>
);
Expand Down
52 changes: 30 additions & 22 deletions frontend/src/features/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -294,22 +294,32 @@ export function SettingsPage({
</div>
<button className="approve" onClick={() => setMcpOpen(true)}><Pencil size={14} />{settingsCopy.mcp.edit}</button>
</div>
<DataTable
className="mcp-server-table"
columns={mcpColumns}
rows={mcpServers}
empty={settingsCopy.mcp.noServers}
gridTemplateColumns="minmax(140px,.8fr) minmax(280px,2fr) 110px"
/>
<div className="settings-section-content">
<DataTable
className="mcp-server-table"
columns={mcpColumns}
rows={mcpServers}
empty={settingsCopy.mcp.noServers}
gridTemplateColumns="minmax(140px,.8fr) minmax(280px,2fr) 110px"
/>
</div>
</section>
<section className="panel app-settings app-data-settings">
<PanelHeader title={<SectionInfo title={settingsCopy.storage.title} description={sectionDetails.applicationData} />} />
<p className="hint section-description">{settingsCopy.storage.description}</p>
<div className="panel-title-action">
<div className="panel-title-action__copy">
<PanelHeader title={<SectionInfo title={settingsCopy.storage.title} description={sectionDetails.applicationData} />} />
<p className="hint section-description">{settingsCopy.storage.description}</p>
</div>
{!dataEditing && <button className="approve" onClick={() => { setDataPathDraft(dataPath); setDataEditing(true); }}>
<Pencil size={14} />
{l.edit}
</button>}
</div>
<div className="setting-row">
<span>
<strong>{settingsCopy.storage.location}</strong>
<small>{settingsCopy.storage.locationHint}</small>
</span>
<div className="settings-summary">
<span className="settings-summary-label">{settingsCopy.storage.location}</span>
<span className="settings-summary-detail">{settingsCopy.storage.locationHint}</span>
</div>
{dataEditing ? (
<div className="app-data-path-actions">
<input value={dataPathDraft} onChange={(event) => setDataPathDraft(event.target.value)} />
Expand All @@ -326,10 +336,6 @@ export function SettingsPage({
) : (
<div className="app-data-path-actions">
<p className="setting-prompt-preview app-data-path">{dataPath}</p>
<button className="approve" onClick={() => { setDataPathDraft(dataPath); setDataEditing(true); }}>
<Pencil size={14} />
{l.edit}
</button>
</div>
)}
</div>
Expand Down Expand Up @@ -410,11 +416,13 @@ export function SettingsPage({
{l.edit}
</button>
</div>
<p className="operational-prompt-warning" role="note">
<AlertTriangle size={16} />
{l.warning}
</p>
<p className="setting-prompt-preview">{prompt || l.empty}</p>
<div className="settings-section-content">
<p className="operational-prompt-warning" role="note">
<AlertTriangle size={16} />
{l.warning}
</p>
<p className="setting-prompt-preview">{prompt || l.empty}</p>
</div>
</section>
<OrbitLogs logs={logs} locale={locale} />
<Modal open={mcpOpen} title={settingsCopy.mcp.title} onClose={() => setMcpOpen(false)}>
Expand Down
Loading
Loading