Expand AITURK Turkish settings and desktop surfaces for beta 5

This commit is contained in:
yilsem committed 2026-09-06 05:03:32 +03:00
1 parent 237d1e2060
commit 5e76f58637
108 files changed
+6675 -1034

No files matched your search

@@ -245,7 +245,7 @@ export function VoicePlaybackActivity() {
variant="ghost"
>
<VolumeX className={iconSize.xs} />
Stop
{t.surfaces.stop}
</Button>
</div>
)
+16 -6
View File
@@ -11,6 +11,7 @@ import { lazy, type ReactNode, Suspense } from 'react'
import { ContribBoundary, ContribRender } from '@/contrib/react/boundary'
import { useContributions } from '@/contrib/react/use-contributions'
import { translateNow, type Translations, useI18n } from '@/i18n'
import { $routeTiles, closeRouteTile, type RouteTile } from '@/store/route-tiles'
import { ARTIFACTS_ROUTE, contributedRoutes, MESSAGING_ROUTE, ROUTES_AREA, SKILLS_ROUTE } from '../routes'
@@ -22,10 +23,10 @@ const MessagingView = lazy(async () => ({ default: (await import('../messaging')
const ArtifactsView = lazy(async () => ({ default: (await import('../artifacts')).ArtifactsView }))
// Built-in page views + their pane titles, keyed by route.
const BUILTIN_PAGES: Record<string, { render: () => ReactNode; title: string }> = {
[ARTIFACTS_ROUTE]: { render: () => <ArtifactsView />, title: 'Artifacts' },
[MESSAGING_ROUTE]: { render: () => <MessagingView />, title: 'Messaging' },
[SKILLS_ROUTE]: { render: () => <SkillsView />, title: 'Capabilities' }
const BUILTIN_PAGES: Record<string, { render: () => ReactNode; titleKey: keyof Translations['sidebar']['nav'] }> = {
[ARTIFACTS_ROUTE]: { render: () => <ArtifactsView />, titleKey: 'artifacts' },
[MESSAGING_ROUTE]: { render: () => <MessagingView />, titleKey: 'messaging' },
[SKILLS_ROUTE]: { render: () => <SkillsView />, titleKey: 'skills' }
}
/** Humanize a route path into a tab title: `/my-atlas` → `My Atlas`. */
@@ -41,13 +42,21 @@ const humanizePath = (path: string): string =>
* else a humanized path — never the internal `${source}:${id}` key. */
function routeTitle(path: string): string {
if (BUILTIN_PAGES[path]) {
return BUILTIN_PAGES[path].title
return translateNow(`sidebar.nav.${BUILTIN_PAGES[path].titleKey}`)
}
return contributedRoutes().find(r => r.path === path)?.title ?? humanizePath(path)
}
function RouteTileTitle({ path }: { path: string }) {
const { t } = useI18n()
const builtin = BUILTIN_PAGES[path]
return <>{builtin ? t.sidebar.nav[builtin.titleKey] : routeTitle(path)}</>
}
function RouteTilePane({ path }: { path: string }) {
const { t } = useI18n()
const builtin = BUILTIN_PAGES[path]
// Subscribe so a plugin page tile appears the moment its route registers.
@@ -74,7 +83,7 @@ function RouteTilePane({ path }: { path: string }) {
return (
<div className="grid h-full place-items-center font-mono text-[11px] text-(--ui-text-quaternary)">
no page at {path}
{t.surfaces.noPage(path)}
</div>
)
}
@@ -91,6 +100,7 @@ export const watchRouteTiles = paneMirror<RouteTile>({
dir: t => t.dir,
minWidth: '22rem',
title: routeTitle,
tabTitle: path => <RouteTileTitle path={path} />,
render: path => <RouteTilePane path={path} />,
close: closeRouteTile
})
+3 -2
View File
@@ -333,6 +333,7 @@ function TileChat({
}
export function SessionTilePane({ storedSessionId }: { storedSessionId: string }) {
const { t } = useI18n()
const tiles = useStore($sessionTiles)
const tile = tiles.find(t => t.storedSessionId === storedSessionId)
const ownerRoute = tile?.ownerRoute
@@ -462,10 +463,10 @@ export function SessionTilePane({ storedSessionId }: { storedSessionId: string }
return (
<div className="grid h-full place-items-center p-4">
<div className="max-w-[24rem] space-y-2 text-center font-mono text-[11px]">
<div className="text-(--ui-danger,#f87171)">Couldn't open this session</div>
<div className="text-(--ui-danger,#f87171)">{t.surfaces.sessionUnavailable}</div>
<div className="break-words text-(--ui-text-quaternary)">{tile.error}</div>
<Button onClick={() => patchSessionTile(storedSessionId, { error: undefined })} size="sm" variant="outline">
Retry
{t.common.retry}
</Button>
</div>
</div>
@@ -72,47 +72,6 @@ interface Option<T extends string = string> {
label: string
}
const GROUPINGS: Option<SidebarGrouping>[] = [
{ icon: 'clock', id: 'date', label: 'Updated' },
{ icon: 'root-folder', id: 'project', label: 'Project' },
{ icon: 'pulse', id: 'status', label: 'Status' },
{ icon: 'account', id: 'profile', label: 'Profile' }
]
const ORDERINGS: Option<SidebarOrdering>[] = [
{ icon: 'clock', id: 'updated', label: 'Updated' },
{ icon: 'add', id: 'created', label: 'Created' },
{ icon: 'pulse', id: 'status', label: 'Status' },
{ icon: 'symbol-numeric', id: 'tokens', label: 'Tokens' },
{ icon: 'credit-card', id: 'cost', label: 'Cost' },
{ icon: 'list-ordered', id: 'manual', label: 'Manual' }
]
const ROW_META: Option<SidebarRowMeta>[] = [
{ icon: 'clock', id: 'updated', label: 'Updated' },
{ icon: 'comment', id: 'preview', label: 'Preview' },
{ icon: 'symbol-numeric', id: 'tokens', label: 'Tokens' },
{ icon: 'credit-card', id: 'cost', label: 'Cost' },
{ icon: 'git-pull-request', id: 'pr', label: 'PR' },
{ icon: 'account', id: 'profile', label: 'Profile' }
]
const PR_FILTERS: Option<PullRequestBucket>[] = [
{ icon: 'git-pull-request', id: 'open', label: 'Open' },
{ icon: 'git-pull-request-draft', id: 'draft', label: 'Draft' },
{ icon: 'git-merge', id: 'merged', label: 'Merged' },
{ icon: 'git-pull-request-closed', id: 'closed', label: 'Closed' },
{ icon: 'circle-slash', id: 'none', label: 'No PR' }
]
const STATUS_FILTERS: Option<SessionStatusBucket>[] = [
{ dot: sessionDotClassName('needs-input'), id: 'needs-input', label: 'Needs input' },
{ dot: sessionDotClassName('working'), id: 'working', label: 'Working' },
{ dot: sessionDotClassName('unread'), id: 'unread', label: 'Unread' },
{ dot: sessionDotClassName('draft'), id: 'draft', label: 'Draft' },
{ dot: cn(sessionDotClassName('idle'), 'bg-(--ui-text-quaternary)'), id: 'idle', label: 'Idle' }
]
function OptionGlyph({ option }: { option: Option }) {
if (option.dot) {
return <span aria-hidden="true" className={cn('shrink-0', option.dot)} />
@@ -121,7 +80,7 @@ function OptionGlyph({ option }: { option: Option }) {
return option.icon ? <Codicon className="text-(--ui-text-tertiary)" name={option.icon} size="0.8125rem" /> : null
}
/** Every option row — single or multi select — leaves the menu open, so a whole
/** Every option row — single or multi select — leaves the menu open, so a whole
* view can be set up in one pass. Only the actions at the bottom dismiss it. */
const keepOpen = (event: Event) => event.preventDefault()
@@ -151,6 +110,48 @@ function OptionRadio({ option }: { option: Option }) {
export function SidebarFilterMenu({ className }: { className?: string }) {
const { t } = useI18n()
const GROUPINGS: Option<SidebarGrouping>[] = [
{ icon: 'clock', id: 'date', label: t.surfaces.filters.updated },
{ icon: 'root-folder', id: 'project', label: t.surfaces.filters.project },
{ icon: 'pulse', id: 'status', label: t.surfaces.filters.status },
{ icon: 'account', id: 'profile', label: t.surfaces.filters.profile }
]
const ORDERINGS: Option<SidebarOrdering>[] = [
{ icon: 'clock', id: 'updated', label: t.surfaces.filters.updated },
{ icon: 'add', id: 'created', label: t.surfaces.filters.created },
{ icon: 'pulse', id: 'status', label: t.surfaces.filters.status },
{ icon: 'symbol-numeric', id: 'tokens', label: t.surfaces.filters.tokens },
{ icon: 'credit-card', id: 'cost', label: t.surfaces.filters.cost },
{ icon: 'list-ordered', id: 'manual', label: t.surfaces.filters.manual }
]
const ROW_META: Option<SidebarRowMeta>[] = [
{ icon: 'clock', id: 'updated', label: t.surfaces.filters.updated },
{ icon: 'comment', id: 'preview', label: t.surfaces.filters.preview },
{ icon: 'symbol-numeric', id: 'tokens', label: t.surfaces.filters.tokens },
{ icon: 'credit-card', id: 'cost', label: t.surfaces.filters.cost },
{ icon: 'git-pull-request', id: 'pr', label: t.surfaces.filters.pr },
{ icon: 'account', id: 'profile', label: t.surfaces.filters.profile }
]
const PR_FILTERS: Option<PullRequestBucket>[] = [
{ icon: 'git-pull-request', id: 'open', label: t.surfaces.filters.open },
{ icon: 'git-pull-request-draft', id: 'draft', label: t.surfaces.filters.draft },
{ icon: 'git-merge', id: 'merged', label: t.surfaces.filters.merged },
{ icon: 'git-pull-request-closed', id: 'closed', label: t.surfaces.filters.closed },
{ icon: 'circle-slash', id: 'none', label: t.surfaces.filters.noPr }
]
const STATUS_FILTERS: Option<SessionStatusBucket>[] = [
{ dot: sessionDotClassName('needs-input'), id: 'needs-input', label: t.surfaces.filters.needsInput },
{ dot: sessionDotClassName('working'), id: 'working', label: t.surfaces.filters.working },
{ dot: sessionDotClassName('unread'), id: 'unread', label: t.surfaces.filters.unread },
{ dot: sessionDotClassName('draft'), id: 'draft', label: t.surfaces.filters.draft },
{ dot: cn(sessionDotClassName('idle'), 'bg-(--ui-text-quaternary)'), id: 'idle', label: t.surfaces.filters.idle }
]
const grouping = useStore($sidebarGrouping)
const ordering = useStore($sidebarOrdering)
const rowMeta = useStore($sidebarRowMeta)
@@ -170,7 +171,7 @@ export function SidebarFilterMenu({ className }: { className?: string }) {
const projects = useStore($projectTree)
const hasCost = useStore($sessionsHaveCost)
const unreadIds = useStore($unreadFinishedSessionIds)
// PR state comes from `gh` on whichever machine holds the checkout — Electron
// PR state comes from `gh` on whichever machine holds the checkout — Electron
// locally, the gateway's REST mirror remotely. Resolved per render, not once
// at module load: switching to a remote profile swaps the bridge underneath.
const prAvailable = Boolean(desktopGit()?.review?.prList)
@@ -217,12 +218,12 @@ export function SidebarFilterMenu({ className }: { className?: string }) {
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
aria-label="Filters"
aria-label={t.surfaces.filters.filters}
className={cn(
className,
'data-[state=open]:bg-(--ui-control-active-background) data-[state=open]:text-foreground data-[state=open]:opacity-100',
// Active filters read as "this control is engaged", the same way the
// open menu does — never as an accent, which the sidebar reserves
// open menu does — never as an accent, which the sidebar reserves
// for a session that is actually doing something.
filtersActive && 'bg-(--ui-control-active-background) text-foreground opacity-100'
)}
@@ -238,7 +239,7 @@ export function SidebarFilterMenu({ className }: { className?: string }) {
<DropdownMenuGroup>
<DropdownMenuSub>
<DropdownMenuSubTrigger hideChevron>
Grouping
{t.surfaces.filters.grouping}
<span className="ml-auto flex items-center gap-1 pl-4 text-(--ui-text-tertiary)">
{groupingLabel}
<Codicon name="chevron-right" size="1rem" />
@@ -257,7 +258,7 @@ export function SidebarFilterMenu({ className }: { className?: string }) {
</DropdownMenuSub>
<DropdownMenuSub>
<DropdownMenuSubTrigger>Ordering</DropdownMenuSubTrigger>
<DropdownMenuSubTrigger>{t.surfaces.filters.ordering}</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuRadioGroup
onValueChange={value => setSidebarOrdering(value as SidebarOrdering)}
@@ -271,7 +272,7 @@ export function SidebarFilterMenu({ className }: { className?: string }) {
</DropdownMenuSub>
<DropdownMenuSub>
<DropdownMenuSubTrigger>Show</DropdownMenuSubTrigger>
<DropdownMenuSubTrigger>{t.surfaces.filters.show}</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
{rowMetaOptions.map(option => (
<OptionCheckbox
@@ -284,22 +285,22 @@ export function SidebarFilterMenu({ className }: { className?: string }) {
</DropdownMenuSubContent>
</DropdownMenuSub>
{/* A render variant, not a grouping: three-line cards (project · age /
title / model · size) compose with whichever grouping is active. */}
{/* A render variant, not a grouping: three-line cards (project · age /
title / model · size) compose with whichever grouping is active. */}
<OptionCheckbox
checked={cardRows}
onCheck={() => setSidebarCardRows(!cardRows)}
option={{ icon: 'inbox', id: 'card-rows', label: 'Inbox style' }}
option={{ icon: 'inbox', id: 'card-rows', label: t.surfaces.filters.inbox }}
/>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuLabel>Filters</DropdownMenuLabel>
<DropdownMenuLabel>{t.surfaces.filters.filters}</DropdownMenuLabel>
<DropdownMenuSub>
<DropdownMenuSubTrigger>Status</DropdownMenuSubTrigger>
<DropdownMenuSubTrigger>{t.surfaces.filters.status}</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
{STATUS_FILTERS.map(option => (
<OptionCheckbox
@@ -316,7 +317,7 @@ export function SidebarFilterMenu({ className }: { className?: string }) {
this submenu never appears rather than filtering everything out. */}
{prAvailable && (
<DropdownMenuSub>
<DropdownMenuSubTrigger>Pull request</DropdownMenuSubTrigger>
<DropdownMenuSubTrigger>{t.surfaces.filters.pullRequest}</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
{PR_FILTERS.map(option => (
<OptionCheckbox
@@ -331,7 +332,7 @@ export function SidebarFilterMenu({ className }: { className?: string }) {
)}
<DropdownMenuSub>
<DropdownMenuSubTrigger>Profile</DropdownMenuSubTrigger>
<DropdownMenuSubTrigger>{t.surfaces.filters.profile}</DropdownMenuSubTrigger>
<DropdownMenuSubContent className="max-h-80 overflow-y-auto">
{/* Scoped to one profile the rail is already the filter, so the
per-profile boxes only appear where they can narrow something.
@@ -358,7 +359,7 @@ export function SidebarFilterMenu({ className }: { className?: string }) {
{projects.length > 1 && (
<DropdownMenuSub>
<DropdownMenuSubTrigger>Project</DropdownMenuSubTrigger>
<DropdownMenuSubTrigger>{t.surfaces.filters.project}</DropdownMenuSubTrigger>
<DropdownMenuSubContent className="max-h-80 overflow-y-auto">
{projects.map(project => (
<OptionCheckbox
@@ -378,7 +379,7 @@ export function SidebarFilterMenu({ className }: { className?: string }) {
)}
{/* Off by default: one profile's sessions are what the rail selected.
Nothing to widen to until a second profile exists — but stay
Nothing to widen to until a second profile exists — but stay
visible while it's on, or deleting your way back down to one
profile would strand the sidebar in a mode nothing can leave (the
rail hides its switcher at one profile too). */}
@@ -393,23 +394,25 @@ export function SidebarFilterMenu({ className }: { className?: string }) {
<OptionCheckbox
checked={showArchived}
onCheck={() => setSidebarShowArchived(!showArchived)}
option={{ id: 'archived', label: 'Archived' }}
option={{ id: 'archived', label: t.surfaces.filters.archived }}
/>
{/* One way back rather than two near-identical ones: this drops the
grouping and sort too, which "clear filters" left behind. */}
{viewCustomized && <DropdownMenuItem onSelect={resetSidebarView}>Reset to defaults</DropdownMenuItem>}
{viewCustomized && (
<DropdownMenuItem onSelect={resetSidebarView}>{t.surfaces.filters.reset}</DropdownMenuItem>
)}
</DropdownMenuGroup>
<DropdownMenuSeparator />
{foldIds.length > 0 && (
<DropdownMenuItem onSelect={() => setWorkspaceNodesOpen(foldIds, foldCollapsed)}>
{foldCollapsed ? 'Expand all' : 'Collapse all'}
{foldCollapsed ? t.surfaces.filters.expand : t.surfaces.filters.collapse}
</DropdownMenuItem>
)}
<DropdownMenuItem disabled={unreadIds.length === 0} onSelect={markAllSessionsRead}>
Mark all as read
{t.surfaces.filters.markRead}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>