Add full shell, replaceable Omics adapter and bilingual interaction
Implement approved specification #32 and tickets #33-#37. Keep host authentication server-verified and pin session interaction language. Compile scoped base selectors for browser compatibility and retain full gutters during CSS pruning.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { useI18n } from "../i18n";
|
||||
import { useLayoutEffect, useRef } from "react";
|
||||
import { Settings2, X } from "lucide-react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
@@ -46,11 +47,12 @@ function MarkdownBody({ entry }: { entry: ActivityEntry }) {
|
||||
}
|
||||
|
||||
function ActivityRow({ entry }: { entry: ActivityEntry }) {
|
||||
const { t: translate } = useI18n();
|
||||
return (
|
||||
<article className="w-full min-w-0 border-b border-border/50 py-2 last:border-b-0">
|
||||
<div className="mb-1 flex flex-wrap items-center gap-1 text-[0.65rem] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
|
||||
<span className="text-foreground/80">{ACTIVITY_LABELS[entry.kind]}</span>
|
||||
{entry.phase && <span aria-label={`Phase ${entry.phase}`}>{entry.phase}</span>}
|
||||
<span className="text-foreground/80">{translate(ACTIVITY_LABELS[entry.kind] ?? entry.kind)}</span>
|
||||
{entry.phase && <span aria-label={translate("Phase {phase}", { phase: entry.phase })}>{entry.phase}</span>}
|
||||
</div>
|
||||
<MarkdownBody entry={entry} />
|
||||
</article>
|
||||
@@ -69,6 +71,7 @@ export function ModelActivityPanel({
|
||||
onOpenWorkspaceManager?: () => void;
|
||||
hidden?: boolean;
|
||||
}) {
|
||||
const { t: translate } = useI18n();
|
||||
const activityLog = useSessionStore((s) => s.activityLog);
|
||||
const visibleActivity = activityLog.filter(isVisibleModelActivity);
|
||||
const visibleCount = visibleActivity.length;
|
||||
@@ -87,12 +90,11 @@ export function ModelActivityPanel({
|
||||
: "fixed inset-y-0 left-0 z-30 flex min-w-0 w-[min(90vw,24rem)] shrink-0 flex-col border-r border-border bg-sidebar"
|
||||
}>
|
||||
<div className="flex items-center justify-between gap-2 border-b border-border/60 px-3 py-2">
|
||||
<h2 className="font-heading text-sm font-semibold text-foreground">Model activity</h2>
|
||||
<h2 className="font-heading text-sm font-semibold text-foreground">{translate("Model activity")}</h2>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button size="sm" variant="ghost" aria-label="Workspace management" onClick={onOpenWorkspaceManager}>
|
||||
<Settings2 className="size-3.5" />Workspace management
|
||||
</Button>
|
||||
<Button size="icon-sm" variant="ghost" aria-label="Close model activity" onClick={onClose}>
|
||||
<Button size="sm" variant="ghost" aria-label={translate("Workspace management")} onClick={onOpenWorkspaceManager}>
|
||||
<Settings2 className="size-3.5" />{translate("Workspace management")}</Button>
|
||||
<Button size="icon-sm" variant="ghost" aria-label={translate("Close model activity")} onClick={onClose}>
|
||||
<X className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -101,14 +103,14 @@ export function ModelActivityPanel({
|
||||
ref={scrollRef}
|
||||
data-testid="activity-scroll"
|
||||
tabIndex={0}
|
||||
aria-label="Model activity timeline"
|
||||
aria-label={translate("Model activity timeline")}
|
||||
className="w-full min-w-0 flex-1 overflow-y-auto px-3 py-2 outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/40"
|
||||
onScroll={(event) => {
|
||||
followRef.current = isNearBottom(event.currentTarget);
|
||||
}}
|
||||
>
|
||||
{visibleActivity.length === 0 ? (
|
||||
<p className="py-3 text-sm text-muted-foreground">No activity yet.</p>
|
||||
<p className="py-3 text-sm text-muted-foreground">{translate("No activity yet.")}</p>
|
||||
) : (
|
||||
visibleActivity.map((entry, index) => (
|
||||
<ActivityRow key={`${index}-${entry.toolCallId ?? entry.kind}`} entry={entry} />
|
||||
|
||||
Reference in New Issue
Block a user