chore: merge dev into v2 (#36144)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Co-authored-by: usrnk1 <7547651+usrnk1@users.noreply.github.com> Co-authored-by: James Long <longster@gmail.com> Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Co-authored-by: Jack <jack@anoma.ly> Co-authored-by: opencode <opencode@sst.dev> Co-authored-by: Frank <frank@anoma.ly> Co-authored-by: Jay <53023+jayair@users.noreply.github.com> Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com> Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com> Co-authored-by: Julian Coy <julian@ex-machina.co> Co-authored-by: Brendan Allan <git@brendonovich.dev> Co-authored-by: Vladimir Glafirov <vglafirov@gitlab.com> Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com> Co-authored-by: Dustin Deus <deusdustin@gmail.com> Co-authored-by: Kit Langton <kit.langton@gmail.com> Co-authored-by: Simon Klee <hello@simonklee.dk> Co-authored-by: Jay <air@live.ca> Co-authored-by: David Hill <1879069+iamdavidhill@users.noreply.github.com> Co-authored-by: Aiden Cline <aidenpcline@gmail.com> Co-authored-by: James Long <jlongster@users.noreply.github.com> Co-authored-by: 冯基魁 <56265583+fengjikui@users.noreply.github.com> Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
This commit is contained in:
co-authored by
opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
usrnk1
James Long
opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
Aiden Cline
Brendan Allan
Jack
opencode
Frank
Jay
Luke Parker
Aarav Sareen
Julian Coy
Brendan Allan
Vladimir Glafirov
Adam
Dustin Deus
Kit Langton
Simon Klee
Jay
David Hill
Aiden Cline
James Long
冯基魁
Aiden Cline
parent
a72992e00f
commit
a7746379d5
@@ -185,6 +185,14 @@
|
||||
background-color 0.15s ease,
|
||||
color 0.15s ease;
|
||||
|
||||
[data-component="task-tool-surface"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
[data-slot="basic-tool-tool-info-structured"] {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
@@ -250,16 +258,30 @@
|
||||
|
||||
body[data-new-layout] {
|
||||
[data-component="task-tool-card"] {
|
||||
padding: 8px 12px 8px 10px;
|
||||
border-radius: 8px;
|
||||
gap: 8px;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
border-color: transparent;
|
||||
background: var(--task-agent-background, light-dark(#fafafa, rgba(250, 250, 250, 0.15)));
|
||||
box-shadow: inset 0 0 0 0.5px var(--task-agent-border, var(--v2-border-border-base));
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
|
||||
[data-component="task-tool-surface"] {
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
background: var(--v2-background-bg-layer-01);
|
||||
}
|
||||
|
||||
[data-component="task-tool-spinner"],
|
||||
[data-component="task-tool-title"] {
|
||||
color: var(--task-agent-color, var(--text-strong));
|
||||
[data-component="task-tool-icon"] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
[data-component="icon"] {
|
||||
color: var(--task-agent-color, var(--v2-icon-icon-base));
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="task-tool-title"] {
|
||||
@@ -270,6 +292,7 @@ body[data-new-layout] {
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.04px;
|
||||
color: var(--v2-text-text-base);
|
||||
flex: none;
|
||||
flex-grow: 0;
|
||||
}
|
||||
@@ -281,7 +304,7 @@ body[data-new-layout] {
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.04px;
|
||||
color: light-dark(var(--v2-text-text-base), #fafafa);
|
||||
color: var(--v2-text-text-base);
|
||||
font-variation-settings: "slnt" 0;
|
||||
flex: none;
|
||||
flex-grow: 0;
|
||||
@@ -294,7 +317,11 @@ body[data-new-layout] {
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
border-color: transparent;
|
||||
background: var(--task-agent-background, light-dark(#fafafa, rgba(250, 250, 250, 0.15)));
|
||||
background: transparent;
|
||||
|
||||
[data-component="task-tool-surface"] {
|
||||
background: var(--v2-background-bg-layer-01);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,22 +369,12 @@ const agentTones: Record<string, string> = {
|
||||
plan: "var(--icon-agent-plan-base)",
|
||||
}
|
||||
|
||||
const v2AgentTones: Record<string, { color: string; border: string; background: string }> = {
|
||||
build: {
|
||||
color: "var(--v2-agent-build-solid)",
|
||||
border: "var(--v2-agent-build-border)",
|
||||
background: "var(--v2-agent-build-background)",
|
||||
},
|
||||
explore: {
|
||||
color: "var(--v2-agent-explore-solid)",
|
||||
border: "var(--v2-agent-explore-border)",
|
||||
background: "var(--v2-agent-explore-background)",
|
||||
},
|
||||
plan: {
|
||||
color: "var(--v2-agent-plan-solid)",
|
||||
border: "var(--v2-agent-plan-border)",
|
||||
background: "var(--v2-agent-plan-background)",
|
||||
},
|
||||
const v2AgentTones: Record<string, string> = {
|
||||
build: "var(--v2-agent-build-solid)",
|
||||
explore: "var(--v2-agent-explore-solid)",
|
||||
plan: "var(--v2-agent-plan-solid)",
|
||||
review: "var(--v2-agent-review-solid)",
|
||||
writer: "var(--v2-agent-writer-solid)",
|
||||
}
|
||||
|
||||
const agentThemeColors: Record<string, string> = {
|
||||
@@ -431,19 +421,17 @@ function tone(name: string) {
|
||||
function taskAgent(
|
||||
raw: unknown,
|
||||
list?: readonly { name: string; color?: string }[],
|
||||
): { name?: string; color?: string; v2Color?: string; border?: string; background?: string } {
|
||||
): { name?: string; color?: string; v2Color?: string } {
|
||||
if (typeof raw !== "string" || !raw) return {}
|
||||
const key = raw.toLowerCase()
|
||||
const item = list?.find((entry) => entry.name === raw || entry.name.toLowerCase() === key)
|
||||
const v2Tone = item?.color ? undefined : v2AgentTones[key]
|
||||
const color = agentColor(item?.color, agentThemeColors) ?? agentTones[key] ?? tone(key)
|
||||
const v2Color = agentColor(item?.color, v2AgentThemeColors) ?? v2Tone?.color ?? color
|
||||
const v2Color = agentColor(item?.color, v2AgentThemeColors) ?? v2Tone ?? color
|
||||
return {
|
||||
name: item?.name ?? `${raw[0]!.toUpperCase()}${raw.slice(1)}`,
|
||||
color,
|
||||
v2Color,
|
||||
border: v2Tone?.border ?? `color-mix(in srgb, ${v2Color} 48%, transparent)`,
|
||||
background: v2Tone?.background ?? `color-mix(in srgb, ${v2Color} 12%, transparent)`,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1934,8 +1922,6 @@ ToolRegistry.register({
|
||||
const title = createMemo(() => agent().name ?? i18n.t("ui.tool.agent.default"))
|
||||
const tone = createMemo(() => agent().color)
|
||||
const v2Tone = createMemo(() => agent().v2Color)
|
||||
const border = createMemo(() => agent().border)
|
||||
const background = createMemo(() => agent().background)
|
||||
const subtitle = createMemo(() => {
|
||||
const value =
|
||||
typeof props.input.description === "string" && props.input.description
|
||||
@@ -1980,25 +1966,34 @@ ToolRegistry.register({
|
||||
style={{
|
||||
"--task-agent-color": v2Tone(),
|
||||
"--task-agent-legacy-color": tone(),
|
||||
"--task-agent-border": border(),
|
||||
"--task-agent-background": background(),
|
||||
}}
|
||||
>
|
||||
<div data-slot="basic-tool-tool-info-structured">
|
||||
<div data-slot="basic-tool-tool-info-main">
|
||||
<Show when={running()}>
|
||||
<span data-component="task-tool-spinner" style={{ color: tone() ?? "var(--icon-interactive-base)" }}>
|
||||
<Show when={newLayout()} fallback={<Spinner />}>
|
||||
<SessionProgressIndicatorV2
|
||||
style={{ color: v2Tone() ?? "light-dark(var(--v2-text-text-base), #ffffff)" }}
|
||||
/>
|
||||
</Show>
|
||||
</span>
|
||||
</Show>
|
||||
<span data-component="task-tool-title">{title()}</span>
|
||||
<Show when={subtitle()}>
|
||||
<span data-slot="basic-tool-tool-subtitle">{subtitle()}</span>
|
||||
</Show>
|
||||
<div data-component="task-tool-surface">
|
||||
<div data-slot="basic-tool-tool-info-structured">
|
||||
<div data-slot="basic-tool-tool-info-main">
|
||||
<Show
|
||||
when={running()}
|
||||
fallback={
|
||||
<Show when={newLayout()}>
|
||||
<span data-component="task-tool-icon">
|
||||
<Icon name="subagent" size="small" />
|
||||
</span>
|
||||
</Show>
|
||||
}
|
||||
>
|
||||
<span data-component="task-tool-spinner" style={{ color: tone() ?? "var(--icon-interactive-base)" }}>
|
||||
<Show when={newLayout()} fallback={<Spinner />}>
|
||||
<SessionProgressIndicatorV2
|
||||
style={{ color: v2Tone() ?? "light-dark(var(--v2-text-text-base), #ffffff)" }}
|
||||
/>
|
||||
</Show>
|
||||
</span>
|
||||
</Show>
|
||||
<span data-component="task-tool-title">{title()}</span>
|
||||
<Show when={subtitle()}>
|
||||
<span data-slot="basic-tool-tool-subtitle">{subtitle()}</span>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={clickable()}>
|
||||
|
||||
@@ -359,6 +359,34 @@ const TOOL_SAMPLES = {
|
||||
title: "Agent (Explore)",
|
||||
metadata: { sessionId: "sub-session-1" },
|
||||
},
|
||||
"task build": {
|
||||
tool: "task",
|
||||
input: { description: "Implement the settings page", subagent_type: "build", prompt: "Build it" },
|
||||
output: "Implemented the settings page.",
|
||||
title: "Agent (Build)",
|
||||
metadata: { sessionId: "sub-session-2" },
|
||||
},
|
||||
"task plan": {
|
||||
tool: "task",
|
||||
input: { description: "Plan the migration strategy", subagent_type: "plan", prompt: "Plan it" },
|
||||
output: "Drafted a 4-step migration plan.",
|
||||
title: "Agent (Plan)",
|
||||
metadata: { sessionId: "sub-session-3" },
|
||||
},
|
||||
"task themed": {
|
||||
tool: "task",
|
||||
input: { description: "Review the diff for regressions", subagent_type: "review", prompt: "Review it" },
|
||||
output: "No regressions found.",
|
||||
title: "Agent (Review)",
|
||||
metadata: { sessionId: "sub-session-4" },
|
||||
},
|
||||
"task fallback": {
|
||||
tool: "task",
|
||||
input: { description: "Write release notes", subagent_type: "writer", prompt: "Write them" },
|
||||
output: "Release notes drafted.",
|
||||
title: "Agent (Writer)",
|
||||
metadata: { sessionId: "sub-session-5" },
|
||||
},
|
||||
webfetch: {
|
||||
tool: "webfetch",
|
||||
input: { url: "https://solidjs.com/docs/latest/api" },
|
||||
@@ -1197,6 +1225,7 @@ function Playground() {
|
||||
|
||||
const data = createMemo(() => ({
|
||||
session: [session()],
|
||||
agent: [{ name: "build" }, { name: "plan" }, { name: "explore" }, { name: "review" }],
|
||||
session_status: {},
|
||||
session_diff: {},
|
||||
message: { [session().id]: state.messages },
|
||||
@@ -1323,6 +1352,19 @@ function Playground() {
|
||||
])
|
||||
}
|
||||
|
||||
const addAgentTasksTurn = () => {
|
||||
addFullTurn("Run subagents with every agent type", [
|
||||
toolPart(TOOL_SAMPLES.task),
|
||||
toolPart(TOOL_SAMPLES["task build"]),
|
||||
toolPart(TOOL_SAMPLES["task plan"]),
|
||||
toolPart(TOOL_SAMPLES["task themed"]),
|
||||
toolPart(TOOL_SAMPLES["task fallback"]),
|
||||
toolPart(TOOL_SAMPLES.task, "running"),
|
||||
toolPart(TOOL_SAMPLES["task build"], "running"),
|
||||
toolPart(TOOL_SAMPLES["task plan"], "running"),
|
||||
])
|
||||
}
|
||||
|
||||
const addKitchenSink = () => {
|
||||
// User message variants
|
||||
addUser("short")
|
||||
@@ -1708,6 +1750,9 @@ function Playground() {
|
||||
<button style={btnStyle} onClick={addReasoningFullTurn}>
|
||||
full turn
|
||||
</button>
|
||||
<button style={btnStyle} onClick={addAgentTasksTurn}>
|
||||
agent tasks
|
||||
</button>
|
||||
<button style={btnAccent} onClick={addKitchenSink}>
|
||||
kitchen sink
|
||||
</button>
|
||||
@@ -1947,7 +1992,7 @@ function Playground() {
|
||||
{/* Main area: timeline preview */}
|
||||
<div
|
||||
ref={previewRef!}
|
||||
style={{ flex: "1", overflow: "auto", "min-width": "0", "background-color": "var(--background-stronger)" }}
|
||||
style={{ flex: "1", overflow: "auto", "min-width": "0", "background-color": "var(--v2-background-bg-base)" }}
|
||||
>
|
||||
<DataProvider data={data()} directory="/project">
|
||||
<FileComponentProvider component={FileStub}>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Show, type JSX, type ParentProps } from "solid-js"
|
||||
import "./session-review-v2.css"
|
||||
|
||||
export function SessionFilePanelV2(props: {
|
||||
sidebar?: JSX.Element
|
||||
toolbar: boolean
|
||||
toolbarStart?: JSX.Element
|
||||
toolbarEnd?: JSX.Element
|
||||
children?: JSX.Element
|
||||
}) {
|
||||
return (
|
||||
<div data-component="session-review-v2">
|
||||
<div data-slot="session-review-v2-body">
|
||||
{props.sidebar}
|
||||
<div data-slot="session-review-v2-preview">
|
||||
<Show when={props.toolbar}>
|
||||
<div data-slot="session-review-v2-toolbar">
|
||||
<div data-slot="session-review-v2-toolbar-group" class="session-review-v2-toolbar-group--start">
|
||||
{props.toolbarStart}
|
||||
</div>
|
||||
<Show when={props.toolbarEnd}>
|
||||
{(toolbar) => (
|
||||
<div data-slot="session-review-v2-toolbar-group" class="session-review-v2-toolbar-group--segments">
|
||||
{toolbar()}
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function SessionFilePanelV2Title(props: ParentProps) {
|
||||
return <div data-slot="session-review-v2-toolbar-title">{props.children}</div>
|
||||
}
|
||||
|
||||
export function SessionFilePanelV2Empty(props: ParentProps) {
|
||||
return <div data-slot="session-review-v2-empty">{props.children}</div>
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||
import { Show, createEffect, createMemo, createSignal, type JSX } from "solid-js"
|
||||
import { getWorkerPool } from "../../pierre/worker"
|
||||
import "./session-review-v2.css"
|
||||
import { SessionFilePanelV2, SessionFilePanelV2Empty } from "./session-file-panel-v2"
|
||||
|
||||
export const SESSION_REVIEW_V2_SIDEBAR_WIDTH_DEFAULT = 240
|
||||
export const SESSION_REVIEW_V2_SIDEBAR_WIDTH_MIN = 200
|
||||
@@ -45,6 +45,11 @@ export type SessionReviewV2SidebarProps = {
|
||||
filter: string
|
||||
onFilterChange: (value: string) => void
|
||||
onFilterKeyDown?: JSX.EventHandlerUnion<HTMLInputElement, KeyboardEvent>
|
||||
filterAutofocus?: boolean
|
||||
filterRef?: (element: HTMLInputElement) => void
|
||||
filterControls?: string
|
||||
filterActiveDescendant?: string
|
||||
filterExpanded?: boolean
|
||||
width?: number
|
||||
onWidthChange?: (width: number) => void
|
||||
minWidth?: number
|
||||
@@ -69,55 +74,62 @@ export function SessionReviewV2Sidebar(props: SessionReviewV2SidebarProps) {
|
||||
|
||||
return (
|
||||
<div data-component="session-review-v2-sidebar-root">
|
||||
<aside
|
||||
data-slot="session-review-v2-sidebar"
|
||||
data-resizing={resizing() ? "" : undefined}
|
||||
aria-hidden={!props.open}
|
||||
inert={!props.open}
|
||||
style={{ width: props.open ? `${width()}px` : "0px" }}
|
||||
>
|
||||
<div data-slot="session-review-v2-sidebar-header">
|
||||
<div data-slot="session-review-v2-sidebar-title">{props.title}</div>
|
||||
{props.stats}
|
||||
</div>
|
||||
<div data-slot="session-review-v2-sidebar-filter">
|
||||
<TextInputV2
|
||||
type="search"
|
||||
value={props.filter}
|
||||
onInput={(event) => props.onFilterChange(event.currentTarget.value)}
|
||||
onKeyDown={props.onFilterKeyDown}
|
||||
showClearButton={props.filter.length > 0}
|
||||
clearLabel={i18n.t("ui.list.clearFilter")}
|
||||
onClearClick={() => props.onFilterChange("")}
|
||||
placeholder={i18n.t("ui.sessionReviewV2.filterFiles")}
|
||||
aria-label={i18n.t("ui.sessionReviewV2.filterFiles")}
|
||||
leadingIcon={
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M12.25 12.25L10.0625 10.0625M11.0833 6.41667C11.0833 8.994 8.994 11.0833 6.41667 11.0833C3.83934 11.0833 1.75 8.994 1.75 6.41667C1.75 3.83934 3.83934 1.75 6.41667 1.75C8.994 1.75 11.0833 3.83934 11.0833 6.41667Z"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="square"
|
||||
/>
|
||||
</svg>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<ScrollView
|
||||
data-slot="session-review-v2-sidebar-tree"
|
||||
class="group/file-tree-v2"
|
||||
thumbVisibility="scroll"
|
||||
viewportRef={props.viewportRef}
|
||||
<Show when={props.open}>
|
||||
<aside
|
||||
data-slot="session-review-v2-sidebar"
|
||||
data-resizing={resizing() ? "" : undefined}
|
||||
style={{ width: `${width()}px` }}
|
||||
>
|
||||
{props.children}
|
||||
</ScrollView>
|
||||
</aside>
|
||||
<div data-slot="session-review-v2-sidebar-header">
|
||||
<div data-slot="session-review-v2-sidebar-title">{props.title}</div>
|
||||
{props.stats}
|
||||
</div>
|
||||
<div data-slot="session-review-v2-sidebar-filter">
|
||||
<TextInputV2
|
||||
type="search"
|
||||
value={props.filter}
|
||||
onInput={(event) => props.onFilterChange(event.currentTarget.value)}
|
||||
onKeyDown={props.onFilterKeyDown}
|
||||
autofocus={props.filterAutofocus}
|
||||
ref={props.filterRef}
|
||||
role={props.filterControls ? "combobox" : undefined}
|
||||
aria-autocomplete={props.filterControls ? "list" : undefined}
|
||||
aria-controls={props.filterControls}
|
||||
aria-activedescendant={props.filterActiveDescendant}
|
||||
aria-expanded={props.filterControls ? props.filterExpanded : undefined}
|
||||
showClearButton={props.filter.length > 0}
|
||||
clearLabel={i18n.t("ui.list.clearFilter")}
|
||||
onClearClick={() => props.onFilterChange("")}
|
||||
placeholder={i18n.t("ui.sessionReviewV2.filterFiles")}
|
||||
aria-label={i18n.t("ui.sessionReviewV2.filterFiles")}
|
||||
leadingIcon={
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M12.25 12.25L10.0625 10.0625M11.0833 6.41667C11.0833 8.994 8.994 11.0833 6.41667 11.0833C3.83934 11.0833 1.75 8.994 1.75 6.41667C1.75 3.83934 3.83934 1.75 6.41667 1.75C8.994 1.75 11.0833 3.83934 11.0833 6.41667Z"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="square"
|
||||
/>
|
||||
</svg>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<ScrollView
|
||||
data-slot="session-review-v2-sidebar-tree"
|
||||
class="group/file-tree-v2"
|
||||
thumbVisibility="scroll"
|
||||
viewportRef={props.viewportRef}
|
||||
>
|
||||
{props.children}
|
||||
</ScrollView>
|
||||
</aside>
|
||||
</Show>
|
||||
<Show when={props.open && props.onWidthChange}>
|
||||
<div data-slot="session-review-v2-sidebar-resize" onPointerDown={() => setResizing(true)}>
|
||||
<ResizeHandle
|
||||
@@ -185,130 +197,125 @@ export function SessionReviewV2(props: SessionReviewV2Props) {
|
||||
cycle(event.key === "<" ? prev() : next())
|
||||
})
|
||||
|
||||
return (
|
||||
<div data-component="session-review-v2">
|
||||
<div data-slot="session-review-v2-body">
|
||||
{props.sidebar}
|
||||
|
||||
<div data-slot="session-review-v2-preview">
|
||||
<Show when={props.hasDiffs} fallback={props.empty}>
|
||||
<div data-slot="session-review-v2-toolbar">
|
||||
<div data-slot="session-review-v2-toolbar-group" class="session-review-v2-toolbar-group--start">
|
||||
{props.sidebarToggle}
|
||||
<Show when={showCollapsedMeta()}>
|
||||
<div data-slot="session-review-v2-toolbar-collapsed-meta">
|
||||
<Show when={title()}>
|
||||
<div data-slot="session-review-v2-toolbar-title">{title()}</div>
|
||||
</Show>
|
||||
{stats()}
|
||||
<Show when={canCycle()}>
|
||||
<span data-slot="session-review-v2-file-position">
|
||||
{fileIndex() + 1}/{props.files.length}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
<div data-slot="session-review-v2-toolbar-group">
|
||||
<TooltipV2
|
||||
openDelay={2000}
|
||||
value={
|
||||
<>
|
||||
{i18n.t("ui.sessionReviewV2.previousFile")}
|
||||
<KeybindV2 keys={["<"]} variant="neutral" />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
icon="arrow-left"
|
||||
variant="ghost"
|
||||
size="small"
|
||||
class="session-review-v2-file-nav-button"
|
||||
disabled={!canCycle()}
|
||||
onClick={() => {
|
||||
const file = prev()
|
||||
if (!file) return
|
||||
props.onSelectFile(file)
|
||||
}}
|
||||
aria-label={i18n.t("ui.sessionReviewV2.previousFile")}
|
||||
/>
|
||||
</TooltipV2>
|
||||
<TooltipV2
|
||||
openDelay={2000}
|
||||
value={
|
||||
<>
|
||||
{i18n.t("ui.sessionReviewV2.nextFile")}
|
||||
<KeybindV2 keys={[">"]} variant="neutral" />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
icon="arrow-right"
|
||||
variant="ghost"
|
||||
size="small"
|
||||
class="session-review-v2-file-nav-button"
|
||||
disabled={!canCycle()}
|
||||
onClick={() => {
|
||||
const file = next()
|
||||
if (!file) return
|
||||
props.onSelectFile(file)
|
||||
}}
|
||||
aria-label={i18n.t("ui.sessionReviewV2.nextFile")}
|
||||
/>
|
||||
</TooltipV2>
|
||||
</div>
|
||||
</div>
|
||||
<div data-slot="session-review-v2-toolbar-group" class="session-review-v2-toolbar-group--segments">
|
||||
<SegmentedControlV2
|
||||
value={props.expandMode}
|
||||
onChange={(value) => {
|
||||
if (value !== "expand" && value !== "collapse") return
|
||||
props.onExpandModeChange(value)
|
||||
}}
|
||||
class="session-review-v2-segmented-control session-review-v2-segmented-control--icon"
|
||||
aria-label={i18n.t("ui.sessionReviewV2.expandMode")}
|
||||
>
|
||||
<TooltipV2 openDelay={2000} value={i18n.t("ui.sessionReviewV2.showAllLines")}>
|
||||
<SegmentedControlItemV2 value="expand" aria-label={i18n.t("ui.sessionReviewV2.showAllLines")}>
|
||||
<Icon name="expand" />
|
||||
</SegmentedControlItemV2>
|
||||
</TooltipV2>
|
||||
<TooltipV2 openDelay={2000} value={i18n.t("ui.sessionReviewV2.hideNonDiffLines")}>
|
||||
<SegmentedControlItemV2 value="collapse" aria-label={i18n.t("ui.sessionReviewV2.hideNonDiffLines")}>
|
||||
<Icon name="collapse" />
|
||||
</SegmentedControlItemV2>
|
||||
</TooltipV2>
|
||||
</SegmentedControlV2>
|
||||
<Show when={props.onDiffStyleChange}>
|
||||
<SegmentedControlV2
|
||||
value={props.diffStyle}
|
||||
onChange={(value) => {
|
||||
if (value !== "unified" && value !== "split") return
|
||||
props.onDiffStyleChange?.(value)
|
||||
}}
|
||||
class="session-review-v2-segmented-control session-review-v2-segmented-control--icon"
|
||||
aria-label={i18n.t("ui.sessionReviewV2.diffView")}
|
||||
>
|
||||
<TooltipV2 openDelay={2000} value={i18n.t("ui.sessionReviewV2.unifiedDiff")}>
|
||||
<SegmentedControlItemV2 value="unified" aria-label={i18n.t("ui.sessionReviewV2.unifiedDiff")}>
|
||||
<Icon name="unified" />
|
||||
</SegmentedControlItemV2>
|
||||
</TooltipV2>
|
||||
<TooltipV2 openDelay={2000} value={i18n.t("ui.sessionReviewV2.splitDiff")}>
|
||||
<SegmentedControlItemV2 value="split" aria-label={i18n.t("ui.sessionReviewV2.splitDiff")}>
|
||||
<Icon name="split" />
|
||||
</SegmentedControlItemV2>
|
||||
</TooltipV2>
|
||||
</SegmentedControlV2>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={props.activeFile} fallback={<div data-slot="session-review-v2-empty">{props.empty}</div>}>
|
||||
{props.preview}
|
||||
</Show>
|
||||
const toolbarStart = () => (
|
||||
<>
|
||||
{props.sidebarToggle}
|
||||
<Show when={showCollapsedMeta()}>
|
||||
<div data-slot="session-review-v2-toolbar-collapsed-meta">
|
||||
<Show when={title()}>
|
||||
<div data-slot="session-review-v2-toolbar-title">{title()}</div>
|
||||
</Show>
|
||||
{stats()}
|
||||
<Show when={canCycle()}>
|
||||
<span data-slot="session-review-v2-file-position">
|
||||
{fileIndex() + 1}/{props.files.length}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
<div class="flex items-center">
|
||||
<TooltipV2
|
||||
openDelay={2000}
|
||||
value={
|
||||
<>
|
||||
{i18n.t("ui.sessionReviewV2.previousFile")}
|
||||
<KeybindV2 keys={["<"]} variant="neutral" />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
icon="arrow-left"
|
||||
variant="ghost"
|
||||
size="small"
|
||||
class="session-review-v2-file-nav-button"
|
||||
disabled={!canCycle()}
|
||||
onClick={() => cycle(prev())}
|
||||
aria-label={i18n.t("ui.sessionReviewV2.previousFile")}
|
||||
/>
|
||||
</TooltipV2>
|
||||
<TooltipV2
|
||||
openDelay={2000}
|
||||
value={
|
||||
<>
|
||||
{i18n.t("ui.sessionReviewV2.nextFile")}
|
||||
<KeybindV2 keys={[">"]} variant="neutral" />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
icon="arrow-right"
|
||||
variant="ghost"
|
||||
size="small"
|
||||
class="session-review-v2-file-nav-button"
|
||||
disabled={!canCycle()}
|
||||
onClick={() => cycle(next())}
|
||||
aria-label={i18n.t("ui.sessionReviewV2.nextFile")}
|
||||
/>
|
||||
</TooltipV2>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
const toolbarEnd = () => (
|
||||
<>
|
||||
<SegmentedControlV2
|
||||
value={props.expandMode}
|
||||
onChange={(value) => {
|
||||
if (value !== "expand" && value !== "collapse") return
|
||||
props.onExpandModeChange(value)
|
||||
}}
|
||||
class="session-review-v2-segmented-control session-review-v2-segmented-control--icon"
|
||||
aria-label={i18n.t("ui.sessionReviewV2.expandMode")}
|
||||
>
|
||||
<TooltipV2 openDelay={2000} value={i18n.t("ui.sessionReviewV2.showAllLines")}>
|
||||
<SegmentedControlItemV2 value="expand" aria-label={i18n.t("ui.sessionReviewV2.showAllLines")}>
|
||||
<Icon name="expand" />
|
||||
</SegmentedControlItemV2>
|
||||
</TooltipV2>
|
||||
<TooltipV2 openDelay={2000} value={i18n.t("ui.sessionReviewV2.hideNonDiffLines")}>
|
||||
<SegmentedControlItemV2 value="collapse" aria-label={i18n.t("ui.sessionReviewV2.hideNonDiffLines")}>
|
||||
<Icon name="collapse" />
|
||||
</SegmentedControlItemV2>
|
||||
</TooltipV2>
|
||||
</SegmentedControlV2>
|
||||
<Show when={props.onDiffStyleChange}>
|
||||
<SegmentedControlV2
|
||||
value={props.diffStyle}
|
||||
onChange={(value) => {
|
||||
if (value !== "unified" && value !== "split") return
|
||||
props.onDiffStyleChange?.(value)
|
||||
}}
|
||||
class="session-review-v2-segmented-control session-review-v2-segmented-control--icon"
|
||||
aria-label={i18n.t("ui.sessionReviewV2.diffView")}
|
||||
>
|
||||
<TooltipV2 openDelay={2000} value={i18n.t("ui.sessionReviewV2.unifiedDiff")}>
|
||||
<SegmentedControlItemV2 value="unified" aria-label={i18n.t("ui.sessionReviewV2.unifiedDiff")}>
|
||||
<Icon name="unified" />
|
||||
</SegmentedControlItemV2>
|
||||
</TooltipV2>
|
||||
<TooltipV2 openDelay={2000} value={i18n.t("ui.sessionReviewV2.splitDiff")}>
|
||||
<SegmentedControlItemV2 value="split" aria-label={i18n.t("ui.sessionReviewV2.splitDiff")}>
|
||||
<Icon name="split" />
|
||||
</SegmentedControlItemV2>
|
||||
</TooltipV2>
|
||||
</SegmentedControlV2>
|
||||
</Show>
|
||||
</>
|
||||
)
|
||||
|
||||
return (
|
||||
<SessionFilePanelV2
|
||||
sidebar={props.sidebar}
|
||||
toolbar={props.hasDiffs}
|
||||
toolbarStart={toolbarStart()}
|
||||
toolbarEnd={toolbarEnd()}
|
||||
>
|
||||
<Show when={props.hasDiffs} fallback={props.empty}>
|
||||
<Show when={props.activeFile} fallback={<SessionFilePanelV2Empty>{props.empty}</SessionFilePanelV2Empty>}>
|
||||
{props.preview}
|
||||
</Show>
|
||||
</Show>
|
||||
</SessionFilePanelV2>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user