mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-29 06:07:11 +02:00
feat: redesign UI for modern look and clean UX
- Refined dark theme with zinc palette and indigo accents - Clean sidebar showing only project/session tree - Moved pattern management to full-screen settings panel - New session modal with project + pattern picker - Modern chat with avatar icons, inline send button, Enter-to-send - Welcome pane when no session is selected - Added lucide-react for consistent iconography - Custom scrollbar styling and auto-resize textarea - Removed clutter: no sidebar pattern list, no verbose headers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
+103
-153
@@ -2,24 +2,21 @@ import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { AppShell } from '@renderer/components/AppShell';
|
||||
import { ChatPane } from '@renderer/components/ChatPane';
|
||||
import { PatternEditor } from '@renderer/components/PatternEditor';
|
||||
import { NewSessionModal } from '@renderer/components/NewSessionModal';
|
||||
import { SettingsPanel } from '@renderer/components/SettingsPanel';
|
||||
import { Sidebar } from '@renderer/components/Sidebar';
|
||||
import { WelcomePane } from '@renderer/components/WelcomePane';
|
||||
import { getElectronApi } from '@renderer/lib/electronApi';
|
||||
import type { PatternDefinition } from '@shared/domain/pattern';
|
||||
import { createBuiltinPatterns } from '@shared/domain/pattern';
|
||||
import type { WorkspaceState } from '@shared/domain/workspace';
|
||||
import { createId, nowIso } from '@shared/utils/ids';
|
||||
|
||||
function clonePattern(pattern: PatternDefinition): PatternDefinition {
|
||||
return structuredClone(pattern);
|
||||
}
|
||||
|
||||
function createDraftPattern(): PatternDefinition {
|
||||
const timestamp = nowIso();
|
||||
return {
|
||||
id: createId('pattern'),
|
||||
id: createId('custom-pattern'),
|
||||
name: 'New Pattern',
|
||||
description: 'Reusable orchestration pattern.',
|
||||
description: '',
|
||||
mode: 'single',
|
||||
availability: 'available',
|
||||
maxIterations: 1,
|
||||
@@ -27,7 +24,7 @@ function createDraftPattern(): PatternDefinition {
|
||||
{
|
||||
id: createId('agent'),
|
||||
name: 'Primary Agent',
|
||||
description: 'General-purpose project assistant.',
|
||||
description: 'General-purpose assistant.',
|
||||
instructions: 'You are a helpful coding assistant working inside the selected project.',
|
||||
model: 'gpt-5.4',
|
||||
reasoningEffort: 'high',
|
||||
@@ -38,68 +35,25 @@ function createDraftPattern(): PatternDefinition {
|
||||
};
|
||||
}
|
||||
|
||||
function EmptyDetail() {
|
||||
const patterns = createBuiltinPatterns(nowIso());
|
||||
return (
|
||||
<div className="flex h-screen items-center justify-center px-10">
|
||||
<div className="max-w-3xl rounded-3xl border border-slate-800 bg-slate-900/70 p-10">
|
||||
<div className="text-xs uppercase tracking-[0.22em] text-slate-400">Workspace Overview</div>
|
||||
<h2 className="mt-3 text-3xl font-semibold text-white">Chat-first orchestration across projects</h2>
|
||||
<p className="mt-4 text-sm leading-7 text-slate-300">
|
||||
Select a pattern to edit it, add one or more projects on the left, and start sessions that bind a
|
||||
project folder to a reusable orchestration blueprint.
|
||||
</p>
|
||||
<div className="mt-8 grid gap-4 md:grid-cols-2">
|
||||
{patterns.map((pattern) => (
|
||||
<div
|
||||
className="rounded-2xl border border-slate-800 bg-slate-950/70 p-4"
|
||||
key={pattern.id}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<h3 className="text-sm font-semibold text-slate-100">{pattern.name}</h3>
|
||||
<span
|
||||
className={`rounded-full px-2 py-0.5 text-[11px] uppercase tracking-wide ${
|
||||
pattern.availability === 'unavailable'
|
||||
? 'bg-amber-500/15 text-amber-200'
|
||||
: 'bg-emerald-500/10 text-emerald-200'
|
||||
}`}
|
||||
>
|
||||
{pattern.mode}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-2 text-sm text-slate-400">{pattern.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const api = getElectronApi();
|
||||
const [workspace, setWorkspace] = useState<WorkspaceState>();
|
||||
const [draftPattern, setDraftPattern] = useState<PatternDefinition | null>(null);
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
const [showNewSession, setShowNewSession] = useState(false);
|
||||
|
||||
// Load workspace on mount
|
||||
useEffect(() => {
|
||||
let disposed = false;
|
||||
|
||||
void api
|
||||
.loadWorkspace()
|
||||
.then((nextWorkspace) => {
|
||||
if (!disposed) {
|
||||
setWorkspace(nextWorkspace);
|
||||
}
|
||||
})
|
||||
.catch((nextError) => {
|
||||
if (!disposed) {
|
||||
setError(nextError instanceof Error ? nextError.message : String(nextError));
|
||||
}
|
||||
});
|
||||
.then((ws) => !disposed && setWorkspace(ws))
|
||||
.catch((e) => !disposed && setError(e instanceof Error ? e.message : String(e)));
|
||||
|
||||
const offWorkspace = api.onWorkspaceUpdated((nextWorkspace) => {
|
||||
setWorkspace(nextWorkspace);
|
||||
const offWorkspace = api.onWorkspaceUpdated((ws) => {
|
||||
setWorkspace(ws);
|
||||
setError(undefined);
|
||||
});
|
||||
|
||||
@@ -109,118 +63,114 @@ export default function App() {
|
||||
};
|
||||
}, [api]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!workspace?.selectedPatternId) {
|
||||
setDraftPattern(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedPattern = workspace.patterns.find((pattern) => pattern.id === workspace.selectedPatternId);
|
||||
setDraftPattern(selectedPattern ? clonePattern(selectedPattern) : null);
|
||||
}, [workspace?.lastUpdatedAt, workspace?.selectedPatternId, workspace?.patterns]);
|
||||
|
||||
// Derived state
|
||||
const selectedSession = useMemo(
|
||||
() => workspace?.sessions.find((session) => session.id === workspace.selectedSessionId),
|
||||
() => workspace?.sessions.find((s) => s.id === workspace.selectedSessionId),
|
||||
[workspace?.selectedSessionId, workspace?.sessions],
|
||||
);
|
||||
const selectedPattern = useMemo(
|
||||
const patternForSession = useMemo(
|
||||
() =>
|
||||
draftPattern ??
|
||||
workspace?.patterns.find((pattern) => pattern.id === workspace.selectedPatternId),
|
||||
[draftPattern, workspace?.patterns, workspace?.selectedPatternId],
|
||||
selectedSession
|
||||
? workspace?.patterns.find((p) => p.id === selectedSession.patternId)
|
||||
: undefined,
|
||||
[selectedSession, workspace?.patterns],
|
||||
);
|
||||
const selectedProject = useMemo(
|
||||
() => workspace?.projects.find((project) => project.id === workspace.selectedProjectId),
|
||||
[workspace?.projects, workspace?.selectedProjectId],
|
||||
const projectForSession = useMemo(
|
||||
() =>
|
||||
selectedSession
|
||||
? workspace?.projects.find((p) => p.id === selectedSession.projectId)
|
||||
: undefined,
|
||||
[selectedSession, workspace?.projects],
|
||||
);
|
||||
|
||||
// Loading state
|
||||
if (!workspace) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-slate-950 text-slate-100">
|
||||
Loading workspace…
|
||||
<div className="flex h-screen items-center justify-center bg-[var(--color-surface-0)]">
|
||||
<div className="text-sm text-zinc-500">Loading workspace…</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const patternForSession = selectedSession
|
||||
? workspace.patterns.find((pattern) => pattern.id === selectedSession.patternId)
|
||||
: undefined;
|
||||
const projectForSession = selectedSession
|
||||
? workspace.projects.find((project) => project.id === selectedSession.projectId)
|
||||
: undefined;
|
||||
// Determine main content
|
||||
let content: React.ReactNode;
|
||||
if (error) {
|
||||
content = (
|
||||
<div className="flex h-full items-center justify-center px-8">
|
||||
<div className="max-w-md rounded-xl border border-red-500/20 bg-red-500/5 p-6 text-center">
|
||||
<h2 className="text-sm font-semibold text-red-300">Something went wrong</h2>
|
||||
<p className="mt-2 text-[13px] leading-relaxed text-red-400/80">{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (selectedSession && patternForSession && projectForSession) {
|
||||
content = (
|
||||
<ChatPane
|
||||
onSend={(c) => api.sendSessionMessage({ sessionId: selectedSession.id, content: c })}
|
||||
pattern={patternForSession}
|
||||
project={projectForSession}
|
||||
session={selectedSession}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<WelcomePane
|
||||
hasProjects={workspace.projects.length > 0}
|
||||
onAddProject={() => void api.addProject()}
|
||||
onNewSession={() => setShowNewSession(true)}
|
||||
onOpenSettings={() => setShowSettings(true)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Settings overlay
|
||||
const overlay = showSettings ? (
|
||||
<SettingsPanel
|
||||
onClose={() => setShowSettings(false)}
|
||||
onDeletePattern={async (id) => {
|
||||
await api.deletePattern(id);
|
||||
}}
|
||||
onNewPattern={createDraftPattern}
|
||||
onSavePattern={async (pattern) => {
|
||||
await api.savePattern({ pattern });
|
||||
}}
|
||||
patterns={workspace.patterns}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<AppShell
|
||||
content={
|
||||
error ? (
|
||||
<div className="flex h-screen items-center justify-center px-10">
|
||||
<div className="max-w-lg rounded-3xl border border-rose-500/40 bg-rose-500/10 p-8 text-rose-100">
|
||||
<div className="text-xs uppercase tracking-[0.2em] text-rose-200">Error</div>
|
||||
<h2 className="mt-3 text-2xl font-semibold">Something went wrong</h2>
|
||||
<p className="mt-3 text-sm leading-7">{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : selectedSession && patternForSession && projectForSession ? (
|
||||
<ChatPane
|
||||
onSend={(content) => api.sendSessionMessage({ sessionId: selectedSession.id, content })}
|
||||
pattern={patternForSession}
|
||||
project={projectForSession}
|
||||
session={selectedSession}
|
||||
/>
|
||||
) : selectedPattern ? (
|
||||
<PatternEditor
|
||||
isBuiltin={selectedPattern.id.startsWith('pattern-')}
|
||||
onChange={setDraftPattern}
|
||||
onDelete={
|
||||
selectedPattern.id.startsWith('pattern-')
|
||||
? undefined
|
||||
: async () => {
|
||||
await api.deletePattern(selectedPattern.id);
|
||||
}
|
||||
}
|
||||
onSave={async () => {
|
||||
await api.savePattern({ pattern: draftPattern ?? selectedPattern });
|
||||
<>
|
||||
<AppShell
|
||||
content={content}
|
||||
overlay={overlay}
|
||||
sidebar={
|
||||
<Sidebar
|
||||
onAddProject={() => void api.addProject()}
|
||||
onNewSession={() => setShowNewSession(true)}
|
||||
onOpenSettings={() => setShowSettings(true)}
|
||||
onProjectSelect={(projectId) => {
|
||||
void api.selectProject(projectId);
|
||||
}}
|
||||
pattern={draftPattern ?? selectedPattern}
|
||||
onSessionSelect={(sessionId) => {
|
||||
void api.selectSession(sessionId);
|
||||
}}
|
||||
workspace={workspace}
|
||||
/>
|
||||
) : (
|
||||
<EmptyDetail />
|
||||
)
|
||||
}
|
||||
sidebar={
|
||||
<Sidebar
|
||||
onAddProject={() => {
|
||||
void api.addProject();
|
||||
}}
|
||||
onCreateSession={() => {
|
||||
if (!workspace.selectedProjectId || !workspace.selectedPatternId) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
void api.createSession({
|
||||
projectId: workspace.selectedProjectId,
|
||||
patternId: workspace.selectedPatternId,
|
||||
});
|
||||
{showNewSession && (
|
||||
<NewSessionModal
|
||||
defaultProjectId={workspace.selectedProjectId}
|
||||
onClose={() => setShowNewSession(false)}
|
||||
onCreate={(projectId, patternId) => {
|
||||
setShowNewSession(false);
|
||||
void api.createSession({ projectId, patternId });
|
||||
}}
|
||||
onNewPattern={() => {
|
||||
setDraftPattern(createDraftPattern());
|
||||
void api.selectPattern(undefined);
|
||||
void api.selectSession(undefined);
|
||||
}}
|
||||
onPatternSelect={(patternId) => {
|
||||
void api.selectPattern(patternId);
|
||||
void api.selectSession(undefined);
|
||||
}}
|
||||
onProjectSelect={(projectId) => {
|
||||
void api.selectProject(projectId);
|
||||
void api.selectSession(undefined);
|
||||
}}
|
||||
onSessionSelect={(sessionId) => {
|
||||
void api.selectSession(sessionId);
|
||||
}}
|
||||
workspace={workspace}
|
||||
patterns={workspace.patterns}
|
||||
projects={workspace.projects}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user