Skip to content
Open
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
3 changes: 2 additions & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"scripts": {
"dev": "vite",
"dev:demo": "vite --mode demo",
"dev:design": "vite --mode demo --host 127.0.0.1 --port 5173",
"build": "tsc -b && vite build",
"build:demo": "tsc -b && vite build --mode demo",
"preview": "vite preview",
Expand Down Expand Up @@ -47,4 +48,4 @@
"public"
]
}
}
}
7 changes: 4 additions & 3 deletions ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ function AppShell() {
const section = findSectionForActivity(selectedSidebar)
const isDesktop = useIsDesktop()
const showSidebarPanel = isDesktop && section != null
const showDemoBanner = import.meta.env.VITE_DEMO_MODE && import.meta.env.VITE_SHOW_DEMO_BANNER

// Auto-close the mobile secondary drawer once the user picks a sub-item.
// We snapshot the focused tab at drawer-open time (see openSecondaryDrawer
Expand Down Expand Up @@ -104,7 +105,7 @@ function AppShell() {
const mainContent = (
<main className="flex flex-col min-w-0 min-h-0 bg-bg h-full">
{/* Mobile header — visible only below md */}
<div className="flex items-center gap-3 px-4 py-3 border-b border-border bg-bg-secondary shrink-0 md:hidden">
<div className="flex items-center gap-3 px-4 py-3 border-b border-border/80 bg-bg-secondary shrink-0 md:hidden">
<button
onClick={() => setSidebarOpen(true)}
className="text-text-muted hover:text-text p-1 -ml-1"
Expand All @@ -123,7 +124,7 @@ function AppShell() {

return (
<div className="flex flex-col h-full">
{import.meta.env.VITE_DEMO_MODE && <DemoBanner />}
{showDemoBanner && <DemoBanner />}
{import.meta.env.VITE_DEMO_MODE && <DemoAnalytics />}
<UpdateBanner />
<div className="flex flex-1 min-h-0">
Expand Down Expand Up @@ -163,7 +164,7 @@ function AppShell() {
<section.Secondary />
</Sidebar>
</Panel>
<Separator className="w-px bg-border hover:bg-accent/40 active:bg-accent/60 transition-colors" />
<Separator className="w-px bg-border/80 hover:bg-accent/40 active:bg-accent/60 transition-colors" />
</>
)}
<Panel id="main">
Expand Down
25 changes: 13 additions & 12 deletions ui/src/components/ActivityBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ const NAV_SECTIONS: NavSection[] = [
// ==================== ActivityBar ====================

/**
* Linear-style left nav. 200px wide on all viewports; on mobile (<md)
* Linear-style left nav. 276px wide on desktop; on mobile (<md)
* it slides in over the page from the left, on desktop it's a static
* column. Top section (no header) is the pinned-nav block — Chat,
* Inbox, Workspaces, etc. — always visible. Labeled sections (Agent,
Expand Down Expand Up @@ -175,13 +175,13 @@ export function ActivityBar({ open, onClose, onItemActivated }: ActivityBarProps
onClick={onClose}
/>

{/* ActivityBar — 200px on all viewports. Mobile: slide-in over
{/* ActivityBar — Linear-style workspace rail. Mobile: slide-in over
* page with backdrop. Desktop: static column flush left. */}
<aside
className={`
w-[200px] h-full flex flex-col shrink-0
w-[276px] h-full flex flex-col shrink-0
bg-bg-secondary
border-r border-border
border-r border-border/80
fixed z-50 top-0 left-0 transition-transform duration-200
${open ? 'translate-x-0' : '-translate-x-full'}
md:static md:translate-x-0 md:z-auto md:transition-none
Expand All @@ -192,14 +192,15 @@ export function ActivityBar({ open, onClose, onItemActivated }: ActivityBarProps
<img
src="/alice.ico"
alt="Alice"
className="w-7 h-7 rounded-lg ring-1 ring-accent/25 shadow-[0_0_8px_rgba(88,166,255,0.15)]"
className="w-7 h-7 rounded-full ring-1 ring-white/10 shadow-[0_0_14px_rgba(198,109,55,0.12)]"
draggable={false}
/>
<h1 className="text-[15px] font-semibold text-text">OpenAlice</h1>
<h1 className="min-w-0 flex-1 truncate text-[15px] font-semibold text-text">OpenAlice</h1>
<span className="text-text-muted/70">⌄</span>
</div>

{/* Navigation */}
<nav className="flex-1 flex flex-col px-2 overflow-y-auto pb-3">
<nav className="flex-1 flex flex-col px-3 overflow-y-auto pb-3">
{NAV_SECTIONS.map((section, si) => {
const labeled = section.sectionLabel.length > 0
// User toggle wins over default. The collapse store stores
Expand Down Expand Up @@ -228,7 +229,7 @@ export function ActivityBar({ open, onClose, onItemActivated }: ActivityBarProps
/>
)}
{showItems && (
<div className="flex flex-col gap-0.5" id={`activity-section-${si}`}>
<div className="flex flex-col gap-1" id={`activity-section-${si}`}>
{section.items.map((item) => {
const sec = activitySectionFor(item.page)
const isActive = selectedSidebar === sec
Expand Down Expand Up @@ -263,8 +264,8 @@ export function ActivityBar({ open, onClose, onItemActivated }: ActivityBarProps
title={t(item.labelKey)}
className={`relative flex items-center gap-3 px-3 py-1.5 rounded-md text-[13px] transition-colors text-left ${
isActive
? 'bg-bg-tertiary text-text'
: 'text-text-muted hover:text-text hover:bg-bg-tertiary/50'
? 'bg-bg-tertiary text-text shadow-[inset_0_0_0_1px_rgba(255,255,255,0.045)]'
: 'text-text-muted hover:text-text hover:bg-white/[0.035]'
}`}
>
{/* Active indicator — left vertical bar */}
Expand All @@ -281,7 +282,7 @@ export function ActivityBar({ open, onClose, onItemActivated }: ActivityBarProps
{item.page === 'inbox' && unreadInbox > 0 && (
<span
aria-label={t('nav.unread', { count: unreadInbox })}
className="shrink-0 min-w-[18px] h-[18px] px-1.5 rounded-full bg-red text-[10px] font-semibold text-white tabular-nums flex items-center justify-center"
className="shrink-0 min-w-[18px] h-[18px] px-1.5 rounded-full border border-border bg-bg-tertiary text-[10px] font-semibold text-text-muted tabular-nums flex items-center justify-center"
>
{unreadInbox > 99 ? '99+' : unreadInbox}
</span>
Expand Down Expand Up @@ -340,7 +341,7 @@ function SectionHeader({
<button
type="button"
onClick={onToggleCollapse}
className="flex-1 flex items-center gap-1.5 py-1 text-[11px] font-medium text-text-muted/60 hover:text-text-muted uppercase tracking-wider transition-colors text-left"
className="flex-1 flex items-center gap-1.5 py-1 text-[12px] font-semibold text-text-muted/75 hover:text-text-muted transition-colors text-left"
aria-expanded={!isCollapsed}
aria-controls={controlsId}
>
Expand Down
101 changes: 82 additions & 19 deletions ui/src/components/EmptyEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,89 @@
import { ArrowUp, Boxes, Paperclip, Sparkles } from 'lucide-react'
import { useTranslation } from 'react-i18next'

/**
* Shown in the main editor area when no tabs are open. Phase-2 minimal:
* logo + a couple of plain-text pointers so a fresh user knows where to
* start. The full onboarding system (guided setup, status checks, etc.)
* is a separate effort that will replace this surface.
* Default editor surface when no tab is open.
*
* The empty state is intentionally a product surface, not onboarding copy:
* the central composer is the primary object on screen and the surrounding
* canvas stays quiet until Alice has something to render.
*/
export function EmptyEditor() {
const { t } = useTranslation()

return (
<div className="flex flex-col items-center justify-center h-full select-none px-6 gap-5 text-center">
<img
src="/alice.ico"
alt="OpenAlice"
className="w-16 h-16 rounded-2xl ring-1 ring-accent/25 shadow-[0_0_18px_rgba(88,166,255,0.18)]"
draggable={false}
/>
<div className="space-y-2 max-w-md">
<h2 className="text-base font-semibold text-text">OpenAlice</h2>
<p className="text-[13px] text-text-muted leading-relaxed">
Click an icon on the activity bar to open its sidebar, then pick something from the sidebar to open it as a tab.
</p>
<p className="text-[12px] text-text-muted/70 leading-relaxed">
First time here? Open <span className="text-text">Settings → AI Provider</span> to configure a model, then jump back to <span className="text-text">Chat</span>.
</p>
<div className="relative h-full min-h-0 overflow-hidden bg-bg text-text select-none">
<div className="absolute inset-x-0 top-0 h-40 bg-gradient-to-b from-white/[0.035] to-transparent" />
<div className="absolute inset-x-0 bottom-0 h-[38%] bg-gradient-to-t from-black/35 to-transparent" />

<div className="absolute inset-0 opacity-[0.08] [background-image:linear-gradient(to_right,#ffffff_1px,transparent_1px),linear-gradient(to_bottom,#ffffff_1px,transparent_1px)] [background-size:96px_96px]" />

<div className="absolute left-1/2 top-[29%] h-[26rem] w-[26rem] -translate-x-1/2 rounded-full border border-white/[0.045]" />
<div className="absolute left-1/2 top-[38%] h-12 w-80 -translate-x-[43%] rotate-[-45deg] rounded-xl bg-white/[0.025]" />
<div className="absolute left-1/2 top-[43%] h-10 w-64 -translate-x-[52%] rotate-[-45deg] rounded-xl bg-white/[0.02]" />
<div className="absolute left-1/2 top-[48%] h-10 w-44 -translate-x-[62%] rotate-[-45deg] rounded-xl bg-white/[0.018]" />

<div className="relative z-10 flex h-full flex-col items-center justify-center px-6">
<div className="w-full max-w-[890px] rounded-xl border border-border bg-bg-tertiary/88 shadow-[0_34px_90px_rgba(0,0,0,0.48)] backdrop-blur-sm">
<label htmlFor="ask-alice-input" className="sr-only">{t('home.askLabel')}</label>
<textarea
id="ask-alice-input"
rows={2}
placeholder={t('home.askPlaceholder')}
className="block h-20 w-full resize-none bg-transparent px-6 py-5 text-[20px] font-medium leading-7 text-text outline-none placeholder:text-text-muted/55"
/>

<div className="flex h-14 items-center gap-3 border-t border-border/80 px-5">
<button
type="button"
className="inline-flex h-8 items-center gap-2 rounded-lg px-2.5 text-[13px] font-semibold text-text-muted transition-colors hover:bg-white/[0.045] hover:text-text"
>
<Boxes size={15} strokeWidth={1.8} />
<span>{t('home.skills')}</span>
</button>

<div className="inline-flex h-7 items-center gap-2 rounded-full border border-border bg-white/[0.035] px-3 text-[12px] font-medium text-text-muted">
<span className="h-1.5 w-1.5 rounded-full bg-accent shadow-[0_0_12px_rgba(35,185,154,0.65)]" />
<span>{t('home.agentRuntime')}</span>
</div>

<div className="ml-auto flex items-center gap-2">
<button
type="button"
aria-label={t('home.attachContext')}
className="flex h-8 w-8 items-center justify-center rounded-full text-text-muted transition-colors hover:bg-white/[0.045] hover:text-text"
>
<Paperclip size={16} strokeWidth={1.9} />
</button>
<button
type="button"
aria-label={t('home.submit')}
className="flex h-9 w-9 items-center justify-center rounded-full bg-white/[0.07] text-text-muted transition-colors hover:bg-white/[0.11] hover:text-text"
>
<ArrowUp size={17} strokeWidth={2.1} />
</button>
</div>
</div>
</div>

<div className="mt-3 flex w-full max-w-[872px] items-center gap-3 rounded-full border border-border/80 bg-[#0f1012]/90 px-3 py-2 text-[13px] shadow-[0_18px_44px_rgba(0,0,0,0.3)]">
<span className="rounded-full border border-border bg-bg-tertiary px-2.5 py-1 font-mono text-[10px] font-medium text-text-muted">
{t('home.newBadge')}
</span>
<span className="flex-1 truncate font-semibold text-text/85">
{t('home.sharedSkills')}
</span>
<button type="button" className="hidden text-text-muted transition-colors hover:text-text sm:inline">
{t('home.dismiss')}
</button>
<button
type="button"
className="inline-flex h-7 items-center gap-1.5 rounded-full bg-white/[0.08] px-3 text-[12px] font-semibold text-text transition-colors hover:bg-white/[0.12]"
>
<Sparkles size={13} strokeWidth={1.8} />
{t('home.shareSkills')}
</button>
</div>
</div>
</div>
)
Expand Down
4 changes: 2 additions & 2 deletions ui/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ interface SidebarProps {
export function Sidebar({ title, actions, children, leading }: SidebarProps) {
return (
<aside className="flex h-full w-full flex-col bg-bg-secondary">
<div className="flex items-center justify-between px-3 h-10 shrink-0 gap-2">
<div className="flex items-center justify-between px-4 h-10 shrink-0 gap-2 border-b border-border/60">
<div className="flex items-center gap-1.5 min-w-0">
{leading}
<h2 className="text-[13px] font-medium text-text truncate">{title}</h2>
<h2 className="text-[13px] font-semibold text-text truncate">{title}</h2>
</div>
{actions && <div className="flex items-center gap-0.5 shrink-0">{actions}</div>}
</div>
Expand Down
34 changes: 18 additions & 16 deletions ui/src/components/TabHost.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,24 @@ export function TabHost() {
const isDesktop = useIsDesktop()

return (
<div className="flex flex-col flex-1 min-h-0">
<TabStrip />
<div className="relative flex-1 min-h-0">
{tabIds.length === 0 ? (
<EmptyEditor />
) : (
tabIds.map((id) => {
const tab = tabsMap[id]
if (!tab) return null
const isActive = id === activeTabId
// Mobile: only render the active tab to avoid blowing memory and
// because we don't even have a strip to switch tabs from.
if (!isDesktop && !isActive) return null
return <TabFrame key={id} tab={tab} visible={isActive} />
})
)}
<div className="flex flex-col flex-1 min-h-0 bg-bg p-3">
<div className="flex flex-col flex-1 min-h-0 overflow-hidden rounded-xl border border-border/80 bg-[#0a0a0b] shadow-[inset_0_1px_0_rgba(255,255,255,0.025)]">
<TabStrip />
<div className="relative flex-1 min-h-0">
{tabIds.length === 0 ? (
<EmptyEditor />
) : (
tabIds.map((id) => {
const tab = tabsMap[id]
if (!tab) return null
const isActive = id === activeTabId
// Mobile: only render the active tab to avoid blowing memory and
// because we don't even have a strip to switch tabs from.
if (!isDesktop && !isActive) return null
return <TabFrame key={id} tab={tab} visible={isActive} />
})
)}
</div>
</div>
</div>
)
Expand Down
10 changes: 5 additions & 5 deletions ui/src/components/TabStrip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export function TabStrip() {
<>
<div
onWheel={handleWheel}
className="scrollbar-hide hidden md:flex shrink-0 h-9 bg-bg-secondary border-b border-border overflow-x-auto"
className="scrollbar-hide hidden md:flex shrink-0 h-10 bg-bg-secondary/95 border-b border-border/80 overflow-x-auto"
>
{tabIds.map((id) => {
const tab = tabsMap[id]
Expand Down Expand Up @@ -150,10 +150,10 @@ function TabButton({ title, active, onSelect, onClose, onContextMenu }: TabButto
}
}}
onContextMenu={onContextMenu}
className={`group flex items-center gap-2 pl-3 pr-2 h-full text-[13px] cursor-pointer border-r border-border transition-colors ${
className={`group flex items-center gap-2 pl-3 pr-2 h-full text-[13px] cursor-pointer border-r border-border/80 transition-colors ${
active
? 'bg-bg text-text'
: 'text-text-muted hover:text-text hover:bg-bg-tertiary/40'
? 'bg-bg-tertiary text-text'
: 'text-text-muted hover:text-text hover:bg-white/[0.035]'
}`}
>
<span className="truncate max-w-[200px]">{title}</span>
Expand All @@ -163,7 +163,7 @@ function TabButton({ title, active, onSelect, onClose, onContextMenu }: TabButto
e.stopPropagation()
onClose()
}}
className="w-4 h-4 rounded flex items-center justify-center text-text-muted/60 hover:text-text hover:bg-bg-tertiary"
className="w-4 h-4 rounded flex items-center justify-center text-text-muted/60 hover:text-text hover:bg-white/[0.06]"
aria-label={`Close ${title}`}
>
<X size={11} strokeWidth={2.5} />
Expand Down
14 changes: 13 additions & 1 deletion ui/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const en = {
item: {
inbox: 'Inbox',
tracked: 'Tracked',
chat: 'Chat',
chat: 'Ask Alice',
workspaces: 'Workspaces',
market: 'Market',
news: 'News',
Expand All @@ -36,6 +36,18 @@ export const en = {
unread: '{{count}} unread',
about: 'About {{label}}',
},
home: {
askLabel: 'Ask Alice',
askPlaceholder: 'Ask Alice...',
skills: 'Skills',
agentRuntime: 'Agent Runtime',
attachContext: 'Attach context',
submit: 'Submit',
newBadge: 'NEW',
sharedSkills: 'Shared skills: create and use skills across your team',
dismiss: 'Dismiss',
shareSkills: 'Share skills',
},
settings: {
title: 'Settings',
tab: {
Expand Down
14 changes: 13 additions & 1 deletion ui/src/i18n/locales/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const ja: Resources = {
item: {
inbox: '受信トレイ',
tracked: 'トラッキング',
chat: 'チャット',
chat: 'Alice に聞く',
workspaces: 'ワークスペース',
market: 'マーケット',
news: 'ニュース',
Expand All @@ -25,6 +25,18 @@ export const ja: Resources = {
unread: '未読 {{count}} 件',
about: '{{label}}について',
},
home: {
askLabel: 'Alice に聞く',
askPlaceholder: 'Alice に聞く...',
skills: 'スキル',
agentRuntime: 'エージェントランタイム',
attachContext: 'コンテキストを添付',
submit: '送信',
newBadge: 'NEW',
sharedSkills: '共有スキル: チーム全体でスキルを作成して使用',
dismiss: '閉じる',
shareSkills: 'スキルを共有',
},
settings: {
title: '設定',
tab: {
Expand Down
Loading