feat(app): session timeline/turn rework (#13196)
Co-authored-by: David Hill <iamdavidhill@gmail.com>
This commit is contained in:
@@ -28,7 +28,6 @@ test("smoke model selection updates prompt footer", async ({ page, gotoSession }
|
|||||||
const key = await target.getAttribute("data-key")
|
const key = await target.getAttribute("data-key")
|
||||||
if (!key) throw new Error("Failed to resolve model key from list item")
|
if (!key) throw new Error("Failed to resolve model key from list item")
|
||||||
|
|
||||||
const name = (await target.locator("span").first().innerText()).trim()
|
|
||||||
const model = key.split(":").slice(1).join(":")
|
const model = key.split(":").slice(1).join(":")
|
||||||
|
|
||||||
await input.fill(model)
|
await input.fill(model)
|
||||||
@@ -37,6 +36,13 @@ test("smoke model selection updates prompt footer", async ({ page, gotoSession }
|
|||||||
|
|
||||||
await expect(dialog).toHaveCount(0)
|
await expect(dialog).toHaveCount(0)
|
||||||
|
|
||||||
const form = page.locator(promptSelector).locator("xpath=ancestor::form[1]")
|
await page.locator(promptSelector).click()
|
||||||
await expect(form.locator('[data-component="button"]').filter({ hasText: name }).first()).toBeVisible()
|
await page.keyboard.type("/model")
|
||||||
|
await expect(command).toBeVisible()
|
||||||
|
await command.hover()
|
||||||
|
await page.keyboard.press("Enter")
|
||||||
|
|
||||||
|
const dialogAgain = page.getByRole("dialog")
|
||||||
|
await expect(dialogAgain).toBeVisible()
|
||||||
|
await expect(dialogAgain.locator(`[data-slot="list-item"][data-key="${key}"][data-selected="true"]`)).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export function ModelSelectorPopover(props: {
|
|||||||
}}
|
}}
|
||||||
modal={false}
|
modal={false}
|
||||||
placement="top-start"
|
placement="top-start"
|
||||||
gutter={8}
|
gutter={4}
|
||||||
>
|
>
|
||||||
<Kobalte.Trigger as={props.triggerAs ?? "div"} {...props.triggerProps}>
|
<Kobalte.Trigger as={props.triggerAs ?? "div"} {...props.triggerProps}>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid
|
|||||||
import { useProviders } from "@/hooks/use-providers"
|
import { useProviders } from "@/hooks/use-providers"
|
||||||
import { useCommand } from "@/context/command"
|
import { useCommand } from "@/context/command"
|
||||||
import { Persist, persisted } from "@/utils/persist"
|
import { Persist, persisted } from "@/utils/persist"
|
||||||
import { SessionContextUsage } from "@/components/session-context-usage"
|
|
||||||
import { usePermission } from "@/context/permission"
|
import { usePermission } from "@/context/permission"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
@@ -94,7 +93,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
const local = useLocal()
|
const local = useLocal()
|
||||||
const files = useFile()
|
const files = useFile()
|
||||||
const prompt = usePrompt()
|
const prompt = usePrompt()
|
||||||
const commentCount = createMemo(() => prompt.context.items().filter((item) => !!item.comment?.trim()).length)
|
|
||||||
const layout = useLayout()
|
const layout = useLayout()
|
||||||
const comments = useComments()
|
const comments = useComments()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
@@ -105,7 +103,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
let editorRef!: HTMLDivElement
|
let editorRef!: HTMLDivElement
|
||||||
let fileInputRef!: HTMLInputElement
|
let fileInputRef: HTMLInputElement | undefined
|
||||||
let scrollRef!: HTMLDivElement
|
let scrollRef!: HTMLDivElement
|
||||||
let slashPopoverRef!: HTMLDivElement
|
let slashPopoverRef!: HTMLDivElement
|
||||||
|
|
||||||
@@ -223,14 +221,25 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
mode: "normal",
|
mode: "normal",
|
||||||
applyingHistory: false,
|
applyingHistory: false,
|
||||||
})
|
})
|
||||||
const placeholder = createMemo(() =>
|
|
||||||
promptPlaceholder({
|
const commentCount = createMemo(() => {
|
||||||
mode: store.mode,
|
if (store.mode === "shell") return 0
|
||||||
commentCount: commentCount(),
|
return prompt.context.items().filter((item) => !!item.comment?.trim()).length
|
||||||
example: language.t(EXAMPLES[store.placeholder]),
|
})
|
||||||
t: (key, params) => language.t(key as Parameters<typeof language.t>[0], params as never),
|
|
||||||
}),
|
const contextItems = createMemo(() => {
|
||||||
)
|
const items = prompt.context.items()
|
||||||
|
if (store.mode !== "shell") return items
|
||||||
|
return items.filter((item) => !item.comment?.trim())
|
||||||
|
})
|
||||||
|
|
||||||
|
const hasUserPrompt = createMemo(() => {
|
||||||
|
const sessionID = params.id
|
||||||
|
if (!sessionID) return false
|
||||||
|
const messages = sync.data.message[sessionID]
|
||||||
|
if (!messages) return false
|
||||||
|
return messages.some((m) => m.role === "user")
|
||||||
|
})
|
||||||
|
|
||||||
const MAX_HISTORY = 100
|
const MAX_HISTORY = 100
|
||||||
const [history, setHistory] = persisted(
|
const [history, setHistory] = persisted(
|
||||||
@@ -250,6 +259,18 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const suggest = createMemo(() => !hasUserPrompt())
|
||||||
|
|
||||||
|
const placeholder = createMemo(() =>
|
||||||
|
promptPlaceholder({
|
||||||
|
mode: store.mode,
|
||||||
|
commentCount: commentCount(),
|
||||||
|
example: suggest() ? language.t(EXAMPLES[store.placeholder]) : "",
|
||||||
|
suggest: suggest(),
|
||||||
|
t: (key, params) => language.t(key as Parameters<typeof language.t>[0], params as never),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
const applyHistoryPrompt = (p: Prompt, position: "start" | "end") => {
|
const applyHistoryPrompt = (p: Prompt, position: "start" | "end") => {
|
||||||
const length = position === "start" ? 0 : promptLength(p)
|
const length = position === "start" ? 0 : promptLength(p)
|
||||||
setStore("applyingHistory", true)
|
setStore("applyingHistory", true)
|
||||||
@@ -282,6 +303,25 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
const isFocused = createFocusSignal(() => editorRef)
|
const isFocused = createFocusSignal(() => editorRef)
|
||||||
const escBlur = () => platform.platform === "desktop" && platform.os === "macos"
|
const escBlur = () => platform.platform === "desktop" && platform.os === "macos"
|
||||||
|
|
||||||
|
const pick = () => fileInputRef?.click()
|
||||||
|
|
||||||
|
const setMode = (mode: "normal" | "shell") => {
|
||||||
|
setStore("mode", mode)
|
||||||
|
setStore("popover", null)
|
||||||
|
requestAnimationFrame(() => editorRef?.focus())
|
||||||
|
}
|
||||||
|
|
||||||
|
command.register("prompt-input", () => [
|
||||||
|
{
|
||||||
|
id: "file.attach",
|
||||||
|
title: language.t("prompt.action.attachFile"),
|
||||||
|
category: language.t("command.category.file"),
|
||||||
|
keybind: "mod+u",
|
||||||
|
disabled: store.mode !== "normal",
|
||||||
|
onSelect: pick,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
const closePopover = () => setStore("popover", null)
|
const closePopover = () => setStore("popover", null)
|
||||||
|
|
||||||
const resetHistoryNavigation = (force = false) => {
|
const resetHistoryNavigation = (force = false) => {
|
||||||
@@ -326,6 +366,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
params.id
|
params.id
|
||||||
if (params.id) return
|
if (params.id) return
|
||||||
|
if (!suggest()) return
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
setStore("placeholder", (prev) => (prev + 1) % EXAMPLES.length)
|
setStore("placeholder", (prev) => (prev + 1) % EXAMPLES.length)
|
||||||
}, 6500)
|
}, 6500)
|
||||||
@@ -816,6 +857,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if ((event.metaKey || event.ctrlKey) && !event.altKey && !event.shiftKey && event.key.toLowerCase() === "u") {
|
||||||
|
event.preventDefault()
|
||||||
|
if (store.mode !== "normal") return
|
||||||
|
pick()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (event.key === "Backspace") {
|
if (event.key === "Backspace") {
|
||||||
const selection = window.getSelection()
|
const selection = window.getSelection()
|
||||||
if (selection && selection.isCollapsed) {
|
if (selection && selection.isCollapsed) {
|
||||||
@@ -956,8 +1004,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const variants = createMemo(() => ["default", ...local.model.variant.list()])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="relative size-full _max-h-[320px] flex flex-col gap-3">
|
<div class="relative size-full _max-h-[320px] flex flex-col gap-0">
|
||||||
<PromptPopover
|
<PromptPopover
|
||||||
popover={store.popover}
|
popover={store.popover}
|
||||||
setSlashPopoverRef={(el) => (slashPopoverRef = el)}
|
setSlashPopoverRef={(el) => (slashPopoverRef = el)}
|
||||||
@@ -977,8 +1027,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
classList={{
|
classList={{
|
||||||
"group/prompt-input": true,
|
"group/prompt-input": true,
|
||||||
"bg-surface-raised-stronger-non-alpha shadow-xs-border relative": true,
|
"bg-surface-raised-stronger-non-alpha shadow-xs-border relative z-10": true,
|
||||||
"rounded-[14px] overflow-clip focus-within:shadow-xs-border": true,
|
"rounded-[12px] overflow-clip focus-within:shadow-xs-border": true,
|
||||||
"border-icon-info-active border-dashed": store.draggingType !== null,
|
"border-icon-info-active border-dashed": store.draggingType !== null,
|
||||||
[props.class ?? ""]: !!props.class,
|
[props.class ?? ""]: !!props.class,
|
||||||
}}
|
}}
|
||||||
@@ -988,7 +1038,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
label={language.t(store.draggingType === "@mention" ? "prompt.dropzone.file.label" : "prompt.dropzone.label")}
|
label={language.t(store.draggingType === "@mention" ? "prompt.dropzone.file.label" : "prompt.dropzone.label")}
|
||||||
/>
|
/>
|
||||||
<PromptContextItems
|
<PromptContextItems
|
||||||
items={prompt.context.items()}
|
items={contextItems()}
|
||||||
active={(item) => {
|
active={(item) => {
|
||||||
const active = comments.active()
|
const active = comments.active()
|
||||||
return !!item.commentID && item.commentID === active?.id && item.path === active?.file
|
return !!item.commentID && item.commentID === active?.id && item.path === active?.file
|
||||||
@@ -1008,7 +1058,22 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
onRemove={removeImageAttachment}
|
onRemove={removeImageAttachment}
|
||||||
removeLabel={language.t("prompt.attachment.remove")}
|
removeLabel={language.t("prompt.attachment.remove")}
|
||||||
/>
|
/>
|
||||||
<div class="relative max-h-[240px] overflow-y-auto" ref={(el) => (scrollRef = el)}>
|
<div
|
||||||
|
class="relative max-h-[240px] overflow-y-auto"
|
||||||
|
ref={(el) => (scrollRef = el)}
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
const target = e.target
|
||||||
|
if (!(target instanceof HTMLElement)) return
|
||||||
|
if (
|
||||||
|
target.closest(
|
||||||
|
'[data-action="prompt-attach"], [data-action="prompt-submit"], [data-action="prompt-permissions"]',
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
editorRef?.focus()
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
data-component="prompt-input"
|
data-component="prompt-input"
|
||||||
ref={(el) => {
|
ref={(el) => {
|
||||||
@@ -1029,141 +1094,22 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
classList={{
|
classList={{
|
||||||
"select-text": true,
|
"select-text": true,
|
||||||
"w-full p-3 pr-12 text-14-regular text-text-strong focus:outline-none whitespace-pre-wrap": true,
|
"w-full pl-3 pr-2 pt-2 pb-12 text-14-regular text-text-strong focus:outline-none whitespace-pre-wrap": true,
|
||||||
"[&_[data-type=file]]:text-syntax-property": true,
|
"[&_[data-type=file]]:text-syntax-property": true,
|
||||||
"[&_[data-type=agent]]:text-syntax-type": true,
|
"[&_[data-type=agent]]:text-syntax-type": true,
|
||||||
"font-mono!": store.mode === "shell",
|
"font-mono!": store.mode === "shell",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Show when={!prompt.dirty()}>
|
<Show when={!prompt.dirty()}>
|
||||||
<div class="absolute top-0 inset-x-0 p-3 pr-12 text-14-regular text-text-weak pointer-events-none whitespace-nowrap truncate">
|
<div
|
||||||
|
class="absolute top-0 inset-x-0 pl-3 pr-2 pt-2 pb-12 text-14-regular text-text-weak pointer-events-none whitespace-nowrap truncate"
|
||||||
|
classList={{ "font-mono!": store.mode === "shell" }}
|
||||||
|
>
|
||||||
{placeholder()}
|
{placeholder()}
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
|
||||||
<div class="relative p-3 flex items-center justify-between gap-2">
|
<div class="pointer-events-none absolute bottom-2 right-2 flex items-center gap-2">
|
||||||
<div class="flex items-center gap-2 min-w-0 flex-1">
|
|
||||||
<Switch>
|
|
||||||
<Match when={store.mode === "shell"}>
|
|
||||||
<div class="flex items-center gap-2 px-2 h-6">
|
|
||||||
<Icon name="console" size="small" class="text-icon-primary" />
|
|
||||||
<span class="text-12-regular text-text-primary">{language.t("prompt.mode.shell")}</span>
|
|
||||||
<span class="text-12-regular text-text-weak">{language.t("prompt.mode.shell.exit")}</span>
|
|
||||||
</div>
|
|
||||||
</Match>
|
|
||||||
<Match when={store.mode === "normal"}>
|
|
||||||
<TooltipKeybind
|
|
||||||
placement="top"
|
|
||||||
gutter={8}
|
|
||||||
title={language.t("command.agent.cycle")}
|
|
||||||
keybind={command.keybind("agent.cycle")}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
options={agentNames()}
|
|
||||||
current={local.agent.current()?.name ?? ""}
|
|
||||||
onSelect={local.agent.set}
|
|
||||||
class={`capitalize ${local.model.variant.list().length > 0 ? "max-w-full" : "max-w-[120px]"}`}
|
|
||||||
valueClass="truncate"
|
|
||||||
variant="ghost"
|
|
||||||
/>
|
|
||||||
</TooltipKeybind>
|
|
||||||
<Show
|
|
||||||
when={providers.paid().length > 0}
|
|
||||||
fallback={
|
|
||||||
<TooltipKeybind
|
|
||||||
placement="top"
|
|
||||||
gutter={8}
|
|
||||||
title={language.t("command.model.choose")}
|
|
||||||
keybind={command.keybind("model.choose")}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
as="div"
|
|
||||||
variant="ghost"
|
|
||||||
class="px-2 min-w-0 max-w-[240px]"
|
|
||||||
onClick={() => dialog.show(() => <DialogSelectModelUnpaid />)}
|
|
||||||
>
|
|
||||||
<Show when={local.model.current()?.provider?.id}>
|
|
||||||
<ProviderIcon id={local.model.current()!.provider.id as IconName} class="size-4 shrink-0" />
|
|
||||||
</Show>
|
|
||||||
<span class="truncate">
|
|
||||||
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
|
|
||||||
</span>
|
|
||||||
<Icon name="chevron-down" size="small" class="shrink-0" />
|
|
||||||
</Button>
|
|
||||||
</TooltipKeybind>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<TooltipKeybind
|
|
||||||
placement="top"
|
|
||||||
gutter={8}
|
|
||||||
title={language.t("command.model.choose")}
|
|
||||||
keybind={command.keybind("model.choose")}
|
|
||||||
>
|
|
||||||
<ModelSelectorPopover
|
|
||||||
triggerAs={Button}
|
|
||||||
triggerProps={{ variant: "ghost", class: "min-w-0 max-w-[240px]" }}
|
|
||||||
>
|
|
||||||
<Show when={local.model.current()?.provider?.id}>
|
|
||||||
<ProviderIcon id={local.model.current()!.provider.id as IconName} class="size-4 shrink-0" />
|
|
||||||
</Show>
|
|
||||||
<span class="truncate">
|
|
||||||
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
|
|
||||||
</span>
|
|
||||||
<Icon name="chevron-down" size="small" class="shrink-0" />
|
|
||||||
</ModelSelectorPopover>
|
|
||||||
</TooltipKeybind>
|
|
||||||
</Show>
|
|
||||||
<Show when={local.model.variant.list().length > 0}>
|
|
||||||
<TooltipKeybind
|
|
||||||
placement="top"
|
|
||||||
gutter={8}
|
|
||||||
title={language.t("command.model.variant.cycle")}
|
|
||||||
keybind={command.keybind("model.variant.cycle")}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
data-action="model-variant-cycle"
|
|
||||||
variant="ghost"
|
|
||||||
class="text-text-base _hidden group-hover/prompt-input:inline-block capitalize text-12-regular"
|
|
||||||
onClick={() => local.model.variant.cycle()}
|
|
||||||
>
|
|
||||||
{local.model.variant.current() ?? language.t("common.default")}
|
|
||||||
</Button>
|
|
||||||
</TooltipKeybind>
|
|
||||||
</Show>
|
|
||||||
<Show when={permission.permissionsEnabled() && params.id}>
|
|
||||||
<TooltipKeybind
|
|
||||||
placement="top"
|
|
||||||
gutter={8}
|
|
||||||
title={language.t("command.permissions.autoaccept.enable")}
|
|
||||||
keybind={command.keybind("permissions.autoaccept")}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
onClick={() => permission.toggleAutoAccept(params.id!, sdk.directory)}
|
|
||||||
classList={{
|
|
||||||
"_hidden group-hover/prompt-input:flex size-6 items-center justify-center": true,
|
|
||||||
"text-text-base": !permission.isAutoAccepting(params.id!, sdk.directory),
|
|
||||||
"hover:bg-surface-success-base": permission.isAutoAccepting(params.id!, sdk.directory),
|
|
||||||
}}
|
|
||||||
aria-label={
|
|
||||||
permission.isAutoAccepting(params.id!, sdk.directory)
|
|
||||||
? language.t("command.permissions.autoaccept.disable")
|
|
||||||
: language.t("command.permissions.autoaccept.enable")
|
|
||||||
}
|
|
||||||
aria-pressed={permission.isAutoAccepting(params.id!, sdk.directory)}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
name="chevron-double-right"
|
|
||||||
size="small"
|
|
||||||
classList={{ "text-icon-success-base": permission.isAutoAccepting(params.id!, sdk.directory) }}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</TooltipKeybind>
|
|
||||||
</Show>
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-1 shrink-0">
|
|
||||||
<input
|
<input
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
type="file"
|
type="file"
|
||||||
@@ -1175,54 +1121,262 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
e.currentTarget.value = ""
|
e.currentTarget.value = ""
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div class="flex items-center gap-1 mr-1">
|
|
||||||
<SessionContextUsage />
|
<div
|
||||||
<Show when={store.mode === "normal"}>
|
aria-hidden={store.mode !== "normal"}
|
||||||
<Tooltip placement="top" value={language.t("prompt.action.attachFile")}>
|
class="flex items-center gap-1 transition-all duration-200 ease-out"
|
||||||
<Button
|
classList={{
|
||||||
type="button"
|
"opacity-100 translate-y-0 scale-100 pointer-events-auto": store.mode === "normal",
|
||||||
variant="ghost"
|
"opacity-0 translate-y-2 scale-95 pointer-events-none": store.mode !== "normal",
|
||||||
class="size-6 px-1"
|
}}
|
||||||
onClick={() => fileInputRef.click()}
|
|
||||||
aria-label={language.t("prompt.action.attachFile")}
|
|
||||||
>
|
|
||||||
<Icon name="photo" class="size-4.5" />
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
<Tooltip
|
|
||||||
placement="top"
|
|
||||||
inactive={!prompt.dirty() && !working()}
|
|
||||||
value={
|
|
||||||
<Switch>
|
|
||||||
<Match when={working()}>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<span>{language.t("prompt.action.stop")}</span>
|
|
||||||
<span class="text-icon-base text-12-medium text-[10px]!">{language.t("common.key.esc")}</span>
|
|
||||||
</div>
|
|
||||||
</Match>
|
|
||||||
<Match when={true}>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<span>{language.t("prompt.action.send")}</span>
|
|
||||||
<Icon name="enter" size="small" class="text-icon-base" />
|
|
||||||
</div>
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<IconButton
|
<TooltipKeybind
|
||||||
type="submit"
|
placement="top"
|
||||||
disabled={!prompt.dirty() && !working() && commentCount() === 0}
|
title={language.t("prompt.action.attachFile")}
|
||||||
icon={working() ? "stop" : "arrow-up"}
|
keybind={command.keybind("file.attach")}
|
||||||
variant="primary"
|
>
|
||||||
class="h-6 w-4.5"
|
<Button
|
||||||
aria-label={working() ? language.t("prompt.action.stop") : language.t("prompt.action.send")}
|
data-action="prompt-attach"
|
||||||
/>
|
type="button"
|
||||||
</Tooltip>
|
variant="ghost"
|
||||||
|
class="size-8 p-0"
|
||||||
|
onClick={pick}
|
||||||
|
disabled={store.mode !== "normal"}
|
||||||
|
tabIndex={store.mode === "normal" ? undefined : -1}
|
||||||
|
aria-label={language.t("prompt.action.attachFile")}
|
||||||
|
>
|
||||||
|
<Icon name="plus" class="size-4.5" />
|
||||||
|
</Button>
|
||||||
|
</TooltipKeybind>
|
||||||
|
|
||||||
|
<Tooltip
|
||||||
|
placement="top"
|
||||||
|
inactive={!prompt.dirty() && !working()}
|
||||||
|
value={
|
||||||
|
<Switch>
|
||||||
|
<Match when={working()}>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span>{language.t("prompt.action.stop")}</span>
|
||||||
|
<span class="text-icon-base text-12-medium text-[10px]!">{language.t("common.key.esc")}</span>
|
||||||
|
</div>
|
||||||
|
</Match>
|
||||||
|
<Match when={true}>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span>{language.t("prompt.action.send")}</span>
|
||||||
|
<Icon name="enter" size="small" class="text-icon-base" />
|
||||||
|
</div>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
data-action="prompt-submit"
|
||||||
|
type="submit"
|
||||||
|
disabled={store.mode !== "normal" || (!prompt.dirty() && !working() && commentCount() === 0)}
|
||||||
|
tabIndex={store.mode === "normal" ? undefined : -1}
|
||||||
|
icon={working() ? "stop" : "arrow-up"}
|
||||||
|
variant="primary"
|
||||||
|
class="size-8"
|
||||||
|
aria-label={working() ? language.t("prompt.action.stop") : language.t("prompt.action.send")}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Show when={store.mode === "normal" && permission.permissionsEnabled() && params.id}>
|
||||||
|
<div class="pointer-events-none absolute bottom-2 left-2">
|
||||||
|
<div class="pointer-events-auto">
|
||||||
|
<TooltipKeybind
|
||||||
|
placement="top"
|
||||||
|
gutter={8}
|
||||||
|
title={language.t("command.permissions.autoaccept.enable")}
|
||||||
|
keybind={command.keybind("permissions.autoaccept")}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
data-action="prompt-permissions"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => permission.toggleAutoAccept(params.id!, sdk.directory)}
|
||||||
|
classList={{
|
||||||
|
"_hidden group-hover/prompt-input:flex size-6 items-center justify-center": true,
|
||||||
|
"text-text-base": !permission.isAutoAccepting(params.id!, sdk.directory),
|
||||||
|
"hover:bg-surface-success-base": permission.isAutoAccepting(params.id!, sdk.directory),
|
||||||
|
}}
|
||||||
|
aria-label={
|
||||||
|
permission.isAutoAccepting(params.id!, sdk.directory)
|
||||||
|
? language.t("command.permissions.autoaccept.disable")
|
||||||
|
: language.t("command.permissions.autoaccept.enable")
|
||||||
|
}
|
||||||
|
aria-pressed={permission.isAutoAccepting(params.id!, sdk.directory)}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name="chevron-double-right"
|
||||||
|
size="small"
|
||||||
|
classList={{ "text-icon-success-base": permission.isAutoAccepting(params.id!, sdk.directory) }}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</TooltipKeybind>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<Show when={store.mode === "normal" || store.mode === "shell"}>
|
||||||
|
<div class="-mt-3.5 bg-background-base border border-border-weak-base relative z-0 rounded-[12px] rounded-tl-0 rounded-tr-0 overflow-clip">
|
||||||
|
<div class="px-2 pt-5.5 pb-2 flex items-center gap-2 min-w-0">
|
||||||
|
<div class="flex items-center gap-1.5 min-w-0 flex-1">
|
||||||
|
<Show when={store.mode === "shell"}>
|
||||||
|
<div class="h-7 flex items-center gap-1.5 max-w-[160px] min-w-0" style={{ padding: "0 4px 0 8px" }}>
|
||||||
|
<span class="truncate text-13-medium text-text-strong">{language.t("prompt.mode.shell")}</span>
|
||||||
|
<div class="size-4 shrink-0" />
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<Show when={store.mode === "normal"}>
|
||||||
|
<TooltipKeybind
|
||||||
|
placement="top"
|
||||||
|
gutter={4}
|
||||||
|
title={language.t("command.agent.cycle")}
|
||||||
|
keybind={command.keybind("agent.cycle")}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
size="normal"
|
||||||
|
options={agentNames()}
|
||||||
|
current={local.agent.current()?.name ?? ""}
|
||||||
|
onSelect={local.agent.set}
|
||||||
|
class="capitalize max-w-[160px]"
|
||||||
|
valueClass="truncate text-13-regular"
|
||||||
|
triggerStyle={{ height: "28px" }}
|
||||||
|
variant="ghost"
|
||||||
|
/>
|
||||||
|
</TooltipKeybind>
|
||||||
|
<Show
|
||||||
|
when={providers.paid().length > 0}
|
||||||
|
fallback={
|
||||||
|
<TooltipKeybind
|
||||||
|
placement="top"
|
||||||
|
gutter={4}
|
||||||
|
title={language.t("command.model.choose")}
|
||||||
|
keybind={command.keybind("model.choose")}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
as="div"
|
||||||
|
variant="ghost"
|
||||||
|
size="normal"
|
||||||
|
class="min-w-0 max-w-[320px] text-13-regular group"
|
||||||
|
style={{ height: "28px" }}
|
||||||
|
onClick={() => dialog.show(() => <DialogSelectModelUnpaid />)}
|
||||||
|
>
|
||||||
|
<Show when={local.model.current()?.provider?.id}>
|
||||||
|
<ProviderIcon
|
||||||
|
id={local.model.current()!.provider.id as IconName}
|
||||||
|
class="size-4 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity duration-150"
|
||||||
|
style={{ "will-change": "opacity", transform: "translateZ(0)" }}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
|
<span class="truncate">
|
||||||
|
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
|
||||||
|
</span>
|
||||||
|
<Icon name="chevron-down" size="small" class="shrink-0" />
|
||||||
|
</Button>
|
||||||
|
</TooltipKeybind>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TooltipKeybind
|
||||||
|
placement="top"
|
||||||
|
gutter={4}
|
||||||
|
title={language.t("command.model.choose")}
|
||||||
|
keybind={command.keybind("model.choose")}
|
||||||
|
>
|
||||||
|
<ModelSelectorPopover
|
||||||
|
triggerAs={Button}
|
||||||
|
triggerProps={{
|
||||||
|
variant: "ghost",
|
||||||
|
size: "normal",
|
||||||
|
style: { height: "28px" },
|
||||||
|
class: "min-w-0 max-w-[320px] text-13-regular group",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Show when={local.model.current()?.provider?.id}>
|
||||||
|
<ProviderIcon
|
||||||
|
id={local.model.current()!.provider.id as IconName}
|
||||||
|
class="size-4 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity duration-150"
|
||||||
|
style={{ "will-change": "opacity", transform: "translateZ(0)" }}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
|
<span class="truncate">
|
||||||
|
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
|
||||||
|
</span>
|
||||||
|
<Icon name="chevron-down" size="small" class="shrink-0" />
|
||||||
|
</ModelSelectorPopover>
|
||||||
|
</TooltipKeybind>
|
||||||
|
</Show>
|
||||||
|
<TooltipKeybind
|
||||||
|
placement="top"
|
||||||
|
gutter={4}
|
||||||
|
title={language.t("command.model.variant.cycle")}
|
||||||
|
keybind={command.keybind("model.variant.cycle")}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
size="normal"
|
||||||
|
options={variants()}
|
||||||
|
current={local.model.variant.current() ?? "default"}
|
||||||
|
label={(x) => (x === "default" ? language.t("common.default") : x)}
|
||||||
|
onSelect={(x) => local.model.variant.set(x === "default" ? undefined : x)}
|
||||||
|
class="capitalize max-w-[160px]"
|
||||||
|
valueClass="truncate text-13-regular"
|
||||||
|
triggerStyle={{ height: "28px" }}
|
||||||
|
variant="ghost"
|
||||||
|
/>
|
||||||
|
</TooltipKeybind>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="shrink-0">
|
||||||
|
<div
|
||||||
|
data-component="prompt-mode-toggle"
|
||||||
|
class="relative h-8 w-[68px] rounded-[4px] bg-surface-inset-base shadow-[var(--shadow-xs-border-base)] p-0 flex items-center gap-1 overflow-visible"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="absolute inset-y-0 left-0 w-[calc((100%-4px)/2)] rounded-[4px] bg-surface-raised-stronger-non-alpha shadow-[var(--shadow-xs-border)] transition-transform duration-200 ease-out will-change-transform"
|
||||||
|
style={{
|
||||||
|
transform: store.mode === "shell" ? "translateX(0px)" : "translateX(calc(100% + 4px))",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="relative z-10 flex-1 h-full flex items-center justify-center rounded-[4px]"
|
||||||
|
aria-pressed={store.mode === "shell"}
|
||||||
|
onClick={() => setMode("shell")}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name="console"
|
||||||
|
size="normal"
|
||||||
|
classList={{
|
||||||
|
"text-icon-strong-base": store.mode === "shell",
|
||||||
|
"text-icon-weak": store.mode !== "shell",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="relative z-10 flex-1 h-full flex items-center justify-center rounded-[4px]"
|
||||||
|
aria-pressed={store.mode === "normal"}
|
||||||
|
onClick={() => setMode("normal")}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name="prompt"
|
||||||
|
size="normal"
|
||||||
|
classList={{
|
||||||
|
"text-icon-interactive-base": store.mode === "normal",
|
||||||
|
"text-icon-weak": store.mode !== "normal",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,10 +41,9 @@ export const PromptContextItems: Component<ContextItemsProps> = (props) => {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
classList={{
|
classList={{
|
||||||
"group shrink-0 flex flex-col rounded-[6px] pl-2 pr-1 py-1 max-w-[200px] h-12 transition-all transition-transform shadow-xs-border hover:shadow-xs-border-hover": true,
|
"group shrink-0 flex flex-col rounded-[6px] pl-2 pr-1 py-1 max-w-[200px] h-12 cursor-default transition-all transition-transform shadow-xs-border hover:shadow-xs-border-hover": true,
|
||||||
"cursor-pointer hover:bg-surface-interactive-weak": !!item.commentID && !selected,
|
"hover:bg-surface-interactive-weak": !!item.commentID && !selected,
|
||||||
"cursor-pointer bg-surface-interactive-hover hover:bg-surface-interactive-hover shadow-xs-border-hover":
|
"bg-surface-interactive-hover hover:bg-surface-interactive-hover shadow-xs-border-hover": selected,
|
||||||
selected,
|
|
||||||
"bg-background-stronger": !selected,
|
"bg-background-stronger": !selected,
|
||||||
}}
|
}}
|
||||||
onClick={() => props.openComment(item)}
|
onClick={() => props.openComment(item)}
|
||||||
|
|||||||
@@ -9,27 +9,40 @@ describe("promptPlaceholder", () => {
|
|||||||
mode: "shell",
|
mode: "shell",
|
||||||
commentCount: 0,
|
commentCount: 0,
|
||||||
example: "example",
|
example: "example",
|
||||||
|
suggest: true,
|
||||||
t,
|
t,
|
||||||
})
|
})
|
||||||
expect(value).toBe("prompt.placeholder.shell")
|
expect(value).toBe("prompt.placeholder.shell")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("returns summarize placeholders for comment context", () => {
|
test("returns summarize placeholders for comment context", () => {
|
||||||
expect(promptPlaceholder({ mode: "normal", commentCount: 1, example: "example", t })).toBe(
|
expect(promptPlaceholder({ mode: "normal", commentCount: 1, example: "example", suggest: true, t })).toBe(
|
||||||
"prompt.placeholder.summarizeComment",
|
"prompt.placeholder.summarizeComment",
|
||||||
)
|
)
|
||||||
expect(promptPlaceholder({ mode: "normal", commentCount: 2, example: "example", t })).toBe(
|
expect(promptPlaceholder({ mode: "normal", commentCount: 2, example: "example", suggest: true, t })).toBe(
|
||||||
"prompt.placeholder.summarizeComments",
|
"prompt.placeholder.summarizeComments",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("returns default placeholder with example", () => {
|
test("returns default placeholder with example when suggestions enabled", () => {
|
||||||
const value = promptPlaceholder({
|
const value = promptPlaceholder({
|
||||||
mode: "normal",
|
mode: "normal",
|
||||||
commentCount: 0,
|
commentCount: 0,
|
||||||
example: "translated-example",
|
example: "translated-example",
|
||||||
|
suggest: true,
|
||||||
t,
|
t,
|
||||||
})
|
})
|
||||||
expect(value).toBe("prompt.placeholder.normal:translated-example")
|
expect(value).toBe("prompt.placeholder.normal:translated-example")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("returns simple placeholder when suggestions disabled", () => {
|
||||||
|
const value = promptPlaceholder({
|
||||||
|
mode: "normal",
|
||||||
|
commentCount: 0,
|
||||||
|
example: "translated-example",
|
||||||
|
suggest: false,
|
||||||
|
t,
|
||||||
|
})
|
||||||
|
expect(value).toBe("prompt.placeholder.simple")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ type PromptPlaceholderInput = {
|
|||||||
mode: "normal" | "shell"
|
mode: "normal" | "shell"
|
||||||
commentCount: number
|
commentCount: number
|
||||||
example: string
|
example: string
|
||||||
|
suggest: boolean
|
||||||
t: (key: string, params?: Record<string, string>) => string
|
t: (key: string, params?: Record<string, string>) => string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9,5 +10,6 @@ export function promptPlaceholder(input: PromptPlaceholderInput) {
|
|||||||
if (input.mode === "shell") return input.t("prompt.placeholder.shell")
|
if (input.mode === "shell") return input.t("prompt.placeholder.shell")
|
||||||
if (input.commentCount > 1) return input.t("prompt.placeholder.summarizeComments")
|
if (input.commentCount > 1) return input.t("prompt.placeholder.summarizeComments")
|
||||||
if (input.commentCount === 1) return input.t("prompt.placeholder.summarizeComment")
|
if (input.commentCount === 1) return input.t("prompt.placeholder.summarizeComment")
|
||||||
|
if (!input.suggest) return input.t("prompt.placeholder.simple")
|
||||||
return input.t("prompt.placeholder.normal", { example: input.example })
|
return input.t("prompt.placeholder.normal", { example: input.example })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ export const PromptPopover: Component<PromptPopoverProps> = (props) => {
|
|||||||
ref={(el) => {
|
ref={(el) => {
|
||||||
if (props.popover === "slash") props.setSlashPopoverRef(el)
|
if (props.popover === "slash") props.setSlashPopoverRef(el)
|
||||||
}}
|
}}
|
||||||
class="absolute inset-x-0 -top-3 -translate-y-full origin-bottom-left max-h-80 min-h-10
|
class="absolute inset-x-0 -top-2 -translate-y-full origin-bottom-left max-h-80 min-h-10
|
||||||
overflow-auto no-scrollbar flex flex-col p-2 rounded-md
|
overflow-auto no-scrollbar flex flex-col p-2 rounded-[12px]
|
||||||
border border-border-base bg-surface-raised-stronger-non-alpha shadow-md"
|
bg-surface-raised-stronger-non-alpha shadow-[var(--shadow-lg-border-base)]"
|
||||||
onMouseDown={(e) => e.preventDefault()}
|
onMouseDown={(e) => e.preventDefault()}
|
||||||
>
|
>
|
||||||
<Switch>
|
<Switch>
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
queued.abort.abort()
|
queued.abort.abort()
|
||||||
queued.cleanup()
|
queued.cleanup()
|
||||||
pending.delete(sessionID)
|
pending.delete(sessionID)
|
||||||
|
globalSync.todo.set(sessionID, undefined)
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
return sdk.client.session
|
return sdk.client.session
|
||||||
@@ -87,6 +88,9 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
sessionID,
|
sessionID,
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
|
.finally(() => {
|
||||||
|
globalSync.todo.set(sessionID, undefined)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const restoreCommentItems = (items: CommentItem[]) => {
|
const restoreCommentItems = (items: CommentItem[]) => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { For, Show, createMemo, type Component } from "solid-js"
|
import { For, Show, createMemo, onCleanup, onMount, type Component } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
@@ -12,25 +12,98 @@ export const QuestionDock: Component<{ request: QuestionRequest }> = (props) =>
|
|||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
|
||||||
const questions = createMemo(() => props.request.questions)
|
const questions = createMemo(() => props.request.questions)
|
||||||
const single = createMemo(() => questions().length === 1 && questions()[0]?.multiple !== true)
|
const total = createMemo(() => questions().length)
|
||||||
|
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
tab: 0,
|
tab: 0,
|
||||||
answers: [] as QuestionAnswer[],
|
answers: [] as QuestionAnswer[],
|
||||||
custom: [] as string[],
|
custom: [] as string[],
|
||||||
|
customOn: [] as boolean[],
|
||||||
editing: false,
|
editing: false,
|
||||||
sending: false,
|
sending: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let root: HTMLDivElement | undefined
|
||||||
|
|
||||||
const question = createMemo(() => questions()[store.tab])
|
const question = createMemo(() => questions()[store.tab])
|
||||||
const confirm = createMemo(() => !single() && store.tab === questions().length)
|
|
||||||
const options = createMemo(() => question()?.options ?? [])
|
const options = createMemo(() => question()?.options ?? [])
|
||||||
const input = createMemo(() => store.custom[store.tab] ?? "")
|
const input = createMemo(() => store.custom[store.tab] ?? "")
|
||||||
|
const on = createMemo(() => store.customOn[store.tab] === true)
|
||||||
const multi = createMemo(() => question()?.multiple === true)
|
const multi = createMemo(() => question()?.multiple === true)
|
||||||
const customPicked = createMemo(() => {
|
|
||||||
const value = input()
|
const summary = createMemo(() => {
|
||||||
if (!value) return false
|
const n = Math.min(store.tab + 1, total())
|
||||||
return store.answers[store.tab]?.includes(value) ?? false
|
return `${n} of ${total()} questions`
|
||||||
|
})
|
||||||
|
|
||||||
|
const last = createMemo(() => store.tab >= total() - 1)
|
||||||
|
|
||||||
|
const customUpdate = (value: string, selected: boolean = on()) => {
|
||||||
|
const prev = input().trim()
|
||||||
|
const next = value.trim()
|
||||||
|
|
||||||
|
setStore("custom", store.tab, value)
|
||||||
|
if (!selected) return
|
||||||
|
|
||||||
|
if (multi()) {
|
||||||
|
setStore("answers", store.tab, (current = []) => {
|
||||||
|
const removed = prev ? current.filter((item) => item.trim() !== prev) : current
|
||||||
|
if (!next) return removed
|
||||||
|
if (removed.some((item) => item.trim() === next)) return removed
|
||||||
|
return [...removed, next]
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setStore("answers", store.tab, next ? [next] : [])
|
||||||
|
}
|
||||||
|
|
||||||
|
const measure = () => {
|
||||||
|
if (!root) return
|
||||||
|
|
||||||
|
const scroller = document.querySelector(".session-scroller")
|
||||||
|
const head = scroller instanceof HTMLElement ? scroller.firstElementChild : undefined
|
||||||
|
const top =
|
||||||
|
head instanceof HTMLElement && head.classList.contains("sticky") ? head.getBoundingClientRect().bottom : 0
|
||||||
|
if (!top) {
|
||||||
|
root.style.removeProperty("--question-prompt-max-height")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const dock = root.closest('[data-component="session-prompt-dock"]')
|
||||||
|
if (!(dock instanceof HTMLElement)) return
|
||||||
|
|
||||||
|
const dockBottom = dock.getBoundingClientRect().bottom
|
||||||
|
const below = Math.max(0, dockBottom - root.getBoundingClientRect().bottom)
|
||||||
|
const gap = 8
|
||||||
|
const max = Math.max(240, Math.floor(dockBottom - top - gap - below))
|
||||||
|
root.style.setProperty("--question-prompt-max-height", `${max}px`)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
let raf: number | undefined
|
||||||
|
const update = () => {
|
||||||
|
if (raf !== undefined) cancelAnimationFrame(raf)
|
||||||
|
raf = requestAnimationFrame(() => {
|
||||||
|
raf = undefined
|
||||||
|
measure()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
update()
|
||||||
|
window.addEventListener("resize", update)
|
||||||
|
|
||||||
|
const dock = root?.closest('[data-component="session-prompt-dock"]')
|
||||||
|
const scroller = document.querySelector(".session-scroller")
|
||||||
|
const observer = new ResizeObserver(update)
|
||||||
|
if (dock instanceof HTMLElement) observer.observe(dock)
|
||||||
|
if (scroller instanceof HTMLElement) observer.observe(scroller)
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
window.removeEventListener("resize", update)
|
||||||
|
observer.disconnect()
|
||||||
|
if (raf !== undefined) cancelAnimationFrame(raf)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const fail = (err: unknown) => {
|
const fail = (err: unknown) => {
|
||||||
@@ -64,23 +137,13 @@ export const QuestionDock: Component<{ request: QuestionRequest }> = (props) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => void reply(questions().map((_, i) => store.answers[i] ?? []))
|
||||||
void reply(questions().map((_, i) => store.answers[i] ?? []))
|
|
||||||
}
|
|
||||||
|
|
||||||
const pick = (answer: string, custom: boolean = false) => {
|
const pick = (answer: string, custom: boolean = false) => {
|
||||||
setStore("answers", store.tab, [answer])
|
setStore("answers", store.tab, [answer])
|
||||||
|
if (custom) setStore("custom", store.tab, answer)
|
||||||
if (custom) {
|
if (!custom) setStore("customOn", store.tab, false)
|
||||||
setStore("custom", store.tab, answer)
|
setStore("editing", false)
|
||||||
}
|
|
||||||
|
|
||||||
if (single()) {
|
|
||||||
void reply([[answer]])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setStore("tab", store.tab + 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggle = (answer: string) => {
|
const toggle = (answer: string) => {
|
||||||
@@ -90,16 +153,41 @@ export const QuestionDock: Component<{ request: QuestionRequest }> = (props) =>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectTab = (index: number) => {
|
const customToggle = () => {
|
||||||
setStore("tab", index)
|
if (store.sending) return
|
||||||
|
|
||||||
|
if (!multi()) {
|
||||||
|
setStore("customOn", store.tab, true)
|
||||||
|
setStore("editing", true)
|
||||||
|
customUpdate(input(), true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const next = !on()
|
||||||
|
setStore("customOn", store.tab, next)
|
||||||
|
if (next) {
|
||||||
|
setStore("editing", true)
|
||||||
|
customUpdate(input(), true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = input().trim()
|
||||||
|
if (value) setStore("answers", store.tab, (current = []) => current.filter((item) => item.trim() !== value))
|
||||||
setStore("editing", false)
|
setStore("editing", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const customOpen = () => {
|
||||||
|
if (store.sending) return
|
||||||
|
if (!on()) setStore("customOn", store.tab, true)
|
||||||
|
setStore("editing", true)
|
||||||
|
customUpdate(input(), true)
|
||||||
|
}
|
||||||
|
|
||||||
const selectOption = (optIndex: number) => {
|
const selectOption = (optIndex: number) => {
|
||||||
if (store.sending) return
|
if (store.sending) return
|
||||||
|
|
||||||
if (optIndex === options().length) {
|
if (optIndex === options().length) {
|
||||||
setStore("editing", true)
|
customOpen()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,67 +200,67 @@ export const QuestionDock: Component<{ request: QuestionRequest }> = (props) =>
|
|||||||
pick(opt.label)
|
pick(opt.label)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCustomSubmit = (e: Event) => {
|
const commitCustom = () => {
|
||||||
e.preventDefault()
|
setStore("editing", false)
|
||||||
|
customUpdate(input())
|
||||||
|
}
|
||||||
|
|
||||||
|
const next = () => {
|
||||||
if (store.sending) return
|
if (store.sending) return
|
||||||
|
if (store.editing) commitCustom()
|
||||||
|
|
||||||
const value = input().trim()
|
if (store.tab >= total() - 1) {
|
||||||
if (!value) {
|
submit()
|
||||||
setStore("editing", false)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (multi()) {
|
setStore("tab", store.tab + 1)
|
||||||
setStore("answers", store.tab, (current = []) => {
|
setStore("editing", false)
|
||||||
if (current.includes(value)) return current
|
}
|
||||||
return [...current, value]
|
|
||||||
})
|
|
||||||
setStore("editing", false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pick(value, true)
|
const back = () => {
|
||||||
|
if (store.sending) return
|
||||||
|
if (store.tab <= 0) return
|
||||||
|
setStore("tab", store.tab - 1)
|
||||||
|
setStore("editing", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const jump = (tab: number) => {
|
||||||
|
if (store.sending) return
|
||||||
|
setStore("tab", tab)
|
||||||
setStore("editing", false)
|
setStore("editing", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-component="question-prompt">
|
<div data-component="question-prompt" ref={(el) => (root = el)}>
|
||||||
<Show when={!single()}>
|
<div data-slot="question-body">
|
||||||
<div data-slot="question-tabs">
|
<div data-slot="question-header">
|
||||||
<For each={questions()}>
|
<div data-slot="question-header-title">{summary()}</div>
|
||||||
{(q, index) => {
|
<div data-slot="question-progress">
|
||||||
const active = () => index() === store.tab
|
<For each={questions()}>
|
||||||
const answered = () => (store.answers[index()]?.length ?? 0) > 0
|
{(_, i) => (
|
||||||
return (
|
|
||||||
<button
|
<button
|
||||||
data-slot="question-tab"
|
type="button"
|
||||||
data-active={active()}
|
data-slot="question-progress-segment"
|
||||||
data-answered={answered()}
|
data-active={i() === store.tab}
|
||||||
|
data-answered={
|
||||||
|
(store.answers[i()]?.length ?? 0) > 0 ||
|
||||||
|
(store.customOn[i()] === true && (store.custom[i()] ?? "").trim().length > 0)
|
||||||
|
}
|
||||||
disabled={store.sending}
|
disabled={store.sending}
|
||||||
onClick={() => selectTab(index())}
|
onClick={() => jump(i())}
|
||||||
>
|
aria-label={`${language.t("ui.tool.questions")} ${i() + 1}`}
|
||||||
{q.header}
|
/>
|
||||||
</button>
|
)}
|
||||||
)
|
</For>
|
||||||
}}
|
|
||||||
</For>
|
|
||||||
<button
|
|
||||||
data-slot="question-tab"
|
|
||||||
data-active={confirm()}
|
|
||||||
disabled={store.sending}
|
|
||||||
onClick={() => selectTab(questions().length)}
|
|
||||||
>
|
|
||||||
{language.t("ui.common.confirm")}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<Show when={!confirm()}>
|
|
||||||
<div data-slot="question-content">
|
|
||||||
<div data-slot="question-text">
|
|
||||||
{question()?.question}
|
|
||||||
{multi() ? " " + language.t("ui.question.multiHint") : ""}
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-slot="question-content">
|
||||||
|
<div data-slot="question-text">{question()?.question}</div>
|
||||||
|
<Show when={multi()} fallback={<div data-slot="question-hint">{language.t("ui.question.singleHint")}</div>}>
|
||||||
|
<div data-slot="question-hint">{language.t("ui.question.multiHint")}</div>
|
||||||
|
</Show>
|
||||||
<div data-slot="question-options">
|
<div data-slot="question-options">
|
||||||
<For each={options()}>
|
<For each={options()}>
|
||||||
{(opt, i) => {
|
{(opt, i) => {
|
||||||
@@ -181,106 +269,156 @@ export const QuestionDock: Component<{ request: QuestionRequest }> = (props) =>
|
|||||||
<button
|
<button
|
||||||
data-slot="question-option"
|
data-slot="question-option"
|
||||||
data-picked={picked()}
|
data-picked={picked()}
|
||||||
|
role={multi() ? "checkbox" : "radio"}
|
||||||
|
aria-checked={picked()}
|
||||||
disabled={store.sending}
|
disabled={store.sending}
|
||||||
onClick={() => selectOption(i())}
|
onClick={() => selectOption(i())}
|
||||||
>
|
>
|
||||||
<span data-slot="option-label">{opt.label}</span>
|
<span data-slot="question-option-check" aria-hidden="true">
|
||||||
<Show when={opt.description}>
|
<span
|
||||||
<span data-slot="option-description">{opt.description}</span>
|
data-slot="question-option-box"
|
||||||
</Show>
|
data-type={multi() ? "checkbox" : "radio"}
|
||||||
<Show when={picked()}>
|
data-picked={picked()}
|
||||||
<Icon name="check-small" size="normal" />
|
>
|
||||||
</Show>
|
<Show when={multi()} fallback={<span data-slot="question-option-radio-dot" />}>
|
||||||
|
<Icon name="check-small" size="small" />
|
||||||
|
</Show>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span data-slot="question-option-main">
|
||||||
|
<span data-slot="option-label">{opt.label}</span>
|
||||||
|
<Show when={opt.description}>
|
||||||
|
<span data-slot="option-description">{opt.description}</span>
|
||||||
|
</Show>
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</For>
|
</For>
|
||||||
<button
|
|
||||||
data-slot="question-option"
|
<Show
|
||||||
data-picked={customPicked()}
|
when={store.editing}
|
||||||
disabled={store.sending}
|
fallback={
|
||||||
onClick={() => selectOption(options().length)}
|
<button
|
||||||
>
|
data-slot="question-option"
|
||||||
<span data-slot="option-label">{language.t("ui.messagePart.option.typeOwnAnswer")}</span>
|
data-custom="true"
|
||||||
<Show when={!store.editing && input()}>
|
data-picked={on()}
|
||||||
<span data-slot="option-description">{input()}</span>
|
role={multi() ? "checkbox" : "radio"}
|
||||||
</Show>
|
aria-checked={on()}
|
||||||
<Show when={customPicked()}>
|
|
||||||
<Icon name="check-small" size="normal" />
|
|
||||||
</Show>
|
|
||||||
</button>
|
|
||||||
<Show when={store.editing}>
|
|
||||||
<form data-slot="custom-input-form" onSubmit={handleCustomSubmit}>
|
|
||||||
<input
|
|
||||||
ref={(el) => setTimeout(() => el.focus(), 0)}
|
|
||||||
type="text"
|
|
||||||
data-slot="custom-input"
|
|
||||||
placeholder={language.t("ui.question.custom.placeholder")}
|
|
||||||
value={input()}
|
|
||||||
disabled={store.sending}
|
disabled={store.sending}
|
||||||
onInput={(e) => {
|
onClick={customOpen}
|
||||||
setStore("custom", store.tab, e.currentTarget.value)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button type="submit" variant="primary" size="small" disabled={store.sending}>
|
|
||||||
{multi() ? language.t("ui.common.add") : language.t("ui.common.submit")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="small"
|
|
||||||
disabled={store.sending}
|
|
||||||
onClick={() => setStore("editing", false)}
|
|
||||||
>
|
>
|
||||||
{language.t("ui.common.cancel")}
|
<span
|
||||||
</Button>
|
data-slot="question-option-check"
|
||||||
|
aria-hidden="true"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
customToggle()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span data-slot="question-option-box" data-type={multi() ? "checkbox" : "radio"} data-picked={on()}>
|
||||||
|
<Show when={multi()} fallback={<span data-slot="question-option-radio-dot" />}>
|
||||||
|
<Icon name="check-small" size="small" />
|
||||||
|
</Show>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span data-slot="question-option-main">
|
||||||
|
<span data-slot="option-label">{language.t("ui.messagePart.option.typeOwnAnswer")}</span>
|
||||||
|
<span data-slot="option-description">
|
||||||
|
{input() || language.t("ui.question.custom.placeholder")}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
data-slot="question-option"
|
||||||
|
data-custom="true"
|
||||||
|
data-picked={on()}
|
||||||
|
role={multi() ? "checkbox" : "radio"}
|
||||||
|
aria-checked={on()}
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
if (store.sending) {
|
||||||
|
e.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (e.target instanceof HTMLTextAreaElement) return
|
||||||
|
const input = e.currentTarget.querySelector('[data-slot="question-custom-input"]')
|
||||||
|
if (input instanceof HTMLTextAreaElement) input.focus()
|
||||||
|
}}
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
commitCustom()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
data-slot="question-option-check"
|
||||||
|
aria-hidden="true"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
customToggle()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span data-slot="question-option-box" data-type={multi() ? "checkbox" : "radio"} data-picked={on()}>
|
||||||
|
<Show when={multi()} fallback={<span data-slot="question-option-radio-dot" />}>
|
||||||
|
<Icon name="check-small" size="small" />
|
||||||
|
</Show>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span data-slot="question-option-main">
|
||||||
|
<span data-slot="option-label">{language.t("ui.messagePart.option.typeOwnAnswer")}</span>
|
||||||
|
<textarea
|
||||||
|
ref={(el) =>
|
||||||
|
setTimeout(() => {
|
||||||
|
el.focus()
|
||||||
|
el.style.height = "0px"
|
||||||
|
el.style.height = `${el.scrollHeight}px`
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
data-slot="question-custom-input"
|
||||||
|
placeholder={language.t("ui.question.custom.placeholder")}
|
||||||
|
value={input()}
|
||||||
|
rows={1}
|
||||||
|
disabled={store.sending}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
e.preventDefault()
|
||||||
|
setStore("editing", false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (e.key !== "Enter" || e.shiftKey) return
|
||||||
|
e.preventDefault()
|
||||||
|
commitCustom()
|
||||||
|
}}
|
||||||
|
onInput={(e) => {
|
||||||
|
customUpdate(e.currentTarget.value)
|
||||||
|
e.currentTarget.style.height = "0px"
|
||||||
|
e.currentTarget.style.height = `${e.currentTarget.scrollHeight}px`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
</form>
|
</form>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</div>
|
||||||
|
|
||||||
<Show when={confirm()}>
|
<div data-slot="question-footer">
|
||||||
<div data-slot="question-review">
|
<Button variant="ghost" size="large" disabled={store.sending} onClick={reject}>
|
||||||
<div data-slot="review-title">{language.t("ui.messagePart.review.title")}</div>
|
|
||||||
<For each={questions()}>
|
|
||||||
{(q, index) => {
|
|
||||||
const value = () => store.answers[index()]?.join(", ") ?? ""
|
|
||||||
const answered = () => Boolean(value())
|
|
||||||
return (
|
|
||||||
<div data-slot="review-item">
|
|
||||||
<span data-slot="review-label">{q.question}</span>
|
|
||||||
<span data-slot="review-value" data-answered={answered()}>
|
|
||||||
{answered() ? value() : language.t("ui.question.review.notAnswered")}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<div data-slot="question-actions">
|
|
||||||
<Button variant="ghost" size="small" onClick={reject} disabled={store.sending}>
|
|
||||||
{language.t("ui.common.dismiss")}
|
{language.t("ui.common.dismiss")}
|
||||||
</Button>
|
</Button>
|
||||||
<Show when={!single()}>
|
<div data-slot="question-footer-actions">
|
||||||
<Show when={confirm()}>
|
<Show when={store.tab > 0}>
|
||||||
<Button variant="primary" size="small" onClick={submit} disabled={store.sending}>
|
<Button variant="secondary" size="large" disabled={store.sending} onClick={back}>
|
||||||
{language.t("ui.common.submit")}
|
{language.t("ui.common.back")}
|
||||||
</Button>
|
</Button>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={!confirm() && multi()}>
|
<Button variant={last() ? "primary" : "secondary"} size="large" disabled={store.sending} onClick={next}>
|
||||||
<Button
|
{last() ? language.t("ui.common.submit") : language.t("ui.common.next")}
|
||||||
variant="secondary"
|
</Button>
|
||||||
size="small"
|
</div>
|
||||||
onClick={() => selectTab(store.tab + 1)}
|
|
||||||
disabled={store.sending || (store.answers[store.tab]?.length ?? 0) === 0}
|
|
||||||
>
|
|
||||||
{language.t("ui.common.next")}
|
|
||||||
</Button>
|
|
||||||
</Show>
|
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Match, Show, Switch, createMemo } from "solid-js"
|
import { Match, Show, Switch, createMemo } from "solid-js"
|
||||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
import { Tooltip, type TooltipProps } from "@opencode-ai/ui/tooltip"
|
||||||
import { ProgressCircle } from "@opencode-ai/ui/progress-circle"
|
import { ProgressCircle } from "@opencode-ai/ui/progress-circle"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { useParams } from "@solidjs/router"
|
import { useParams } from "@solidjs/router"
|
||||||
@@ -11,6 +11,7 @@ import { getSessionContextMetrics } from "@/components/session/session-context-m
|
|||||||
|
|
||||||
interface SessionContextUsageProps {
|
interface SessionContextUsageProps {
|
||||||
variant?: "button" | "indicator"
|
variant?: "button" | "indicator"
|
||||||
|
placement?: TooltipProps["placement"]
|
||||||
}
|
}
|
||||||
|
|
||||||
function openSessionContext(args: {
|
function openSessionContext(args: {
|
||||||
@@ -52,6 +53,11 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
|||||||
|
|
||||||
const openContext = () => {
|
const openContext = () => {
|
||||||
if (!params.id) return
|
if (!params.id) return
|
||||||
|
|
||||||
|
if (tabs().active() === "context") {
|
||||||
|
tabs().close("context")
|
||||||
|
return
|
||||||
|
}
|
||||||
openSessionContext({
|
openSessionContext({
|
||||||
view: view(),
|
view: view(),
|
||||||
layout,
|
layout,
|
||||||
@@ -90,7 +96,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={params.id}>
|
<Show when={params.id}>
|
||||||
<Tooltip value={tooltipValue()} placement="top">
|
<Tooltip value={tooltipValue()} placement={props.placement ?? "top"}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={variant() === "indicator"}>{circle()}</Match>
|
<Match when={variant() === "indicator"}>{circle()}</Match>
|
||||||
<Match when={true}>
|
<Match when={true}>
|
||||||
|
|||||||
208
packages/app/src/components/session-todo-dock.tsx
Normal file
208
packages/app/src/components/session-todo-dock.tsx
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
import type { Todo } from "@opencode-ai/sdk/v2"
|
||||||
|
import { Checkbox } from "@opencode-ai/ui/checkbox"
|
||||||
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
|
import { For, Show, createEffect, createMemo, createSignal, on, onCleanup } from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
|
|
||||||
|
function dot(status: Todo["status"]) {
|
||||||
|
if (status !== "in_progress") return undefined
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 12 12"
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
fill="currentColor"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="block"
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
cx="6"
|
||||||
|
cy="6"
|
||||||
|
r="3"
|
||||||
|
style={{
|
||||||
|
animation: "var(--animate-pulse-scale)",
|
||||||
|
"transform-origin": "center",
|
||||||
|
"transform-box": "fill-box",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SessionTodoDock(props: { todos: Todo[]; title: string; collapseLabel: string; expandLabel: string }) {
|
||||||
|
const [store, setStore] = createStore({
|
||||||
|
collapsed: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const toggle = () => setStore("collapsed", (value) => !value)
|
||||||
|
|
||||||
|
const summary = createMemo(() => {
|
||||||
|
const total = props.todos.length
|
||||||
|
if (total === 0) return ""
|
||||||
|
const completed = props.todos.filter((todo) => todo.status === "completed").length
|
||||||
|
return `${completed} of ${total} ${props.title.toLowerCase()} completed`
|
||||||
|
})
|
||||||
|
|
||||||
|
const active = createMemo(
|
||||||
|
() =>
|
||||||
|
props.todos.find((todo) => todo.status === "in_progress") ??
|
||||||
|
props.todos.find((todo) => todo.status === "pending") ??
|
||||||
|
props.todos.filter((todo) => todo.status === "completed").at(-1) ??
|
||||||
|
props.todos[0],
|
||||||
|
)
|
||||||
|
|
||||||
|
const preview = createMemo(() => active()?.content ?? "")
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
classList={{
|
||||||
|
"bg-background-base border border-border-weak-base relative z-0 rounded-[12px] overflow-clip": true,
|
||||||
|
"h-[78px]": store.collapsed,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="pl-3 pr-2 py-2 flex items-center gap-2"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={toggle}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key !== "Enter" && event.key !== " ") return
|
||||||
|
event.preventDefault()
|
||||||
|
toggle()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span class="text-14-regular text-text-strong cursor-default">{summary()}</span>
|
||||||
|
<Show when={store.collapsed}>
|
||||||
|
<div class="ml-1 flex-1 min-w-0">
|
||||||
|
<Show when={preview()}>
|
||||||
|
<div class="text-14-regular text-text-base truncate cursor-default">{preview()}</div>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
<div classList={{ "ml-auto": !store.collapsed, "ml-1": store.collapsed }}>
|
||||||
|
<IconButton
|
||||||
|
icon="chevron-down"
|
||||||
|
size="normal"
|
||||||
|
variant="ghost"
|
||||||
|
classList={{ "rotate-180": !store.collapsed }}
|
||||||
|
onMouseDown={(event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
}}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation()
|
||||||
|
toggle()
|
||||||
|
}}
|
||||||
|
aria-label={store.collapsed ? props.expandLabel : props.collapseLabel}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div hidden={store.collapsed}>
|
||||||
|
<TodoList todos={props.todos} open={!store.collapsed} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TodoList(props: { todos: Todo[]; open: boolean }) {
|
||||||
|
const [stuck, setStuck] = createSignal(false)
|
||||||
|
const [scrolling, setScrolling] = createSignal(false)
|
||||||
|
let scrollRef!: HTMLDivElement
|
||||||
|
let timer: number | undefined
|
||||||
|
|
||||||
|
const inProgress = createMemo(() => props.todos.findIndex((todo) => todo.status === "in_progress"))
|
||||||
|
|
||||||
|
const ensure = () => {
|
||||||
|
if (!props.open) return
|
||||||
|
if (scrolling()) return
|
||||||
|
if (!scrollRef || scrollRef.offsetParent === null) return
|
||||||
|
|
||||||
|
const el = scrollRef.querySelector("[data-in-progress]")
|
||||||
|
if (!(el instanceof HTMLElement)) return
|
||||||
|
|
||||||
|
const topFade = 16
|
||||||
|
const bottomFade = 44
|
||||||
|
const container = scrollRef.getBoundingClientRect()
|
||||||
|
const rect = el.getBoundingClientRect()
|
||||||
|
const top = rect.top - container.top + scrollRef.scrollTop
|
||||||
|
const bottom = rect.bottom - container.top + scrollRef.scrollTop
|
||||||
|
const viewTop = scrollRef.scrollTop + topFade
|
||||||
|
const viewBottom = scrollRef.scrollTop + scrollRef.clientHeight - bottomFade
|
||||||
|
|
||||||
|
if (top < viewTop) {
|
||||||
|
scrollRef.scrollTop = Math.max(0, top - topFade)
|
||||||
|
} else if (bottom > viewBottom) {
|
||||||
|
scrollRef.scrollTop = bottom - (scrollRef.clientHeight - bottomFade)
|
||||||
|
}
|
||||||
|
|
||||||
|
setStuck(scrollRef.scrollTop > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
createEffect(
|
||||||
|
on([() => props.open, inProgress], () => {
|
||||||
|
if (!props.open || inProgress() < 0) return
|
||||||
|
requestAnimationFrame(ensure)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
if (!timer) return
|
||||||
|
window.clearTimeout(timer)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="relative">
|
||||||
|
<div
|
||||||
|
class="px-3 pb-11 flex flex-col gap-1.5 max-h-42 overflow-y-auto no-scrollbar"
|
||||||
|
ref={scrollRef}
|
||||||
|
style={{ "overflow-anchor": "none" }}
|
||||||
|
onScroll={(e) => {
|
||||||
|
setStuck(e.currentTarget.scrollTop > 0)
|
||||||
|
setScrolling(true)
|
||||||
|
if (timer) window.clearTimeout(timer)
|
||||||
|
timer = window.setTimeout(() => {
|
||||||
|
setScrolling(false)
|
||||||
|
if (inProgress() < 0) return
|
||||||
|
requestAnimationFrame(ensure)
|
||||||
|
}, 250)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<For each={props.todos}>
|
||||||
|
{(todo) => (
|
||||||
|
<Checkbox
|
||||||
|
readOnly
|
||||||
|
checked={todo.status === "completed"}
|
||||||
|
indeterminate={todo.status === "in_progress"}
|
||||||
|
data-in-progress={todo.status === "in_progress" ? "" : undefined}
|
||||||
|
icon={dot(todo.status)}
|
||||||
|
style={{ "--checkbox-align": "flex-start", "--checkbox-offset": "1px" }}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="text-14-regular min-w-0 break-words"
|
||||||
|
classList={{
|
||||||
|
"text-text-weak": todo.status === "completed" || todo.status === "cancelled",
|
||||||
|
"text-text-strong": todo.status !== "completed" && todo.status !== "cancelled",
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
"line-height": "var(--line-height-normal)",
|
||||||
|
"text-decoration":
|
||||||
|
todo.status === "completed" || todo.status === "cancelled" ? "line-through" : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{todo.content}
|
||||||
|
</span>
|
||||||
|
</Checkbox>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute top-0 left-0 right-0 h-4 transition-opacity duration-150"
|
||||||
|
style={{
|
||||||
|
background: "linear-gradient(to bottom, var(--background-base), transparent)",
|
||||||
|
opacity: stuck() ? 1 : 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -372,7 +372,7 @@ export function SessionHeader() {
|
|||||||
<div class="flex h-[24px] box-border items-center rounded-md border border-border-base bg-surface-panel overflow-hidden">
|
<div class="flex h-[24px] box-border items-center rounded-md border border-border-base bg-surface-panel overflow-hidden">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="rounded-none h-full py-0 pr-3 pl-2 gap-1.5 border-none shadow-none"
|
class="rounded-none h-full py-0 pr-3 pl-0.5 gap-1.5 border-none shadow-none"
|
||||||
onClick={() => openDir(current().id)}
|
onClick={() => openDir(current().id)}
|
||||||
aria-label={language.t("session.header.open.ariaLabel", { app: current().label })}
|
aria-label={language.t("session.header.open.ariaLabel", { app: current().label })}
|
||||||
>
|
>
|
||||||
@@ -552,14 +552,14 @@ export function SessionHeader() {
|
|||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<div class="flex items-center gap-3 ml-2 shrink-0">
|
<div class="hidden md:flex items-center gap-3 ml-2 shrink-0">
|
||||||
<TooltipKeybind
|
<TooltipKeybind
|
||||||
title={language.t("command.terminal.toggle")}
|
title={language.t("command.terminal.toggle")}
|
||||||
keybind={command.keybind("terminal.toggle")}
|
keybind={command.keybind("terminal.toggle")}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="group/terminal-toggle size-6 p-0"
|
class="group/terminal-toggle titlebar-icon w-8 h-6 p-0 box-border"
|
||||||
onClick={() => view().terminal.toggle()}
|
onClick={() => view().terminal.toggle()}
|
||||||
aria-label={language.t("command.terminal.toggle")}
|
aria-label={language.t("command.terminal.toggle")}
|
||||||
aria-expanded={view().terminal.opened()}
|
aria-expanded={view().terminal.opened()}
|
||||||
@@ -568,7 +568,7 @@ export function SessionHeader() {
|
|||||||
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
||||||
<Icon
|
<Icon
|
||||||
size="small"
|
size="small"
|
||||||
name={view().terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
|
name={view().terminal.opened() ? "layout-bottom-partial" : "layout-bottom"}
|
||||||
class="group-hover/terminal-toggle:hidden"
|
class="group-hover/terminal-toggle:hidden"
|
||||||
/>
|
/>
|
||||||
<Icon
|
<Icon
|
||||||
@@ -578,18 +578,18 @@ export function SessionHeader() {
|
|||||||
/>
|
/>
|
||||||
<Icon
|
<Icon
|
||||||
size="small"
|
size="small"
|
||||||
name={view().terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
|
name={view().terminal.opened() ? "layout-bottom" : "layout-bottom-partial"}
|
||||||
class="hidden group-active/terminal-toggle:inline-block"
|
class="hidden group-active/terminal-toggle:inline-block"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipKeybind>
|
</TooltipKeybind>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden lg:block shrink-0">
|
<div class="hidden md:block shrink-0">
|
||||||
<TooltipKeybind title={language.t("command.review.toggle")} keybind={command.keybind("review.toggle")}>
|
<TooltipKeybind title={language.t("command.review.toggle")} keybind={command.keybind("review.toggle")}>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="group/review-toggle size-6 p-0"
|
class="group/review-toggle titlebar-icon w-8 h-6 p-0 box-border"
|
||||||
onClick={() => view().reviewPanel.toggle()}
|
onClick={() => view().reviewPanel.toggle()}
|
||||||
aria-label={language.t("command.review.toggle")}
|
aria-label={language.t("command.review.toggle")}
|
||||||
aria-expanded={view().reviewPanel.opened()}
|
aria-expanded={view().reviewPanel.opened()}
|
||||||
@@ -598,7 +598,7 @@ export function SessionHeader() {
|
|||||||
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
||||||
<Icon
|
<Icon
|
||||||
size="small"
|
size="small"
|
||||||
name={view().reviewPanel.opened() ? "layout-right-full" : "layout-right"}
|
name={view().reviewPanel.opened() ? "layout-right-partial" : "layout-right"}
|
||||||
class="group-hover/review-toggle:hidden"
|
class="group-hover/review-toggle:hidden"
|
||||||
/>
|
/>
|
||||||
<Icon
|
<Icon
|
||||||
@@ -608,38 +608,57 @@ export function SessionHeader() {
|
|||||||
/>
|
/>
|
||||||
<Icon
|
<Icon
|
||||||
size="small"
|
size="small"
|
||||||
name={view().reviewPanel.opened() ? "layout-right" : "layout-right-full"}
|
name={view().reviewPanel.opened() ? "layout-right" : "layout-right-partial"}
|
||||||
class="hidden group-active/review-toggle:inline-block"
|
class="hidden group-active/review-toggle:inline-block"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipKeybind>
|
</TooltipKeybind>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden lg:block shrink-0">
|
<div class="hidden md:block shrink-0">
|
||||||
<TooltipKeybind
|
<div
|
||||||
title={language.t("command.fileTree.toggle")}
|
aria-hidden={!view().reviewPanel.opened()}
|
||||||
keybind={command.keybind("fileTree.toggle")}
|
class="overflow-hidden transition-[width,margin-left] duration-200 ease-out motion-reduce:transition-none"
|
||||||
|
classList={{
|
||||||
|
"w-8 ml-0": view().reviewPanel.opened(),
|
||||||
|
"w-0 -ml-1": !view().reviewPanel.opened(),
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<div
|
||||||
variant="ghost"
|
class="transition-[opacity,transform] duration-200 ease-out origin-center motion-reduce:transition-none"
|
||||||
class="group/file-tree-toggle size-6 p-0"
|
classList={{
|
||||||
onClick={() => layout.fileTree.toggle()}
|
"opacity-100 scale-100": view().reviewPanel.opened(),
|
||||||
aria-label={language.t("command.fileTree.toggle")}
|
"opacity-0 scale-90": !view().reviewPanel.opened(),
|
||||||
aria-expanded={layout.fileTree.opened()}
|
}}
|
||||||
aria-controls="file-tree-panel"
|
|
||||||
>
|
>
|
||||||
<div class="relative flex items-center justify-center size-4">
|
<TooltipKeybind
|
||||||
<Icon
|
title={language.t("command.fileTree.toggle")}
|
||||||
size="small"
|
keybind={command.keybind("fileTree.toggle")}
|
||||||
name="bullet-list"
|
>
|
||||||
classList={{
|
<Button
|
||||||
"text-icon-strong": layout.fileTree.opened(),
|
variant="ghost"
|
||||||
"text-icon-weak": !layout.fileTree.opened(),
|
class="titlebar-icon w-8 h-6 p-0 box-border"
|
||||||
}}
|
onClick={() => layout.fileTree.toggle()}
|
||||||
/>
|
disabled={!view().reviewPanel.opened()}
|
||||||
</div>
|
aria-label={language.t("command.fileTree.toggle")}
|
||||||
</Button>
|
aria-expanded={layout.fileTree.opened()}
|
||||||
</TooltipKeybind>
|
aria-controls="file-tree-panel"
|
||||||
|
tabIndex={view().reviewPanel.opened() ? undefined : -1}
|
||||||
|
>
|
||||||
|
<div class="relative flex items-center justify-center size-4">
|
||||||
|
<Icon
|
||||||
|
size="small"
|
||||||
|
name={layout.fileTree.opened() ? "file-tree-active" : "file-tree"}
|
||||||
|
classList={{
|
||||||
|
"text-icon-strong": layout.fileTree.opened(),
|
||||||
|
"text-icon-weak": !layout.fileTree.opened(),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</TooltipKeybind>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Portal>
|
</Portal>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
|||||||
const MAIN_WORKTREE = "main"
|
const MAIN_WORKTREE = "main"
|
||||||
const CREATE_WORKTREE = "create"
|
const CREATE_WORKTREE = "create"
|
||||||
const ROOT_CLASS =
|
const ROOT_CLASS =
|
||||||
"size-full flex flex-col justify-end items-start gap-4 flex-[1_0_0] self-stretch max-w-200 mx-auto 2xl:max-w-[1000px] px-6 pb-[calc(var(--prompt-height,11.25rem)+64px)]"
|
"size-full flex flex-col justify-end items-start gap-4 flex-[1_0_0] self-stretch max-w-200 mx-auto 2xl:max-w-[1000px] px-6 pb-16"
|
||||||
|
|
||||||
interface NewSessionViewProps {
|
interface NewSessionViewProps {
|
||||||
worktree: string
|
worktree: string
|
||||||
|
|||||||
@@ -196,19 +196,21 @@ export function StatusPopover() {
|
|||||||
triggerProps={{
|
triggerProps={{
|
||||||
variant: "ghost",
|
variant: "ghost",
|
||||||
class:
|
class:
|
||||||
"rounded-md h-[24px] px-3 gap-2 border border-border-base bg-surface-panel shadow-none data-[expanded]:bg-surface-raised-base-active",
|
"rounded-md h-[24px] pr-3 pl-0.5 gap-2 border border-border-base bg-surface-panel shadow-none data-[expanded]:bg-surface-raised-base-active",
|
||||||
style: { scale: 1 },
|
style: { scale: 1 },
|
||||||
}}
|
}}
|
||||||
trigger={
|
trigger={
|
||||||
<div class="flex items-center gap-1.5">
|
<div class="flex items-center gap-0.5">
|
||||||
<div
|
<div class="size-4 flex items-center justify-center">
|
||||||
classList={{
|
<div
|
||||||
"size-1.5 rounded-full": true,
|
classList={{
|
||||||
"bg-icon-success-base": overallHealthy(),
|
"size-1.5 rounded-full": true,
|
||||||
"bg-icon-critical-base": !overallHealthy() && server.healthy() !== undefined,
|
"bg-icon-success-base": overallHealthy(),
|
||||||
"bg-border-weak-base": server.healthy() === undefined,
|
"bg-icon-critical-base": !overallHealthy() && server.healthy() !== undefined,
|
||||||
}}
|
"bg-border-weak-base": server.healthy() === undefined,
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<span class="text-12-regular text-text-strong">{language.t("status.popover.trigger")}</span>
|
<span class="text-12-regular text-text-strong">{language.t("status.popover.trigger")}</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createEffect, createMemo, Show, untrack } from "solid-js"
|
import { createEffect, createMemo, Show, untrack } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { useLocation, useNavigate } from "@solidjs/router"
|
import { useLocation, useNavigate, useParams } from "@solidjs/router"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
@@ -43,6 +43,7 @@ export function Titlebar() {
|
|||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const params = useParams()
|
||||||
|
|
||||||
const mac = createMemo(() => platform.platform === "desktop" && platform.os === "macos")
|
const mac = createMemo(() => platform.platform === "desktop" && platform.os === "macos")
|
||||||
const windows = createMemo(() => platform.platform === "desktop" && platform.os === "windows")
|
const windows = createMemo(() => platform.platform === "desktop" && platform.os === "windows")
|
||||||
@@ -171,9 +172,10 @@ export function Titlebar() {
|
|||||||
<IconButton
|
<IconButton
|
||||||
icon="menu"
|
icon="menu"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="size-8 rounded-md"
|
class="titlebar-icon rounded-md"
|
||||||
onClick={layout.mobileSidebar.toggle}
|
onClick={layout.mobileSidebar.toggle}
|
||||||
aria-label={language.t("sidebar.menu.toggle")}
|
aria-label={language.t("sidebar.menu.toggle")}
|
||||||
|
aria-expanded={layout.mobileSidebar.opened()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
@@ -182,13 +184,14 @@ export function Titlebar() {
|
|||||||
<IconButton
|
<IconButton
|
||||||
icon="menu"
|
icon="menu"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="size-8 rounded-md"
|
class="titlebar-icon rounded-md"
|
||||||
onClick={layout.mobileSidebar.toggle}
|
onClick={layout.mobileSidebar.toggle}
|
||||||
aria-label={language.t("sidebar.menu.toggle")}
|
aria-label={language.t("sidebar.menu.toggle")}
|
||||||
|
aria-expanded={layout.mobileSidebar.opened()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<div class="flex items-center gap-3 shrink-0">
|
<div class="flex items-center gap-1 shrink-0">
|
||||||
<TooltipKeybind
|
<TooltipKeybind
|
||||||
class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0 ml-2"}
|
class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0 ml-2"}
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
@@ -197,7 +200,7 @@ export function Titlebar() {
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="group/sidebar-toggle size-6 p-0"
|
class="group/sidebar-toggle titlebar-icon w-8 h-6 p-0 box-border"
|
||||||
onClick={layout.sidebar.toggle}
|
onClick={layout.sidebar.toggle}
|
||||||
aria-label={language.t("command.sidebar.toggle")}
|
aria-label={language.t("command.sidebar.toggle")}
|
||||||
aria-expanded={layout.sidebar.opened()}
|
aria-expanded={layout.sidebar.opened()}
|
||||||
@@ -205,39 +208,60 @@ export function Titlebar() {
|
|||||||
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
||||||
<Icon
|
<Icon
|
||||||
size="small"
|
size="small"
|
||||||
name={layout.sidebar.opened() ? "layout-left-full" : "layout-left"}
|
name={layout.sidebar.opened() ? "layout-left-partial" : "layout-left"}
|
||||||
class="group-hover/sidebar-toggle:hidden"
|
class="group-hover/sidebar-toggle:hidden"
|
||||||
/>
|
/>
|
||||||
<Icon size="small" name="layout-left-partial" class="hidden group-hover/sidebar-toggle:inline-block" />
|
<Icon size="small" name="layout-left-partial" class="hidden group-hover/sidebar-toggle:inline-block" />
|
||||||
<Icon
|
<Icon
|
||||||
size="small"
|
size="small"
|
||||||
name={layout.sidebar.opened() ? "layout-left" : "layout-left-full"}
|
name={layout.sidebar.opened() ? "layout-left" : "layout-left-partial"}
|
||||||
class="hidden group-active/sidebar-toggle:inline-block"
|
class="hidden group-active/sidebar-toggle:inline-block"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipKeybind>
|
</TooltipKeybind>
|
||||||
<div class="hidden xl:flex items-center gap-1 shrink-0">
|
<div class="hidden xl:flex items-center shrink-0">
|
||||||
<Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={2000}>
|
<Show when={params.dir}>
|
||||||
<Button
|
<TooltipKeybind
|
||||||
variant="ghost"
|
placement="bottom"
|
||||||
icon="arrow-left"
|
title={language.t("command.session.new")}
|
||||||
class="size-6 p-0"
|
keybind={command.keybind("session.new")}
|
||||||
disabled={!canBack()}
|
openDelay={2000}
|
||||||
onClick={back}
|
>
|
||||||
aria-label={language.t("common.goBack")}
|
<Button
|
||||||
/>
|
variant="ghost"
|
||||||
</Tooltip>
|
icon="new-session"
|
||||||
<Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={2000}>
|
class="titlebar-icon w-8 h-6 p-0 box-border"
|
||||||
<Button
|
onClick={() => {
|
||||||
variant="ghost"
|
if (!params.dir) return
|
||||||
icon="arrow-right"
|
navigate(`/${params.dir}/session`)
|
||||||
class="size-6 p-0"
|
}}
|
||||||
disabled={!canForward()}
|
aria-label={language.t("command.session.new")}
|
||||||
onClick={forward}
|
/>
|
||||||
aria-label={language.t("common.goForward")}
|
</TooltipKeybind>
|
||||||
/>
|
</Show>
|
||||||
</Tooltip>
|
<div class="flex items-center gap-0" classList={{ "ml-1": !!params.dir }}>
|
||||||
|
<Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={2000}>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
icon="chevron-left"
|
||||||
|
class="titlebar-icon w-6 h-6 p-0 box-border"
|
||||||
|
disabled={!canBack()}
|
||||||
|
onClick={back}
|
||||||
|
aria-label={language.t("common.goBack")}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={2000}>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
icon="chevron-right"
|
||||||
|
class="titlebar-icon w-6 h-6 p-0 box-border"
|
||||||
|
disabled={!canForward()}
|
||||||
|
onClick={forward}
|
||||||
|
aria-label={language.t("common.goForward")}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
|
<div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(na
|
|||||||
const PALETTE_ID = "command.palette"
|
const PALETTE_ID = "command.palette"
|
||||||
const DEFAULT_PALETTE_KEYBIND = "mod+shift+p"
|
const DEFAULT_PALETTE_KEYBIND = "mod+shift+p"
|
||||||
const SUGGESTED_PREFIX = "suggested."
|
const SUGGESTED_PREFIX = "suggested."
|
||||||
const EDITABLE_KEYBIND_IDS = new Set(["terminal.toggle", "terminal.new"])
|
const EDITABLE_KEYBIND_IDS = new Set(["terminal.toggle", "terminal.new", "file.attach"])
|
||||||
|
|
||||||
function actionId(id: string) {
|
function actionId(id: string) {
|
||||||
if (!id.startsWith(SUGGESTED_PREFIX)) return id
|
if (!id.startsWith(SUGGESTED_PREFIX)) return id
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
type Project,
|
type Project,
|
||||||
type ProviderAuthResponse,
|
type ProviderAuthResponse,
|
||||||
type ProviderListResponse,
|
type ProviderListResponse,
|
||||||
|
type Todo,
|
||||||
createOpencodeClient,
|
createOpencodeClient,
|
||||||
} from "@opencode-ai/sdk/v2/client"
|
} from "@opencode-ai/sdk/v2/client"
|
||||||
import { createStore, produce, reconcile } from "solid-js/store"
|
import { createStore, produce, reconcile } from "solid-js/store"
|
||||||
@@ -41,6 +42,9 @@ type GlobalStore = {
|
|||||||
error?: InitError
|
error?: InitError
|
||||||
path: Path
|
path: Path
|
||||||
project: Project[]
|
project: Project[]
|
||||||
|
session_todo: {
|
||||||
|
[sessionID: string]: Todo[]
|
||||||
|
}
|
||||||
provider: ProviderListResponse
|
provider: ProviderListResponse
|
||||||
provider_auth: ProviderAuthResponse
|
provider_auth: ProviderAuthResponse
|
||||||
config: Config
|
config: Config
|
||||||
@@ -87,12 +91,27 @@ function createGlobalSync() {
|
|||||||
ready: false,
|
ready: false,
|
||||||
path: { state: "", config: "", worktree: "", directory: "", home: "" },
|
path: { state: "", config: "", worktree: "", directory: "", home: "" },
|
||||||
project: projectCache.value,
|
project: projectCache.value,
|
||||||
|
session_todo: {},
|
||||||
provider: { all: [], connected: [], default: {} },
|
provider: { all: [], connected: [], default: {} },
|
||||||
provider_auth: {},
|
provider_auth: {},
|
||||||
config: {},
|
config: {},
|
||||||
reload: undefined,
|
reload: undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const setSessionTodo = (sessionID: string, todos: Todo[] | undefined) => {
|
||||||
|
if (!sessionID) return
|
||||||
|
if (!todos) {
|
||||||
|
setGlobalStore(
|
||||||
|
"session_todo",
|
||||||
|
produce((draft) => {
|
||||||
|
delete draft[sessionID]
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setGlobalStore("session_todo", sessionID, reconcile(todos, { key: "id" }))
|
||||||
|
}
|
||||||
|
|
||||||
const updateStats = (activeDirectoryStores: number) => {
|
const updateStats = (activeDirectoryStores: number) => {
|
||||||
if (!import.meta.env.DEV) return
|
if (!import.meta.env.DEV) return
|
||||||
setDevStats({
|
setDevStats({
|
||||||
@@ -288,6 +307,7 @@ function createGlobalSync() {
|
|||||||
store,
|
store,
|
||||||
setStore,
|
setStore,
|
||||||
push: queue.push,
|
push: queue.push,
|
||||||
|
setSessionTodo,
|
||||||
vcsCache: children.vcsCache.get(directory),
|
vcsCache: children.vcsCache.get(directory),
|
||||||
loadLsp: () => {
|
loadLsp: () => {
|
||||||
sdkFor(directory)
|
sdkFor(directory)
|
||||||
@@ -358,6 +378,9 @@ function createGlobalSync() {
|
|||||||
bootstrap,
|
bootstrap,
|
||||||
updateConfig,
|
updateConfig,
|
||||||
project: projectApi,
|
project: projectApi,
|
||||||
|
todo: {
|
||||||
|
set: setSessionTodo,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
type ProviderAuthResponse,
|
type ProviderAuthResponse,
|
||||||
type ProviderListResponse,
|
type ProviderListResponse,
|
||||||
type QuestionRequest,
|
type QuestionRequest,
|
||||||
|
type Todo,
|
||||||
createOpencodeClient,
|
createOpencodeClient,
|
||||||
} from "@opencode-ai/sdk/v2/client"
|
} from "@opencode-ai/sdk/v2/client"
|
||||||
import { batch } from "solid-js"
|
import { batch } from "solid-js"
|
||||||
@@ -20,6 +21,9 @@ type GlobalStore = {
|
|||||||
ready: boolean
|
ready: boolean
|
||||||
path: Path
|
path: Path
|
||||||
project: Project[]
|
project: Project[]
|
||||||
|
session_todo: {
|
||||||
|
[sessionID: string]: Todo[]
|
||||||
|
}
|
||||||
provider: ProviderListResponse
|
provider: ProviderListResponse
|
||||||
provider_auth: ProviderAuthResponse
|
provider_auth: ProviderAuthResponse
|
||||||
config: Config
|
config: Config
|
||||||
|
|||||||
@@ -39,7 +39,12 @@ export function applyGlobalEvent(input: {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanupSessionCaches(store: Store<State>, setStore: SetStoreFunction<State>, sessionID: string) {
|
function cleanupSessionCaches(
|
||||||
|
store: Store<State>,
|
||||||
|
setStore: SetStoreFunction<State>,
|
||||||
|
sessionID: string,
|
||||||
|
setSessionTodo?: (sessionID: string, todos: Todo[] | undefined) => void,
|
||||||
|
) {
|
||||||
if (!sessionID) return
|
if (!sessionID) return
|
||||||
const hasAny =
|
const hasAny =
|
||||||
store.message[sessionID] !== undefined ||
|
store.message[sessionID] !== undefined ||
|
||||||
@@ -48,6 +53,7 @@ function cleanupSessionCaches(store: Store<State>, setStore: SetStoreFunction<St
|
|||||||
store.permission[sessionID] !== undefined ||
|
store.permission[sessionID] !== undefined ||
|
||||||
store.question[sessionID] !== undefined ||
|
store.question[sessionID] !== undefined ||
|
||||||
store.session_status[sessionID] !== undefined
|
store.session_status[sessionID] !== undefined
|
||||||
|
setSessionTodo?.(sessionID, undefined)
|
||||||
if (!hasAny) return
|
if (!hasAny) return
|
||||||
setStore(
|
setStore(
|
||||||
produce((draft) => {
|
produce((draft) => {
|
||||||
@@ -77,6 +83,7 @@ export function applyDirectoryEvent(input: {
|
|||||||
directory: string
|
directory: string
|
||||||
loadLsp: () => void
|
loadLsp: () => void
|
||||||
vcsCache?: VcsCache
|
vcsCache?: VcsCache
|
||||||
|
setSessionTodo?: (sessionID: string, todos: Todo[] | undefined) => void
|
||||||
}) {
|
}) {
|
||||||
const event = input.event
|
const event = input.event
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
@@ -110,7 +117,7 @@ export function applyDirectoryEvent(input: {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
cleanupSessionCaches(input.store, input.setStore, info.id)
|
cleanupSessionCaches(input.store, input.setStore, info.id, input.setSessionTodo)
|
||||||
if (info.parentID) break
|
if (info.parentID) break
|
||||||
input.setStore("sessionTotal", (value) => Math.max(0, value - 1))
|
input.setStore("sessionTotal", (value) => Math.max(0, value - 1))
|
||||||
break
|
break
|
||||||
@@ -136,7 +143,7 @@ export function applyDirectoryEvent(input: {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
cleanupSessionCaches(input.store, input.setStore, info.id)
|
cleanupSessionCaches(input.store, input.setStore, info.id, input.setSessionTodo)
|
||||||
if (info.parentID) break
|
if (info.parentID) break
|
||||||
input.setStore("sessionTotal", (value) => Math.max(0, value - 1))
|
input.setStore("sessionTotal", (value) => Math.max(0, value - 1))
|
||||||
break
|
break
|
||||||
@@ -149,6 +156,7 @@ export function applyDirectoryEvent(input: {
|
|||||||
case "todo.updated": {
|
case "todo.updated": {
|
||||||
const props = event.properties as { sessionID: string; todos: Todo[] }
|
const props = event.properties as { sessionID: string; todos: Todo[] }
|
||||||
input.setStore("todo", props.sessionID, reconcile(props.todos, { key: "id" }))
|
input.setStore("todo", props.sessionID, reconcile(props.todos, { key: "id" }))
|
||||||
|
input.setSessionTodo?.(props.sessionID, props.todos)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case "session.status": {
|
case "session.status": {
|
||||||
|
|||||||
@@ -289,12 +289,25 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||||||
const directory = sdk.directory
|
const directory = sdk.directory
|
||||||
const client = sdk.client
|
const client = sdk.client
|
||||||
const [store, setStore] = globalSync.child(directory)
|
const [store, setStore] = globalSync.child(directory)
|
||||||
if (store.todo[sessionID] !== undefined) return
|
const existing = store.todo[sessionID]
|
||||||
|
if (existing !== undefined) {
|
||||||
|
if (globalSync.data.session_todo[sessionID] === undefined) {
|
||||||
|
globalSync.todo.set(sessionID, existing)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const cached = globalSync.data.session_todo[sessionID]
|
||||||
|
if (cached !== undefined) {
|
||||||
|
setStore("todo", sessionID, reconcile(cached, { key: "id" }))
|
||||||
|
}
|
||||||
|
|
||||||
const key = keyFor(directory, sessionID)
|
const key = keyFor(directory, sessionID)
|
||||||
return runInflight(inflightTodo, key, () =>
|
return runInflight(inflightTodo, key, () =>
|
||||||
retry(() => client.session.todo({ sessionID })).then((todo) => {
|
retry(() => client.session.todo({ sessionID })).then((todo) => {
|
||||||
setStore("todo", sessionID, reconcile(todo.data ?? [], { key: "id" }))
|
const list = todo.data ?? []
|
||||||
|
setStore("todo", sessionID, reconcile(list, { key: "id" }))
|
||||||
|
globalSync.todo.set(sessionID, list)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -206,6 +206,7 @@ export const dict = {
|
|||||||
"common.attachment": "مرفق",
|
"common.attachment": "مرفق",
|
||||||
"prompt.placeholder.shell": "أدخل أمر shell...",
|
"prompt.placeholder.shell": "أدخل أمر shell...",
|
||||||
"prompt.placeholder.normal": 'اسأل أي شيء... "{{example}}"',
|
"prompt.placeholder.normal": 'اسأل أي شيء... "{{example}}"',
|
||||||
|
"prompt.placeholder.simple": "اسأل أي شيء...",
|
||||||
"prompt.placeholder.summarizeComments": "لخّص التعليقات…",
|
"prompt.placeholder.summarizeComments": "لخّص التعليقات…",
|
||||||
"prompt.placeholder.summarizeComment": "لخّص التعليق…",
|
"prompt.placeholder.summarizeComment": "لخّص التعليق…",
|
||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
@@ -447,6 +448,9 @@ export const dict = {
|
|||||||
"session.messages.loading": "جارٍ تحميل الرسائل...",
|
"session.messages.loading": "جارٍ تحميل الرسائل...",
|
||||||
"session.messages.jumpToLatest": "الانتقال إلى الأحدث",
|
"session.messages.jumpToLatest": "الانتقال إلى الأحدث",
|
||||||
"session.context.addToContext": "إضافة {{selection}} إلى السياق",
|
"session.context.addToContext": "إضافة {{selection}} إلى السياق",
|
||||||
|
"session.todo.title": "المهام",
|
||||||
|
"session.todo.collapse": "طي",
|
||||||
|
"session.todo.expand": "توسيع",
|
||||||
"session.new.worktree.main": "الفرع الرئيسي",
|
"session.new.worktree.main": "الفرع الرئيسي",
|
||||||
"session.new.worktree.mainWithBranch": "الفرع الرئيسي ({{branch}})",
|
"session.new.worktree.mainWithBranch": "الفرع الرئيسي ({{branch}})",
|
||||||
"session.new.worktree.create": "إنشاء شجرة عمل جديدة",
|
"session.new.worktree.create": "إنشاء شجرة عمل جديدة",
|
||||||
|
|||||||
@@ -206,6 +206,7 @@ export const dict = {
|
|||||||
"common.attachment": "anexo",
|
"common.attachment": "anexo",
|
||||||
"prompt.placeholder.shell": "Digite comando do shell...",
|
"prompt.placeholder.shell": "Digite comando do shell...",
|
||||||
"prompt.placeholder.normal": 'Pergunte qualquer coisa... "{{example}}"',
|
"prompt.placeholder.normal": 'Pergunte qualquer coisa... "{{example}}"',
|
||||||
|
"prompt.placeholder.simple": "Pergunte qualquer coisa...",
|
||||||
"prompt.placeholder.summarizeComments": "Resumir comentários…",
|
"prompt.placeholder.summarizeComments": "Resumir comentários…",
|
||||||
"prompt.placeholder.summarizeComment": "Resumir comentário…",
|
"prompt.placeholder.summarizeComment": "Resumir comentário…",
|
||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
@@ -450,6 +451,9 @@ export const dict = {
|
|||||||
"session.messages.loading": "Carregando mensagens...",
|
"session.messages.loading": "Carregando mensagens...",
|
||||||
"session.messages.jumpToLatest": "Ir para a mais recente",
|
"session.messages.jumpToLatest": "Ir para a mais recente",
|
||||||
"session.context.addToContext": "Adicionar {{selection}} ao contexto",
|
"session.context.addToContext": "Adicionar {{selection}} ao contexto",
|
||||||
|
"session.todo.title": "Tarefas",
|
||||||
|
"session.todo.collapse": "Recolher",
|
||||||
|
"session.todo.expand": "Expandir",
|
||||||
"session.new.worktree.main": "Branch principal",
|
"session.new.worktree.main": "Branch principal",
|
||||||
"session.new.worktree.mainWithBranch": "Branch principal ({{branch}})",
|
"session.new.worktree.mainWithBranch": "Branch principal ({{branch}})",
|
||||||
"session.new.worktree.create": "Criar novo worktree",
|
"session.new.worktree.create": "Criar novo worktree",
|
||||||
|
|||||||
@@ -224,6 +224,7 @@ export const dict = {
|
|||||||
|
|
||||||
"prompt.placeholder.shell": "Unesi shell naredbu...",
|
"prompt.placeholder.shell": "Unesi shell naredbu...",
|
||||||
"prompt.placeholder.normal": 'Pitaj bilo šta... "{{example}}"',
|
"prompt.placeholder.normal": 'Pitaj bilo šta... "{{example}}"',
|
||||||
|
"prompt.placeholder.simple": "Pitaj bilo šta...",
|
||||||
"prompt.placeholder.summarizeComments": "Sažmi komentare…",
|
"prompt.placeholder.summarizeComments": "Sažmi komentare…",
|
||||||
"prompt.placeholder.summarizeComment": "Sažmi komentar…",
|
"prompt.placeholder.summarizeComment": "Sažmi komentar…",
|
||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
@@ -505,6 +506,9 @@ export const dict = {
|
|||||||
"session.messages.jumpToLatest": "Idi na najnovije",
|
"session.messages.jumpToLatest": "Idi na najnovije",
|
||||||
|
|
||||||
"session.context.addToContext": "Dodaj {{selection}} u kontekst",
|
"session.context.addToContext": "Dodaj {{selection}} u kontekst",
|
||||||
|
"session.todo.title": "Zadaci",
|
||||||
|
"session.todo.collapse": "Sažmi",
|
||||||
|
"session.todo.expand": "Proširi",
|
||||||
|
|
||||||
"session.new.worktree.main": "Glavna grana",
|
"session.new.worktree.main": "Glavna grana",
|
||||||
"session.new.worktree.mainWithBranch": "Glavna grana ({{branch}})",
|
"session.new.worktree.mainWithBranch": "Glavna grana ({{branch}})",
|
||||||
|
|||||||
@@ -222,6 +222,7 @@ export const dict = {
|
|||||||
|
|
||||||
"prompt.placeholder.shell": "Indtast shell-kommando...",
|
"prompt.placeholder.shell": "Indtast shell-kommando...",
|
||||||
"prompt.placeholder.normal": 'Spørg om hvad som helst... "{{example}}"',
|
"prompt.placeholder.normal": 'Spørg om hvad som helst... "{{example}}"',
|
||||||
|
"prompt.placeholder.simple": "Spørg om hvad som helst...",
|
||||||
"prompt.placeholder.summarizeComments": "Opsummér kommentarer…",
|
"prompt.placeholder.summarizeComments": "Opsummér kommentarer…",
|
||||||
"prompt.placeholder.summarizeComment": "Opsummér kommentar…",
|
"prompt.placeholder.summarizeComment": "Opsummér kommentar…",
|
||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
@@ -500,6 +501,9 @@ export const dict = {
|
|||||||
|
|
||||||
"session.messages.jumpToLatest": "Gå til seneste",
|
"session.messages.jumpToLatest": "Gå til seneste",
|
||||||
"session.context.addToContext": "Tilføj {{selection}} til kontekst",
|
"session.context.addToContext": "Tilføj {{selection}} til kontekst",
|
||||||
|
"session.todo.title": "Opgaver",
|
||||||
|
"session.todo.collapse": "Skjul",
|
||||||
|
"session.todo.expand": "Udvid",
|
||||||
|
|
||||||
"session.new.worktree.main": "Hovedgren",
|
"session.new.worktree.main": "Hovedgren",
|
||||||
"session.new.worktree.mainWithBranch": "Hovedgren ({{branch}})",
|
"session.new.worktree.mainWithBranch": "Hovedgren ({{branch}})",
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ export const dict = {
|
|||||||
"common.attachment": "Anhang",
|
"common.attachment": "Anhang",
|
||||||
"prompt.placeholder.shell": "Shell-Befehl eingeben...",
|
"prompt.placeholder.shell": "Shell-Befehl eingeben...",
|
||||||
"prompt.placeholder.normal": 'Fragen Sie alles... "{{example}}"',
|
"prompt.placeholder.normal": 'Fragen Sie alles... "{{example}}"',
|
||||||
|
"prompt.placeholder.simple": "Fragen Sie alles...",
|
||||||
"prompt.placeholder.summarizeComments": "Kommentare zusammenfassen…",
|
"prompt.placeholder.summarizeComments": "Kommentare zusammenfassen…",
|
||||||
"prompt.placeholder.summarizeComment": "Kommentar zusammenfassen…",
|
"prompt.placeholder.summarizeComment": "Kommentar zusammenfassen…",
|
||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
@@ -458,6 +459,9 @@ export const dict = {
|
|||||||
"session.messages.loading": "Lade Nachrichten...",
|
"session.messages.loading": "Lade Nachrichten...",
|
||||||
"session.messages.jumpToLatest": "Zum neuesten springen",
|
"session.messages.jumpToLatest": "Zum neuesten springen",
|
||||||
"session.context.addToContext": "{{selection}} zum Kontext hinzufügen",
|
"session.context.addToContext": "{{selection}} zum Kontext hinzufügen",
|
||||||
|
"session.todo.title": "Aufgaben",
|
||||||
|
"session.todo.collapse": "Einklappen",
|
||||||
|
"session.todo.expand": "Ausklappen",
|
||||||
"session.new.worktree.main": "Haupt-Branch",
|
"session.new.worktree.main": "Haupt-Branch",
|
||||||
"session.new.worktree.mainWithBranch": "Haupt-Branch ({{branch}})",
|
"session.new.worktree.mainWithBranch": "Haupt-Branch ({{branch}})",
|
||||||
"session.new.worktree.create": "Neuen Worktree erstellen",
|
"session.new.worktree.create": "Neuen Worktree erstellen",
|
||||||
|
|||||||
@@ -224,6 +224,7 @@ export const dict = {
|
|||||||
|
|
||||||
"prompt.placeholder.shell": "Enter shell command...",
|
"prompt.placeholder.shell": "Enter shell command...",
|
||||||
"prompt.placeholder.normal": 'Ask anything... "{{example}}"',
|
"prompt.placeholder.normal": 'Ask anything... "{{example}}"',
|
||||||
|
"prompt.placeholder.simple": "Ask anything...",
|
||||||
"prompt.placeholder.summarizeComments": "Summarize comments…",
|
"prompt.placeholder.summarizeComments": "Summarize comments…",
|
||||||
"prompt.placeholder.summarizeComment": "Summarize comment…",
|
"prompt.placeholder.summarizeComment": "Summarize comment…",
|
||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
@@ -266,7 +267,7 @@ export const dict = {
|
|||||||
"prompt.context.includeActiveFile": "Include active file",
|
"prompt.context.includeActiveFile": "Include active file",
|
||||||
"prompt.context.removeActiveFile": "Remove active file from context",
|
"prompt.context.removeActiveFile": "Remove active file from context",
|
||||||
"prompt.context.removeFile": "Remove file from context",
|
"prompt.context.removeFile": "Remove file from context",
|
||||||
"prompt.action.attachFile": "Attach file",
|
"prompt.action.attachFile": "Add file",
|
||||||
"prompt.attachment.remove": "Remove attachment",
|
"prompt.attachment.remove": "Remove attachment",
|
||||||
"prompt.action.send": "Send",
|
"prompt.action.send": "Send",
|
||||||
"prompt.action.stop": "Stop",
|
"prompt.action.stop": "Stop",
|
||||||
@@ -504,6 +505,9 @@ export const dict = {
|
|||||||
"session.messages.jumpToLatest": "Jump to latest",
|
"session.messages.jumpToLatest": "Jump to latest",
|
||||||
|
|
||||||
"session.context.addToContext": "Add {{selection}} to context",
|
"session.context.addToContext": "Add {{selection}} to context",
|
||||||
|
"session.todo.title": "Todos",
|
||||||
|
"session.todo.collapse": "Collapse",
|
||||||
|
"session.todo.expand": "Expand",
|
||||||
|
|
||||||
"session.new.worktree.main": "Main branch",
|
"session.new.worktree.main": "Main branch",
|
||||||
"session.new.worktree.mainWithBranch": "Main branch ({{branch}})",
|
"session.new.worktree.mainWithBranch": "Main branch ({{branch}})",
|
||||||
|
|||||||
@@ -223,6 +223,7 @@ export const dict = {
|
|||||||
|
|
||||||
"prompt.placeholder.shell": "Introduce comando de shell...",
|
"prompt.placeholder.shell": "Introduce comando de shell...",
|
||||||
"prompt.placeholder.normal": 'Pregunta cualquier cosa... "{{example}}"',
|
"prompt.placeholder.normal": 'Pregunta cualquier cosa... "{{example}}"',
|
||||||
|
"prompt.placeholder.simple": "Pregunta cualquier cosa...",
|
||||||
"prompt.placeholder.summarizeComments": "Resumir comentarios…",
|
"prompt.placeholder.summarizeComments": "Resumir comentarios…",
|
||||||
"prompt.placeholder.summarizeComment": "Resumir comentario…",
|
"prompt.placeholder.summarizeComment": "Resumir comentario…",
|
||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
@@ -506,6 +507,9 @@ export const dict = {
|
|||||||
"session.messages.jumpToLatest": "Ir al último",
|
"session.messages.jumpToLatest": "Ir al último",
|
||||||
|
|
||||||
"session.context.addToContext": "Añadir {{selection}} al contexto",
|
"session.context.addToContext": "Añadir {{selection}} al contexto",
|
||||||
|
"session.todo.title": "Tareas",
|
||||||
|
"session.todo.collapse": "Contraer",
|
||||||
|
"session.todo.expand": "Expandir",
|
||||||
|
|
||||||
"session.new.worktree.main": "Rama principal",
|
"session.new.worktree.main": "Rama principal",
|
||||||
"session.new.worktree.mainWithBranch": "Rama principal ({{branch}})",
|
"session.new.worktree.mainWithBranch": "Rama principal ({{branch}})",
|
||||||
|
|||||||
@@ -206,6 +206,7 @@ export const dict = {
|
|||||||
"common.attachment": "pièce jointe",
|
"common.attachment": "pièce jointe",
|
||||||
"prompt.placeholder.shell": "Entrez une commande shell...",
|
"prompt.placeholder.shell": "Entrez une commande shell...",
|
||||||
"prompt.placeholder.normal": 'Demandez n\'importe quoi... "{{example}}"',
|
"prompt.placeholder.normal": 'Demandez n\'importe quoi... "{{example}}"',
|
||||||
|
"prompt.placeholder.simple": "Demandez n'importe quoi...",
|
||||||
"prompt.placeholder.summarizeComments": "Résumer les commentaires…",
|
"prompt.placeholder.summarizeComments": "Résumer les commentaires…",
|
||||||
"prompt.placeholder.summarizeComment": "Résumer le commentaire…",
|
"prompt.placeholder.summarizeComment": "Résumer le commentaire…",
|
||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
@@ -456,6 +457,9 @@ export const dict = {
|
|||||||
"session.messages.loading": "Chargement des messages...",
|
"session.messages.loading": "Chargement des messages...",
|
||||||
"session.messages.jumpToLatest": "Aller au dernier",
|
"session.messages.jumpToLatest": "Aller au dernier",
|
||||||
"session.context.addToContext": "Ajouter {{selection}} au contexte",
|
"session.context.addToContext": "Ajouter {{selection}} au contexte",
|
||||||
|
"session.todo.title": "Tâches",
|
||||||
|
"session.todo.collapse": "Réduire",
|
||||||
|
"session.todo.expand": "Développer",
|
||||||
"session.new.worktree.main": "Branche principale",
|
"session.new.worktree.main": "Branche principale",
|
||||||
"session.new.worktree.mainWithBranch": "Branche principale ({{branch}})",
|
"session.new.worktree.mainWithBranch": "Branche principale ({{branch}})",
|
||||||
"session.new.worktree.create": "Créer un nouvel arbre de travail",
|
"session.new.worktree.create": "Créer un nouvel arbre de travail",
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ export const dict = {
|
|||||||
"common.attachment": "添付ファイル",
|
"common.attachment": "添付ファイル",
|
||||||
"prompt.placeholder.shell": "シェルコマンドを入力...",
|
"prompt.placeholder.shell": "シェルコマンドを入力...",
|
||||||
"prompt.placeholder.normal": '何でも聞いてください... "{{example}}"',
|
"prompt.placeholder.normal": '何でも聞いてください... "{{example}}"',
|
||||||
|
"prompt.placeholder.simple": "何でも聞いてください...",
|
||||||
"prompt.placeholder.summarizeComments": "コメントを要約…",
|
"prompt.placeholder.summarizeComments": "コメントを要約…",
|
||||||
"prompt.placeholder.summarizeComment": "コメントを要約…",
|
"prompt.placeholder.summarizeComment": "コメントを要約…",
|
||||||
"prompt.mode.shell": "シェル",
|
"prompt.mode.shell": "シェル",
|
||||||
@@ -448,6 +449,9 @@ export const dict = {
|
|||||||
"session.messages.loading": "メッセージを読み込み中...",
|
"session.messages.loading": "メッセージを読み込み中...",
|
||||||
"session.messages.jumpToLatest": "最新へジャンプ",
|
"session.messages.jumpToLatest": "最新へジャンプ",
|
||||||
"session.context.addToContext": "{{selection}}をコンテキストに追加",
|
"session.context.addToContext": "{{selection}}をコンテキストに追加",
|
||||||
|
"session.todo.title": "ToDo",
|
||||||
|
"session.todo.collapse": "折りたたむ",
|
||||||
|
"session.todo.expand": "展開",
|
||||||
"session.new.worktree.main": "メインブランチ",
|
"session.new.worktree.main": "メインブランチ",
|
||||||
"session.new.worktree.mainWithBranch": "メインブランチ ({{branch}})",
|
"session.new.worktree.mainWithBranch": "メインブランチ ({{branch}})",
|
||||||
"session.new.worktree.create": "新しいワークツリーを作成",
|
"session.new.worktree.create": "新しいワークツリーを作成",
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ export const dict = {
|
|||||||
"common.attachment": "첨부 파일",
|
"common.attachment": "첨부 파일",
|
||||||
"prompt.placeholder.shell": "셸 명령어 입력...",
|
"prompt.placeholder.shell": "셸 명령어 입력...",
|
||||||
"prompt.placeholder.normal": '무엇이든 물어보세요... "{{example}}"',
|
"prompt.placeholder.normal": '무엇이든 물어보세요... "{{example}}"',
|
||||||
|
"prompt.placeholder.simple": "무엇이든 물어보세요...",
|
||||||
"prompt.placeholder.summarizeComments": "댓글 요약…",
|
"prompt.placeholder.summarizeComments": "댓글 요약…",
|
||||||
"prompt.placeholder.summarizeComment": "댓글 요약…",
|
"prompt.placeholder.summarizeComment": "댓글 요약…",
|
||||||
"prompt.mode.shell": "셸",
|
"prompt.mode.shell": "셸",
|
||||||
@@ -450,6 +451,9 @@ export const dict = {
|
|||||||
"session.messages.loading": "메시지 로드 중...",
|
"session.messages.loading": "메시지 로드 중...",
|
||||||
"session.messages.jumpToLatest": "최신으로 이동",
|
"session.messages.jumpToLatest": "최신으로 이동",
|
||||||
"session.context.addToContext": "컨텍스트에 {{selection}} 추가",
|
"session.context.addToContext": "컨텍스트에 {{selection}} 추가",
|
||||||
|
"session.todo.title": "할 일",
|
||||||
|
"session.todo.collapse": "접기",
|
||||||
|
"session.todo.expand": "펼치기",
|
||||||
"session.new.worktree.main": "메인 브랜치",
|
"session.new.worktree.main": "메인 브랜치",
|
||||||
"session.new.worktree.mainWithBranch": "메인 브랜치 ({{branch}})",
|
"session.new.worktree.mainWithBranch": "메인 브랜치 ({{branch}})",
|
||||||
"session.new.worktree.create": "새 작업 트리 생성",
|
"session.new.worktree.create": "새 작업 트리 생성",
|
||||||
|
|||||||
@@ -226,6 +226,7 @@ export const dict = {
|
|||||||
|
|
||||||
"prompt.placeholder.shell": "Skriv inn shell-kommando...",
|
"prompt.placeholder.shell": "Skriv inn shell-kommando...",
|
||||||
"prompt.placeholder.normal": 'Spør om hva som helst... "{{example}}"',
|
"prompt.placeholder.normal": 'Spør om hva som helst... "{{example}}"',
|
||||||
|
"prompt.placeholder.simple": "Spør om hva som helst...",
|
||||||
"prompt.placeholder.summarizeComments": "Oppsummer kommentarer…",
|
"prompt.placeholder.summarizeComments": "Oppsummer kommentarer…",
|
||||||
"prompt.placeholder.summarizeComment": "Oppsummer kommentar…",
|
"prompt.placeholder.summarizeComment": "Oppsummer kommentar…",
|
||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
@@ -506,6 +507,9 @@ export const dict = {
|
|||||||
"session.messages.jumpToLatest": "Hopp til nyeste",
|
"session.messages.jumpToLatest": "Hopp til nyeste",
|
||||||
|
|
||||||
"session.context.addToContext": "Legg til {{selection}} i kontekst",
|
"session.context.addToContext": "Legg til {{selection}} i kontekst",
|
||||||
|
"session.todo.title": "Oppgaver",
|
||||||
|
"session.todo.collapse": "Skjul",
|
||||||
|
"session.todo.expand": "Utvid",
|
||||||
|
|
||||||
"session.new.worktree.main": "Hovedgren",
|
"session.new.worktree.main": "Hovedgren",
|
||||||
"session.new.worktree.mainWithBranch": "Hovedgren ({{branch}})",
|
"session.new.worktree.mainWithBranch": "Hovedgren ({{branch}})",
|
||||||
|
|||||||
@@ -207,6 +207,7 @@ export const dict = {
|
|||||||
"common.attachment": "załącznik",
|
"common.attachment": "załącznik",
|
||||||
"prompt.placeholder.shell": "Wpisz polecenie terminala...",
|
"prompt.placeholder.shell": "Wpisz polecenie terminala...",
|
||||||
"prompt.placeholder.normal": 'Zapytaj o cokolwiek... "{{example}}"',
|
"prompt.placeholder.normal": 'Zapytaj o cokolwiek... "{{example}}"',
|
||||||
|
"prompt.placeholder.simple": "Zapytaj o cokolwiek...",
|
||||||
"prompt.placeholder.summarizeComments": "Podsumuj komentarze…",
|
"prompt.placeholder.summarizeComments": "Podsumuj komentarze…",
|
||||||
"prompt.placeholder.summarizeComment": "Podsumuj komentarz…",
|
"prompt.placeholder.summarizeComment": "Podsumuj komentarz…",
|
||||||
"prompt.mode.shell": "Terminal",
|
"prompt.mode.shell": "Terminal",
|
||||||
@@ -449,6 +450,9 @@ export const dict = {
|
|||||||
"session.messages.loading": "Ładowanie wiadomości...",
|
"session.messages.loading": "Ładowanie wiadomości...",
|
||||||
"session.messages.jumpToLatest": "Przejdź do najnowszych",
|
"session.messages.jumpToLatest": "Przejdź do najnowszych",
|
||||||
"session.context.addToContext": "Dodaj {{selection}} do kontekstu",
|
"session.context.addToContext": "Dodaj {{selection}} do kontekstu",
|
||||||
|
"session.todo.title": "Zadania",
|
||||||
|
"session.todo.collapse": "Zwiń",
|
||||||
|
"session.todo.expand": "Rozwiń",
|
||||||
"session.new.worktree.main": "Główna gałąź",
|
"session.new.worktree.main": "Główna gałąź",
|
||||||
"session.new.worktree.mainWithBranch": "Główna gałąź ({{branch}})",
|
"session.new.worktree.mainWithBranch": "Główna gałąź ({{branch}})",
|
||||||
"session.new.worktree.create": "Utwórz nowe drzewo robocze",
|
"session.new.worktree.create": "Utwórz nowe drzewo robocze",
|
||||||
|
|||||||
@@ -223,6 +223,7 @@ export const dict = {
|
|||||||
|
|
||||||
"prompt.placeholder.shell": "Введите команду оболочки...",
|
"prompt.placeholder.shell": "Введите команду оболочки...",
|
||||||
"prompt.placeholder.normal": 'Спросите что угодно... "{{example}}"',
|
"prompt.placeholder.normal": 'Спросите что угодно... "{{example}}"',
|
||||||
|
"prompt.placeholder.simple": "Спросите что угодно...",
|
||||||
"prompt.placeholder.summarizeComments": "Суммировать комментарии…",
|
"prompt.placeholder.summarizeComments": "Суммировать комментарии…",
|
||||||
"prompt.placeholder.summarizeComment": "Суммировать комментарий…",
|
"prompt.placeholder.summarizeComment": "Суммировать комментарий…",
|
||||||
"prompt.mode.shell": "Оболочка",
|
"prompt.mode.shell": "Оболочка",
|
||||||
@@ -504,6 +505,9 @@ export const dict = {
|
|||||||
"session.messages.jumpToLatest": "Перейти к последнему",
|
"session.messages.jumpToLatest": "Перейти к последнему",
|
||||||
|
|
||||||
"session.context.addToContext": "Добавить {{selection}} в контекст",
|
"session.context.addToContext": "Добавить {{selection}} в контекст",
|
||||||
|
"session.todo.title": "Задачи",
|
||||||
|
"session.todo.collapse": "Свернуть",
|
||||||
|
"session.todo.expand": "Развернуть",
|
||||||
|
|
||||||
"session.new.worktree.main": "Основная ветка",
|
"session.new.worktree.main": "Основная ветка",
|
||||||
"session.new.worktree.mainWithBranch": "Основная ветка ({{branch}})",
|
"session.new.worktree.mainWithBranch": "Основная ветка ({{branch}})",
|
||||||
|
|||||||
@@ -223,6 +223,7 @@ export const dict = {
|
|||||||
|
|
||||||
"prompt.placeholder.shell": "ป้อนคำสั่งเชลล์...",
|
"prompt.placeholder.shell": "ป้อนคำสั่งเชลล์...",
|
||||||
"prompt.placeholder.normal": 'ถามอะไรก็ได้... "{{example}}"',
|
"prompt.placeholder.normal": 'ถามอะไรก็ได้... "{{example}}"',
|
||||||
|
"prompt.placeholder.simple": "ถามอะไรก็ได้...",
|
||||||
"prompt.placeholder.summarizeComments": "สรุปความคิดเห็น…",
|
"prompt.placeholder.summarizeComments": "สรุปความคิดเห็น…",
|
||||||
"prompt.placeholder.summarizeComment": "สรุปความคิดเห็น…",
|
"prompt.placeholder.summarizeComment": "สรุปความคิดเห็น…",
|
||||||
"prompt.mode.shell": "เชลล์",
|
"prompt.mode.shell": "เชลล์",
|
||||||
@@ -501,6 +502,9 @@ export const dict = {
|
|||||||
"session.messages.jumpToLatest": "ไปที่ล่าสุด",
|
"session.messages.jumpToLatest": "ไปที่ล่าสุด",
|
||||||
|
|
||||||
"session.context.addToContext": "เพิ่ม {{selection}} ไปยังบริบท",
|
"session.context.addToContext": "เพิ่ม {{selection}} ไปยังบริบท",
|
||||||
|
"session.todo.title": "สิ่งที่ต้องทำ",
|
||||||
|
"session.todo.collapse": "ย่อ",
|
||||||
|
"session.todo.expand": "ขยาย",
|
||||||
|
|
||||||
"session.new.worktree.main": "สาขาหลัก",
|
"session.new.worktree.main": "สาขาหลัก",
|
||||||
"session.new.worktree.mainWithBranch": "สาขาหลัก ({{branch}})",
|
"session.new.worktree.mainWithBranch": "สาขาหลัก ({{branch}})",
|
||||||
|
|||||||
@@ -244,6 +244,7 @@ export const dict = {
|
|||||||
|
|
||||||
"prompt.placeholder.shell": "输入 shell 命令...",
|
"prompt.placeholder.shell": "输入 shell 命令...",
|
||||||
"prompt.placeholder.normal": '随便问点什么... "{{example}}"',
|
"prompt.placeholder.normal": '随便问点什么... "{{example}}"',
|
||||||
|
"prompt.placeholder.simple": "随便问点什么...",
|
||||||
"prompt.placeholder.summarizeComments": "总结评论…",
|
"prompt.placeholder.summarizeComments": "总结评论…",
|
||||||
"prompt.placeholder.summarizeComment": "总结该评论…",
|
"prompt.placeholder.summarizeComment": "总结该评论…",
|
||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
@@ -500,6 +501,9 @@ export const dict = {
|
|||||||
"session.messages.loading": "正在加载消息...",
|
"session.messages.loading": "正在加载消息...",
|
||||||
"session.messages.jumpToLatest": "跳转到最新",
|
"session.messages.jumpToLatest": "跳转到最新",
|
||||||
"session.context.addToContext": "将 {{selection}} 添加到上下文",
|
"session.context.addToContext": "将 {{selection}} 添加到上下文",
|
||||||
|
"session.todo.title": "待办事项",
|
||||||
|
"session.todo.collapse": "折叠",
|
||||||
|
"session.todo.expand": "展开",
|
||||||
"session.new.worktree.main": "主分支",
|
"session.new.worktree.main": "主分支",
|
||||||
"session.new.worktree.mainWithBranch": "主分支({{branch}})",
|
"session.new.worktree.mainWithBranch": "主分支({{branch}})",
|
||||||
"session.new.worktree.create": "创建新的 worktree",
|
"session.new.worktree.create": "创建新的 worktree",
|
||||||
|
|||||||
@@ -223,6 +223,7 @@ export const dict = {
|
|||||||
|
|
||||||
"prompt.placeholder.shell": "輸入 shell 命令...",
|
"prompt.placeholder.shell": "輸入 shell 命令...",
|
||||||
"prompt.placeholder.normal": '隨便問點什麼... "{{example}}"',
|
"prompt.placeholder.normal": '隨便問點什麼... "{{example}}"',
|
||||||
|
"prompt.placeholder.simple": "隨便問點什麼...",
|
||||||
"prompt.placeholder.summarizeComments": "摘要評論…",
|
"prompt.placeholder.summarizeComments": "摘要評論…",
|
||||||
"prompt.placeholder.summarizeComment": "摘要這則評論…",
|
"prompt.placeholder.summarizeComment": "摘要這則評論…",
|
||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
@@ -497,6 +498,9 @@ export const dict = {
|
|||||||
|
|
||||||
"session.messages.jumpToLatest": "跳到最新",
|
"session.messages.jumpToLatest": "跳到最新",
|
||||||
"session.context.addToContext": "將 {{selection}} 新增到上下文",
|
"session.context.addToContext": "將 {{selection}} 新增到上下文",
|
||||||
|
"session.todo.title": "待辦事項",
|
||||||
|
"session.todo.collapse": "折疊",
|
||||||
|
"session.todo.expand": "展開",
|
||||||
|
|
||||||
"session.new.worktree.main": "主分支",
|
"session.new.worktree.main": "主分支",
|
||||||
"session.new.worktree.mainWithBranch": "主分支 ({{branch}})",
|
"session.new.worktree.mainWithBranch": "主分支 ({{branch}})",
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ function DirectoryDataProvider(props: ParentProps<{ directory: string }>) {
|
|||||||
onQuestionReject={(input: { requestID: string }) => sdk.client.question.reject(input)}
|
onQuestionReject={(input: { requestID: string }) => sdk.client.question.reject(input)}
|
||||||
onNavigateToSession={(sessionID: string) => navigate(`/${params.dir}/session/${sessionID}`)}
|
onNavigateToSession={(sessionID: string) => navigate(`/${params.dir}/session/${sessionID}`)}
|
||||||
onSessionHref={(sessionID: string) => `/${params.dir}/session/${sessionID}`}
|
onSessionHref={(sessionID: string) => `/${params.dir}/session/${sessionID}`}
|
||||||
onSyncSession={(sessionID: string) => sync.session.sync(sessionID)}
|
|
||||||
>
|
>
|
||||||
<LocalProvider>{props.children}</LocalProvider>
|
<LocalProvider>{props.children}</LocalProvider>
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
|
|||||||
@@ -1710,7 +1710,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
classList={{
|
classList={{
|
||||||
"flex flex-col min-h-0 bg-background-stronger border border-b-0 border-border-weak-base rounded-tl-sm": true,
|
"flex flex-col min-h-0 bg-background-stronger border border-b-0 border-border-weak-base rounded-tl-[12px]": true,
|
||||||
"flex-1 min-w-0": panelProps.mobile,
|
"flex-1 min-w-0": panelProps.mobile,
|
||||||
}}
|
}}
|
||||||
style={{ width: panelProps.mobile ? undefined : `${Math.max(layout.sidebar.width() - 64, 0)}px` }}
|
style={{ width: panelProps.mobile ? undefined : `${Math.max(layout.sidebar.width() - 64, 0)}px` }}
|
||||||
@@ -1725,8 +1725,8 @@ export default function Layout(props: ParentProps) {
|
|||||||
id={`project:${projectId()}`}
|
id={`project:${projectId()}`}
|
||||||
value={projectName}
|
value={projectName}
|
||||||
onSave={(next) => renameProject(p(), next)}
|
onSave={(next) => renameProject(p(), next)}
|
||||||
class="text-16-medium text-text-strong truncate"
|
class="text-14-medium text-text-strong truncate"
|
||||||
displayClass="text-16-medium text-text-strong truncate"
|
displayClass="text-14-medium text-text-strong truncate"
|
||||||
stopPropagation
|
stopPropagation
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -2042,7 +2042,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
<main
|
<main
|
||||||
classList={{
|
classList={{
|
||||||
"size-full overflow-x-hidden flex flex-col items-start contain-strict border-t border-border-weak-base": true,
|
"size-full overflow-x-hidden flex flex-col items-start contain-strict border-t border-border-weak-base": true,
|
||||||
"xl:border-l xl:rounded-tl-sm": !layout.sidebar.opened(),
|
"xl:border-l xl:rounded-tl-[12px]": !layout.sidebar.opened(),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Show when={!autoselecting()} fallback={<div class="size-full" />}>
|
<Show when={!autoselecting()} fallback={<div class="size-full" />}>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export const SidebarContent = (props: {
|
|||||||
>
|
>
|
||||||
<DragDropSensors />
|
<DragDropSensors />
|
||||||
<ConstrainDragXAxis />
|
<ConstrainDragXAxis />
|
||||||
<div class="h-full w-full flex flex-col items-center gap-3 px-3 py-2 overflow-y-auto no-scrollbar">
|
<div class="h-full w-full flex flex-col items-center gap-3 px-3 py-3 overflow-y-auto no-scrollbar">
|
||||||
<SortableProvider ids={props.projects().map((p) => p.worktree)}>
|
<SortableProvider ids={props.projects().map((p) => p.worktree)}>
|
||||||
<For each={props.projects()}>{(project) => props.renderProject(project)}</For>
|
<For each={props.projects()}>{(project) => props.renderProject(project)}</For>
|
||||||
</SortableProvider>
|
</SortableProvider>
|
||||||
@@ -78,7 +78,7 @@ export const SidebarContent = (props: {
|
|||||||
<DragOverlay>{props.renderProjectOverlay()}</DragOverlay>
|
<DragOverlay>{props.renderProjectOverlay()}</DragOverlay>
|
||||||
</DragDropProvider>
|
</DragDropProvider>
|
||||||
</div>
|
</div>
|
||||||
<div class="shrink-0 w-full pt-3 pb-3 flex flex-col items-center gap-2">
|
<div class="shrink-0 w-full pt-3 pb-6 flex flex-col items-center gap-2">
|
||||||
<TooltipKeybind placement={placement()} title={props.settingsLabel()} keybind={props.settingsKeybind() ?? ""}>
|
<TooltipKeybind placement={placement()} title={props.settingsLabel()} keybind={props.settingsKeybind() ?? ""}>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="settings-gear"
|
icon="settings-gear"
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { Dynamic } from "solid-js/web"
|
|||||||
import { useLocal } from "@/context/local"
|
import { useLocal } from "@/context/local"
|
||||||
import { selectionFromLines, useFile, type FileSelection, type SelectedLineRange } from "@/context/file"
|
import { selectionFromLines, useFile, type FileSelection, type SelectedLineRange } from "@/context/file"
|
||||||
import { createStore, produce } from "solid-js/store"
|
import { createStore, produce } from "solid-js/store"
|
||||||
import { SessionContextUsage } from "@/components/session-context-usage"
|
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
||||||
@@ -20,9 +19,11 @@ import { Mark } from "@opencode-ai/ui/logo"
|
|||||||
import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter } from "@thisbeyond/solid-dnd"
|
import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter } from "@thisbeyond/solid-dnd"
|
||||||
import type { DragEvent } from "@thisbeyond/solid-dnd"
|
import type { DragEvent } from "@thisbeyond/solid-dnd"
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
|
import { useGlobalSync } from "@/context/global-sync"
|
||||||
import { useTerminal, type LocalPTY } from "@/context/terminal"
|
import { useTerminal, type LocalPTY } from "@/context/terminal"
|
||||||
import { useLayout } from "@/context/layout"
|
import { useLayout } from "@/context/layout"
|
||||||
import { checksum, base64Encode } from "@opencode-ai/util/encode"
|
import { checksum, base64Encode } from "@opencode-ai/util/encode"
|
||||||
|
import { findLast } from "@opencode-ai/util/array"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { DialogSelectFile } from "@/components/dialog-select-file"
|
import { DialogSelectFile } from "@/components/dialog-select-file"
|
||||||
import FileTree from "@/components/file-tree"
|
import FileTree from "@/components/file-tree"
|
||||||
@@ -34,6 +35,7 @@ import { useSDK } from "@/context/sdk"
|
|||||||
import { usePrompt } from "@/context/prompt"
|
import { usePrompt } from "@/context/prompt"
|
||||||
import { useComments } from "@/context/comments"
|
import { useComments } from "@/context/comments"
|
||||||
import { ConstrainDragYAxis, getDraggableId } from "@/utils/solid-dnd"
|
import { ConstrainDragYAxis, getDraggableId } from "@/utils/solid-dnd"
|
||||||
|
import { usePermission } from "@/context/permission"
|
||||||
import { showToast } from "@opencode-ai/ui/toast"
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
import { SessionHeader, SessionContextTab, SortableTab, FileVisual, NewSessionView } from "@/components/session"
|
import { SessionHeader, SessionContextTab, SortableTab, FileVisual, NewSessionView } from "@/components/session"
|
||||||
import { navMark, navParams } from "@/utils/perf"
|
import { navMark, navParams } from "@/utils/perf"
|
||||||
@@ -89,6 +91,7 @@ export default function Page() {
|
|||||||
const local = useLocal()
|
const local = useLocal()
|
||||||
const file = useFile()
|
const file = useFile()
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
|
const globalSync = useGlobalSync()
|
||||||
const terminal = useTerminal()
|
const terminal = useTerminal()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const codeComponent = useCodeComponent()
|
const codeComponent = useCodeComponent()
|
||||||
@@ -99,6 +102,7 @@ export default function Page() {
|
|||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
const prompt = usePrompt()
|
const prompt = usePrompt()
|
||||||
const comments = useComments()
|
const comments = useComments()
|
||||||
|
const permission = usePermission()
|
||||||
|
|
||||||
const permRequest = createMemo(() => {
|
const permRequest = createMemo(() => {
|
||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
@@ -229,7 +233,7 @@ export default function Page() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDesktop = createMediaQuery("(min-width: 1024px)")
|
const isDesktop = createMediaQuery("(min-width: 768px)")
|
||||||
const desktopReviewOpen = createMemo(() => isDesktop() && view().reviewPanel.opened())
|
const desktopReviewOpen = createMemo(() => isDesktop() && view().reviewPanel.opened())
|
||||||
const desktopFileTreeOpen = createMemo(() => isDesktop() && layout.fileTree.opened())
|
const desktopFileTreeOpen = createMemo(() => isDesktop() && layout.fileTree.opened())
|
||||||
const desktopSidePanelOpen = createMemo(() => desktopReviewOpen() || desktopFileTreeOpen())
|
const desktopSidePanelOpen = createMemo(() => desktopReviewOpen() || desktopFileTreeOpen())
|
||||||
@@ -269,7 +273,6 @@ export default function Page() {
|
|||||||
if (!path) return
|
if (!path) return
|
||||||
file.load(path)
|
file.load(path)
|
||||||
openReviewPanel()
|
openReviewPanel()
|
||||||
tabs().setActive(next)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
@@ -554,13 +557,11 @@ export default function Page() {
|
|||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
activeDraggable: undefined as string | undefined,
|
activeDraggable: undefined as string | undefined,
|
||||||
activeTerminalDraggable: undefined as string | undefined,
|
activeTerminalDraggable: undefined as string | undefined,
|
||||||
expanded: {} as Record<string, boolean>,
|
|
||||||
messageId: undefined as string | undefined,
|
messageId: undefined as string | undefined,
|
||||||
turnStart: 0,
|
turnStart: 0,
|
||||||
mobileTab: "session" as "session" | "changes",
|
mobileTab: "session" as "session" | "changes",
|
||||||
changes: "session" as "session" | "turn",
|
changes: "session" as "session" | "turn",
|
||||||
newSessionWorktree: "main",
|
newSessionWorktree: "main",
|
||||||
promptHeight: 0,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const turnDiffs = createMemo(() => lastUserMessage()?.summary?.diffs ?? [])
|
const turnDiffs = createMemo(() => lastUserMessage()?.summary?.diffs ?? [])
|
||||||
@@ -651,6 +652,7 @@ export default function Page() {
|
|||||||
const idle = { type: "idle" as const }
|
const idle = { type: "idle" as const }
|
||||||
let inputRef!: HTMLDivElement
|
let inputRef!: HTMLDivElement
|
||||||
let promptDock: HTMLDivElement | undefined
|
let promptDock: HTMLDivElement | undefined
|
||||||
|
let dockHeight = 0
|
||||||
let scroller: HTMLDivElement | undefined
|
let scroller: HTMLDivElement | undefined
|
||||||
let content: HTMLDivElement | undefined
|
let content: HTMLDivElement | undefined
|
||||||
|
|
||||||
@@ -673,7 +675,8 @@ export default function Page() {
|
|||||||
sdk.directory
|
sdk.directory
|
||||||
const id = params.id
|
const id = params.id
|
||||||
if (!id) return
|
if (!id) return
|
||||||
sync.session.sync(id)
|
void sync.session.sync(id)
|
||||||
|
void sync.session.todo(id)
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
@@ -726,13 +729,17 @@ export default function Page() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const status = createMemo(() => sync.data.session_status[params.id ?? ""] ?? idle)
|
const status = createMemo(() => sync.data.session_status[params.id ?? ""] ?? idle)
|
||||||
|
const todos = createMemo(() => {
|
||||||
|
const id = params.id
|
||||||
|
if (!id) return []
|
||||||
|
return globalSync.data.session_todo[id] ?? []
|
||||||
|
})
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
sessionKey,
|
sessionKey,
|
||||||
() => {
|
() => {
|
||||||
setStore("messageId", undefined)
|
setStore("messageId", undefined)
|
||||||
setStore("expanded", {})
|
|
||||||
setStore("changes", "session")
|
setStore("changes", "session")
|
||||||
setUi("autoCreated", false)
|
setUi("autoCreated", false)
|
||||||
},
|
},
|
||||||
@@ -751,12 +758,6 @@ export default function Page() {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
const id = lastUserMessage()?.id
|
|
||||||
if (!id) return
|
|
||||||
setStore("expanded", id, status().type !== "idle")
|
|
||||||
})
|
|
||||||
|
|
||||||
const selectionPreview = (path: string, selection: FileSelection) => {
|
const selectionPreview = (path: string, selection: FileSelection) => {
|
||||||
const content = file.get(path)?.content?.content
|
const content = file.get(path)?.content?.content
|
||||||
if (!content) return undefined
|
if (!content) return undefined
|
||||||
@@ -767,6 +768,11 @@ export default function Page() {
|
|||||||
return lines.slice(0, 2).join("\n")
|
return lines.slice(0, 2).join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const addSelectionToContext = (path: string, selection: FileSelection) => {
|
||||||
|
const preview = selectionPreview(path, selection)
|
||||||
|
prompt.context.add({ type: "file", path, selection, preview })
|
||||||
|
}
|
||||||
|
|
||||||
const addCommentToContext = (input: {
|
const addCommentToContext = (input: {
|
||||||
file: string
|
file: string
|
||||||
selection: SelectedLineRange
|
selection: SelectedLineRange
|
||||||
@@ -806,8 +812,8 @@ export default function Page() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't autofocus chat if terminal panel is open
|
// Don't autofocus chat if desktop terminal panel is open
|
||||||
if (view().terminal.opened()) return
|
if (isDesktop() && view().terminal.opened()) return
|
||||||
|
|
||||||
// Only treat explicit scroll keys as potential "user scroll" gestures.
|
// Only treat explicit scroll keys as potential "user scroll" gestures.
|
||||||
if (event.key === "PageUp" || event.key === "PageDown" || event.key === "Home" || event.key === "End") {
|
if (event.key === "PageUp" || event.key === "PageDown" || event.key === "Home" || event.key === "End") {
|
||||||
@@ -905,11 +911,29 @@ export default function Page() {
|
|||||||
const focusInput = () => inputRef?.focus()
|
const focusInput = () => inputRef?.focus()
|
||||||
|
|
||||||
useSessionCommands({
|
useSessionCommands({
|
||||||
activeMessage,
|
command,
|
||||||
|
dialog,
|
||||||
|
file,
|
||||||
|
language,
|
||||||
|
local,
|
||||||
|
permission,
|
||||||
|
prompt,
|
||||||
|
sdk,
|
||||||
|
sync,
|
||||||
|
terminal,
|
||||||
|
layout,
|
||||||
|
params,
|
||||||
|
navigate,
|
||||||
|
tabs,
|
||||||
|
view,
|
||||||
|
info,
|
||||||
|
status,
|
||||||
|
userMessages,
|
||||||
|
visibleUserMessages,
|
||||||
showAllFiles,
|
showAllFiles,
|
||||||
navigateMessageByOffset,
|
navigateMessageByOffset,
|
||||||
setExpanded: (id, fn) => setStore("expanded", id, fn),
|
|
||||||
setActiveMessage,
|
setActiveMessage,
|
||||||
|
addSelectionToContext,
|
||||||
focusInput,
|
focusInput,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -933,7 +957,6 @@ export default function Page() {
|
|||||||
onSelect={(option) => option && setStore("changes", option)}
|
onSelect={(option) => option && setStore("changes", option)}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="large"
|
size="large"
|
||||||
triggerStyle={{ "font-size": "var(--font-size-large)" }}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1421,12 +1444,12 @@ export default function Page() {
|
|||||||
({ height }) => {
|
({ height }) => {
|
||||||
const next = Math.ceil(height)
|
const next = Math.ceil(height)
|
||||||
|
|
||||||
if (next === store.promptHeight) return
|
if (next === dockHeight) return
|
||||||
|
|
||||||
const el = scroller
|
const el = scroller
|
||||||
const stick = el ? el.scrollHeight - el.clientHeight - el.scrollTop < 10 : false
|
const stick = el ? el.scrollHeight - el.clientHeight - el.scrollTop < 10 : false
|
||||||
|
|
||||||
setStore("promptHeight", next)
|
dockHeight = next
|
||||||
|
|
||||||
if (stick && el) {
|
if (stick && el) {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
@@ -1524,13 +1547,7 @@ export default function Page() {
|
|||||||
return (
|
return (
|
||||||
<div class="relative bg-background-base size-full overflow-hidden flex flex-col">
|
<div class="relative bg-background-base size-full overflow-hidden flex flex-col">
|
||||||
<SessionHeader />
|
<SessionHeader />
|
||||||
<div
|
<div class="flex-1 min-h-0 flex flex-col md:flex-row">
|
||||||
class="flex-1 min-h-0 flex"
|
|
||||||
classList={{
|
|
||||||
"flex-col": !isDesktop(),
|
|
||||||
"flex-row": isDesktop(),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SessionMobileTabs
|
<SessionMobileTabs
|
||||||
open={!isDesktop() && !!params.id}
|
open={!isDesktop() && !!params.id}
|
||||||
mobileTab={store.mobileTab}
|
mobileTab={store.mobileTab}
|
||||||
@@ -1545,12 +1562,11 @@ export default function Page() {
|
|||||||
<div
|
<div
|
||||||
classList={{
|
classList={{
|
||||||
"@container relative shrink-0 flex flex-col min-h-0 h-full bg-background-stronger": true,
|
"@container relative shrink-0 flex flex-col min-h-0 h-full bg-background-stronger": true,
|
||||||
"flex-1 pt-2 md:pt-3": true,
|
"flex-1": true,
|
||||||
"md:flex-none": desktopSidePanelOpen(),
|
"md:flex-none": desktopSidePanelOpen(),
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
width: sessionPanelWidth(),
|
width: sessionPanelWidth(),
|
||||||
"--prompt-height": store.promptHeight ? `${store.promptHeight}px` : undefined,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="flex-1 min-h-0 overflow-hidden">
|
<div class="flex-1 min-h-0 overflow-hidden">
|
||||||
@@ -1562,7 +1578,7 @@ export default function Page() {
|
|||||||
mobileFallback={reviewContent({
|
mobileFallback={reviewContent({
|
||||||
diffStyle: "unified",
|
diffStyle: "unified",
|
||||||
classes: {
|
classes: {
|
||||||
root: "pb-[calc(var(--prompt-height,8rem)+32px)]",
|
root: "pb-8",
|
||||||
header: "px-4",
|
header: "px-4",
|
||||||
container: "px-4",
|
container: "px-4",
|
||||||
},
|
},
|
||||||
@@ -1627,8 +1643,6 @@ export default function Page() {
|
|||||||
navMark({ dir: params.dir, to: id, name: "session:first-turn-mounted" })
|
navMark({ dir: params.dir, to: id, name: "session:first-turn-mounted" })
|
||||||
}}
|
}}
|
||||||
lastUserMessageID={lastUserMessage()?.id}
|
lastUserMessageID={lastUserMessage()?.id}
|
||||||
expanded={store.expanded}
|
|
||||||
onToggleExpanded={(id) => setStore("expanded", id, (open: boolean | undefined) => !open)}
|
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
</Match>
|
</Match>
|
||||||
@@ -1659,6 +1673,7 @@ export default function Page() {
|
|||||||
questionRequest={questionRequest}
|
questionRequest={questionRequest}
|
||||||
permissionRequest={permRequest}
|
permissionRequest={permRequest}
|
||||||
blocked={blocked()}
|
blocked={blocked()}
|
||||||
|
todos={todos()}
|
||||||
promptReady={prompt.ready()}
|
promptReady={prompt.ready()}
|
||||||
handoffPrompt={handoff.session.get(sessionKey())?.prompt}
|
handoffPrompt={handoff.session.get(sessionKey())?.prompt}
|
||||||
t={language.t as (key: string, vars?: Record<string, string | number | boolean>) => string}
|
t={language.t as (key: string, vars?: Record<string, string | number | boolean>) => string}
|
||||||
@@ -1731,7 +1746,7 @@ export default function Page() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TerminalPanel
|
<TerminalPanel
|
||||||
open={view().terminal.opened()}
|
open={isDesktop() && view().terminal.opened()}
|
||||||
height={layout.terminal.height()}
|
height={layout.terminal.height()}
|
||||||
resize={layout.terminal.resize}
|
resize={layout.terminal.resize}
|
||||||
close={view().terminal.close}
|
close={view().terminal.close}
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import { Icon } from "@opencode-ai/ui/icon"
|
|||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
||||||
import { InlineInput } from "@opencode-ai/ui/inline-input"
|
import { InlineInput } from "@opencode-ai/ui/inline-input"
|
||||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
|
||||||
import { SessionTurn } from "@opencode-ai/ui/session-turn"
|
import { SessionTurn } from "@opencode-ai/ui/session-turn"
|
||||||
import type { UserMessage } from "@opencode-ai/sdk/v2"
|
import type { UserMessage } from "@opencode-ai/sdk/v2"
|
||||||
import { shouldMarkBoundaryGesture, normalizeWheelDelta } from "@/pages/session/message-gesture"
|
import { shouldMarkBoundaryGesture, normalizeWheelDelta } from "@/pages/session/message-gesture"
|
||||||
|
import { SessionContextUsage } from "@/components/session-context-usage"
|
||||||
|
|
||||||
const boundaryTarget = (root: HTMLElement, target: EventTarget | null) => {
|
const boundaryTarget = (root: HTMLElement, target: EventTarget | null) => {
|
||||||
const current = target instanceof Element ? target : undefined
|
const current = target instanceof Element ? target : undefined
|
||||||
@@ -88,8 +88,6 @@ export function MessageTimeline(props: {
|
|||||||
onUnregisterMessage: (id: string) => void
|
onUnregisterMessage: (id: string) => void
|
||||||
onFirstTurnMount?: () => void
|
onFirstTurnMount?: () => void
|
||||||
lastUserMessageID?: string
|
lastUserMessageID?: string
|
||||||
expanded: Record<string, boolean>
|
|
||||||
onToggleExpanded: (id: string) => void
|
|
||||||
}) {
|
}) {
|
||||||
let touchGesture: number | undefined
|
let touchGesture: number | undefined
|
||||||
|
|
||||||
@@ -100,7 +98,7 @@ export function MessageTimeline(props: {
|
|||||||
>
|
>
|
||||||
<div class="relative w-full h-full min-w-0">
|
<div class="relative w-full h-full min-w-0">
|
||||||
<div
|
<div
|
||||||
class="absolute left-1/2 -translate-x-1/2 bottom-[calc(var(--prompt-height,8rem)+32px)] z-[60] pointer-events-none transition-all duration-200 ease-out"
|
class="absolute left-1/2 -translate-x-1/2 bottom-6 z-[60] pointer-events-none transition-all duration-200 ease-out"
|
||||||
classList={{
|
classList={{
|
||||||
"opacity-100 translate-y-0 scale-100": props.scroll.overflow && !props.scroll.bottom,
|
"opacity-100 translate-y-0 scale-100": props.scroll.overflow && !props.scroll.bottom,
|
||||||
"opacity-0 translate-y-2 scale-95 pointer-events-none": !props.scroll.overflow || props.scroll.bottom,
|
"opacity-0 translate-y-2 scale-95 pointer-events-none": !props.scroll.overflow || props.scroll.bottom,
|
||||||
@@ -164,14 +162,15 @@ export function MessageTimeline(props: {
|
|||||||
<Show when={props.showHeader}>
|
<Show when={props.showHeader}>
|
||||||
<div
|
<div
|
||||||
classList={{
|
classList={{
|
||||||
"sticky top-0 z-30 bg-background-stronger": true,
|
"sticky top-0 z-30 bg-[linear-gradient(to_bottom,var(--background-stronger)_48px,transparent)]": true,
|
||||||
"w-full": true,
|
"w-full": true,
|
||||||
"px-4 md:px-6": true,
|
"pb-4": true,
|
||||||
|
"pl-2 pr-4 md:pl-4 md:pr-6": true,
|
||||||
"md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered,
|
"md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="h-10 w-full flex items-center justify-between gap-2">
|
<div class="h-12 w-full flex items-center justify-between gap-2">
|
||||||
<div class="flex items-center gap-1 min-w-0 flex-1">
|
<div class="flex items-center gap-1 min-w-0 flex-1 pr-3">
|
||||||
<Show when={props.parentID}>
|
<Show when={props.parentID}>
|
||||||
<IconButton
|
<IconButton
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
@@ -185,7 +184,10 @@ export function MessageTimeline(props: {
|
|||||||
<Show
|
<Show
|
||||||
when={props.titleState.editing}
|
when={props.titleState.editing}
|
||||||
fallback={
|
fallback={
|
||||||
<h1 class="text-16-medium text-text-strong truncate min-w-0" onDblClick={props.openTitleEditor}>
|
<h1
|
||||||
|
class="text-14-medium text-text-strong truncate grow-1 min-w-0 pl-2"
|
||||||
|
onDblClick={props.openTitleEditor}
|
||||||
|
>
|
||||||
{props.title}
|
{props.title}
|
||||||
</h1>
|
</h1>
|
||||||
}
|
}
|
||||||
@@ -194,7 +196,8 @@ export function MessageTimeline(props: {
|
|||||||
ref={props.titleRef}
|
ref={props.titleRef}
|
||||||
value={props.titleState.draft}
|
value={props.titleState.draft}
|
||||||
disabled={props.titleState.saving}
|
disabled={props.titleState.saving}
|
||||||
class="text-16-medium text-text-strong grow-1 min-w-0"
|
class="text-14-medium text-text-strong grow-1 min-w-0 pl-2 rounded-[6px]"
|
||||||
|
style={{ "--inline-input-shadow": "var(--shadow-xs-border-select)" }}
|
||||||
onInput={(event) => props.onTitleDraft(event.currentTarget.value)}
|
onInput={(event) => props.onTitleDraft(event.currentTarget.value)}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
@@ -215,19 +218,24 @@ export function MessageTimeline(props: {
|
|||||||
</div>
|
</div>
|
||||||
<Show when={props.sessionID}>
|
<Show when={props.sessionID}>
|
||||||
{(id) => (
|
{(id) => (
|
||||||
<div class="shrink-0 flex items-center">
|
<div class="shrink-0 flex items-center gap-3">
|
||||||
<DropdownMenu open={props.titleState.menuOpen} onOpenChange={props.onTitleMenuOpen}>
|
<SessionContextUsage placement="bottom" />
|
||||||
<Tooltip value={props.t("common.moreOptions")} placement="top">
|
<DropdownMenu
|
||||||
<DropdownMenu.Trigger
|
gutter={4}
|
||||||
as={IconButton}
|
placement="bottom-end"
|
||||||
icon="dot-grid"
|
open={props.titleState.menuOpen}
|
||||||
variant="ghost"
|
onOpenChange={props.onTitleMenuOpen}
|
||||||
class="size-6 rounded-md data-[expanded]:bg-surface-base-active"
|
>
|
||||||
aria-label={props.t("common.moreOptions")}
|
<DropdownMenu.Trigger
|
||||||
/>
|
as={IconButton}
|
||||||
</Tooltip>
|
icon="dot-grid"
|
||||||
|
variant="ghost"
|
||||||
|
class="size-6 rounded-md data-[expanded]:bg-surface-base-active"
|
||||||
|
aria-label={props.t("common.moreOptions")}
|
||||||
|
/>
|
||||||
<DropdownMenu.Portal>
|
<DropdownMenu.Portal>
|
||||||
<DropdownMenu.Content
|
<DropdownMenu.Content
|
||||||
|
style={{ "min-width": "104px" }}
|
||||||
onCloseAutoFocus={(event) => {
|
onCloseAutoFocus={(event) => {
|
||||||
if (!props.titleState.pendingRename) return
|
if (!props.titleState.pendingRename) return
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@@ -263,7 +271,7 @@ export function MessageTimeline(props: {
|
|||||||
<div
|
<div
|
||||||
ref={props.setContentRef}
|
ref={props.setContentRef}
|
||||||
role="log"
|
role="log"
|
||||||
class="flex flex-col gap-12 items-start justify-start pb-[calc(var(--prompt-height,8rem)+64px)] md:pb-[calc(var(--prompt-height,10rem)+64px)] transition-[margin]"
|
class="flex flex-col gap-12 items-start justify-start pb-16 transition-[margin]"
|
||||||
classList={{
|
classList={{
|
||||||
"w-full": true,
|
"w-full": true,
|
||||||
"md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered,
|
"md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered,
|
||||||
@@ -316,8 +324,6 @@ export function MessageTimeline(props: {
|
|||||||
sessionID={props.sessionID}
|
sessionID={props.sessionID}
|
||||||
messageID={message.id}
|
messageID={message.id}
|
||||||
lastUserMessageID={props.lastUserMessageID}
|
lastUserMessageID={props.lastUserMessageID}
|
||||||
stepsExpanded={props.expanded[message.id] ?? false}
|
|
||||||
onStepsExpandedToggle={() => props.onToggleExpanded(message.id)}
|
|
||||||
classes={{
|
classes={{
|
||||||
root: "min-w-0 w-full relative",
|
root: "min-w-0 w-full relative",
|
||||||
content: "flex flex-col justify-between !overflow-visible",
|
content: "flex flex-col justify-between !overflow-visible",
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import { For, Show } from "solid-js"
|
import { For, Show, createEffect, createMemo, createSignal, on, onCleanup } from "solid-js"
|
||||||
import type { QuestionRequest } from "@opencode-ai/sdk/v2"
|
import type { QuestionRequest, Todo } from "@opencode-ai/sdk/v2"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { BasicTool } from "@opencode-ai/ui/basic-tool"
|
import { BasicTool } from "@opencode-ai/ui/basic-tool"
|
||||||
import { PromptInput } from "@/components/prompt-input"
|
import { PromptInput } from "@/components/prompt-input"
|
||||||
import { QuestionDock } from "@/components/question-dock"
|
import { QuestionDock } from "@/components/question-dock"
|
||||||
import { questionSubtitle } from "@/pages/session/session-prompt-helpers"
|
import { SessionTodoDock } from "@/components/session-todo-dock"
|
||||||
|
|
||||||
export function SessionPromptDock(props: {
|
export function SessionPromptDock(props: {
|
||||||
centered: boolean
|
centered: boolean
|
||||||
questionRequest: () => QuestionRequest | undefined
|
questionRequest: () => QuestionRequest | undefined
|
||||||
permissionRequest: () => { patterns: string[]; permission: string } | undefined
|
permissionRequest: () => { patterns: string[]; permission: string } | undefined
|
||||||
blocked: boolean
|
blocked: boolean
|
||||||
|
todos: Todo[]
|
||||||
promptReady: boolean
|
promptReady: boolean
|
||||||
handoffPrompt?: string
|
handoffPrompt?: string
|
||||||
t: (key: string, vars?: Record<string, string | number | boolean>) => string
|
t: (key: string, vars?: Record<string, string | number | boolean>) => string
|
||||||
@@ -22,10 +23,88 @@ export function SessionPromptDock(props: {
|
|||||||
onSubmit: () => void
|
onSubmit: () => void
|
||||||
setPromptDockRef: (el: HTMLDivElement) => void
|
setPromptDockRef: (el: HTMLDivElement) => void
|
||||||
}) {
|
}) {
|
||||||
|
const done = createMemo(
|
||||||
|
() =>
|
||||||
|
props.todos.length > 0 && props.todos.every((todo) => todo.status === "completed" || todo.status === "cancelled"),
|
||||||
|
)
|
||||||
|
|
||||||
|
const [dock, setDock] = createSignal(props.todos.length > 0)
|
||||||
|
const [closing, setClosing] = createSignal(false)
|
||||||
|
const [opening, setOpening] = createSignal(false)
|
||||||
|
let timer: number | undefined
|
||||||
|
let raf: number | undefined
|
||||||
|
|
||||||
|
const scheduleClose = () => {
|
||||||
|
if (timer) window.clearTimeout(timer)
|
||||||
|
timer = window.setTimeout(() => {
|
||||||
|
setDock(false)
|
||||||
|
setClosing(false)
|
||||||
|
timer = undefined
|
||||||
|
}, 400)
|
||||||
|
}
|
||||||
|
|
||||||
|
createEffect(
|
||||||
|
on(
|
||||||
|
() => [props.todos.length, done()] as const,
|
||||||
|
([count, complete], prev) => {
|
||||||
|
if (raf) cancelAnimationFrame(raf)
|
||||||
|
raf = undefined
|
||||||
|
|
||||||
|
if (count === 0) {
|
||||||
|
if (timer) window.clearTimeout(timer)
|
||||||
|
timer = undefined
|
||||||
|
setDock(false)
|
||||||
|
setClosing(false)
|
||||||
|
setOpening(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!complete) {
|
||||||
|
if (timer) window.clearTimeout(timer)
|
||||||
|
timer = undefined
|
||||||
|
const wasHidden = !dock() || closing()
|
||||||
|
setDock(true)
|
||||||
|
setClosing(false)
|
||||||
|
if (wasHidden) {
|
||||||
|
setOpening(true)
|
||||||
|
raf = requestAnimationFrame(() => {
|
||||||
|
setOpening(false)
|
||||||
|
raf = undefined
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setOpening(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prev && prev[1]) {
|
||||||
|
if (closing() && !timer) scheduleClose()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setDock(true)
|
||||||
|
setOpening(false)
|
||||||
|
setClosing(true)
|
||||||
|
scheduleClose()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
if (!timer) return
|
||||||
|
window.clearTimeout(timer)
|
||||||
|
})
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
if (!raf) return
|
||||||
|
cancelAnimationFrame(raf)
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={props.setPromptDockRef}
|
ref={props.setPromptDockRef}
|
||||||
class="absolute inset-x-0 bottom-0 pt-12 pb-4 flex flex-col justify-center items-center z-50 bg-gradient-to-t from-background-stronger via-background-stronger to-transparent pointer-events-none"
|
data-component="session-prompt-dock"
|
||||||
|
class="shrink-0 w-full pb-4 flex flex-col justify-center items-center bg-background-stronger pointer-events-none"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
classList={{
|
classList={{
|
||||||
@@ -35,18 +114,8 @@ export function SessionPromptDock(props: {
|
|||||||
>
|
>
|
||||||
<Show when={props.questionRequest()} keyed>
|
<Show when={props.questionRequest()} keyed>
|
||||||
{(req) => {
|
{(req) => {
|
||||||
const subtitle = questionSubtitle(req.questions.length, (key) => props.t(key))
|
|
||||||
return (
|
return (
|
||||||
<div data-component="tool-part-wrapper" data-question="true" class="mb-3">
|
<div>
|
||||||
<BasicTool
|
|
||||||
icon="bubble-5"
|
|
||||||
locked
|
|
||||||
defaultOpen
|
|
||||||
trigger={{
|
|
||||||
title: props.t("ui.tool.questions"),
|
|
||||||
subtitle,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<QuestionDock request={req} />
|
<QuestionDock request={req} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -122,12 +191,39 @@ export function SessionPromptDock(props: {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<PromptInput
|
<Show when={dock()}>
|
||||||
ref={props.inputRef}
|
<div
|
||||||
newSessionWorktree={props.newSessionWorktree}
|
classList={{
|
||||||
onNewSessionWorktreeReset={props.onNewSessionWorktreeReset}
|
"transition-[max-height,opacity,transform] duration-[400ms] ease-out overflow-hidden": true,
|
||||||
onSubmit={props.onSubmit}
|
"max-h-[320px]": !closing(),
|
||||||
/>
|
"max-h-0 pointer-events-none": closing(),
|
||||||
|
"opacity-0 translate-y-9": closing() || opening(),
|
||||||
|
"opacity-100 translate-y-0": !closing() && !opening(),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SessionTodoDock
|
||||||
|
todos={props.todos}
|
||||||
|
title={props.t("session.todo.title")}
|
||||||
|
collapseLabel={props.t("session.todo.collapse")}
|
||||||
|
expandLabel={props.t("session.todo.expand")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
<div
|
||||||
|
classList={{
|
||||||
|
"relative z-10": true,
|
||||||
|
"transition-[margin] duration-[400ms] ease-out": true,
|
||||||
|
"-mt-9": dock() && !closing(),
|
||||||
|
"mt-0": !dock() || closing(),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PromptInput
|
||||||
|
ref={props.inputRef}
|
||||||
|
newSessionWorktree={props.newSessionWorktree}
|
||||||
|
onNewSessionWorktreeReset={props.onNewSessionWorktreeReset}
|
||||||
|
onSubmit={props.onSubmit}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,11 +22,29 @@ import { UserMessage } from "@opencode-ai/sdk/v2"
|
|||||||
import { canAddSelectionContext } from "@/pages/session/session-command-helpers"
|
import { canAddSelectionContext } from "@/pages/session/session-command-helpers"
|
||||||
|
|
||||||
export type SessionCommandContext = {
|
export type SessionCommandContext = {
|
||||||
activeMessage: () => UserMessage | undefined
|
command: ReturnType<typeof useCommand>
|
||||||
|
dialog: ReturnType<typeof useDialog>
|
||||||
|
file: ReturnType<typeof useFile>
|
||||||
|
language: ReturnType<typeof useLanguage>
|
||||||
|
local: ReturnType<typeof useLocal>
|
||||||
|
permission: ReturnType<typeof usePermission>
|
||||||
|
prompt: ReturnType<typeof usePrompt>
|
||||||
|
sdk: ReturnType<typeof useSDK>
|
||||||
|
sync: ReturnType<typeof useSync>
|
||||||
|
terminal: ReturnType<typeof useTerminal>
|
||||||
|
layout: ReturnType<typeof useLayout>
|
||||||
|
params: ReturnType<typeof useParams>
|
||||||
|
navigate: ReturnType<typeof useNavigate>
|
||||||
|
tabs: () => ReturnType<ReturnType<typeof useLayout>["tabs"]>
|
||||||
|
view: () => ReturnType<ReturnType<typeof useLayout>["view"]>
|
||||||
|
info: () => { revert?: { messageID?: string }; share?: { url?: string } } | undefined
|
||||||
|
status: () => { type: string }
|
||||||
|
userMessages: () => UserMessage[]
|
||||||
|
visibleUserMessages: () => UserMessage[]
|
||||||
showAllFiles: () => void
|
showAllFiles: () => void
|
||||||
navigateMessageByOffset: (offset: number) => void
|
navigateMessageByOffset: (offset: number) => void
|
||||||
setExpanded: (id: string, fn: (open: boolean | undefined) => boolean) => void
|
|
||||||
setActiveMessage: (message: UserMessage | undefined) => void
|
setActiveMessage: (message: UserMessage | undefined) => void
|
||||||
|
addSelectionToContext: (path: string, selection: FileSelection) => void
|
||||||
focusInput: () => void
|
focusInput: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,88 +55,45 @@ const withCategory = (category: string) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSessionCommands = (args: SessionCommandContext) => {
|
export const useSessionCommands = (input: SessionCommandContext) => {
|
||||||
const command = useCommand()
|
const sessionCommand = withCategory(input.language.t("command.category.session"))
|
||||||
const dialog = useDialog()
|
const fileCommand = withCategory(input.language.t("command.category.file"))
|
||||||
const file = useFile()
|
const contextCommand = withCategory(input.language.t("command.category.context"))
|
||||||
const language = useLanguage()
|
const viewCommand = withCategory(input.language.t("command.category.view"))
|
||||||
const local = useLocal()
|
const terminalCommand = withCategory(input.language.t("command.category.terminal"))
|
||||||
const permission = usePermission()
|
const modelCommand = withCategory(input.language.t("command.category.model"))
|
||||||
const prompt = usePrompt()
|
const mcpCommand = withCategory(input.language.t("command.category.mcp"))
|
||||||
const sdk = useSDK()
|
const agentCommand = withCategory(input.language.t("command.category.agent"))
|
||||||
const sync = useSync()
|
const permissionsCommand = withCategory(input.language.t("command.category.permissions"))
|
||||||
const terminal = useTerminal()
|
|
||||||
const layout = useLayout()
|
|
||||||
const params = useParams()
|
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
|
||||||
const tabs = createMemo(() => layout.tabs(sessionKey))
|
|
||||||
const view = createMemo(() => layout.view(sessionKey))
|
|
||||||
const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
|
|
||||||
const idle = { type: "idle" as const }
|
|
||||||
const status = createMemo(() => sync.data.session_status[params.id ?? ""] ?? idle)
|
|
||||||
const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
|
|
||||||
const userMessages = createMemo(() => messages().filter((m) => m.role === "user") as UserMessage[])
|
|
||||||
const visibleUserMessages = createMemo(() => {
|
|
||||||
const revert = info()?.revert?.messageID
|
|
||||||
if (!revert) return userMessages()
|
|
||||||
return userMessages().filter((m) => m.id < revert)
|
|
||||||
})
|
|
||||||
|
|
||||||
const selectionPreview = (path: string, selection: FileSelection) => {
|
|
||||||
const content = file.get(path)?.content?.content
|
|
||||||
if (!content) return undefined
|
|
||||||
const start = Math.max(1, Math.min(selection.startLine, selection.endLine))
|
|
||||||
const end = Math.max(selection.startLine, selection.endLine)
|
|
||||||
const lines = content.split("\n").slice(start - 1, end)
|
|
||||||
if (lines.length === 0) return undefined
|
|
||||||
return lines.slice(0, 2).join("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
const addSelectionToContext = (path: string, selection: FileSelection) => {
|
|
||||||
const preview = selectionPreview(path, selection)
|
|
||||||
prompt.context.add({ type: "file", path, selection, preview })
|
|
||||||
}
|
|
||||||
|
|
||||||
const sessionCommand = withCategory(language.t("command.category.session"))
|
|
||||||
const fileCommand = withCategory(language.t("command.category.file"))
|
|
||||||
const contextCommand = withCategory(language.t("command.category.context"))
|
|
||||||
const viewCommand = withCategory(language.t("command.category.view"))
|
|
||||||
const terminalCommand = withCategory(language.t("command.category.terminal"))
|
|
||||||
const modelCommand = withCategory(language.t("command.category.model"))
|
|
||||||
const mcpCommand = withCategory(language.t("command.category.mcp"))
|
|
||||||
const agentCommand = withCategory(language.t("command.category.agent"))
|
|
||||||
const permissionsCommand = withCategory(language.t("command.category.permissions"))
|
|
||||||
|
|
||||||
const sessionCommands = createMemo(() => [
|
const sessionCommands = createMemo(() => [
|
||||||
sessionCommand({
|
sessionCommand({
|
||||||
id: "session.new",
|
id: "session.new",
|
||||||
title: language.t("command.session.new"),
|
title: input.language.t("command.session.new"),
|
||||||
keybind: "mod+shift+s",
|
keybind: "mod+shift+s",
|
||||||
slash: "new",
|
slash: "new",
|
||||||
onSelect: () => navigate(`/${params.dir}/session`),
|
onSelect: () => input.navigate(`/${input.params.dir}/session`),
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
|
|
||||||
const fileCommands = createMemo(() => [
|
const fileCommands = createMemo(() => [
|
||||||
fileCommand({
|
fileCommand({
|
||||||
id: "file.open",
|
id: "file.open",
|
||||||
title: language.t("command.file.open"),
|
title: input.language.t("command.file.open"),
|
||||||
description: language.t("palette.search.placeholder"),
|
description: input.language.t("palette.search.placeholder"),
|
||||||
keybind: "mod+p",
|
keybind: "mod+p",
|
||||||
slash: "open",
|
slash: "open",
|
||||||
onSelect: () => dialog.show(() => <DialogSelectFile onOpenFile={args.showAllFiles} />),
|
onSelect: () => input.dialog.show(() => <DialogSelectFile onOpenFile={input.showAllFiles} />),
|
||||||
}),
|
}),
|
||||||
fileCommand({
|
fileCommand({
|
||||||
id: "tab.close",
|
id: "tab.close",
|
||||||
title: language.t("command.tab.close"),
|
title: input.language.t("command.tab.close"),
|
||||||
keybind: "mod+w",
|
keybind: "mod+w",
|
||||||
disabled: !tabs().active(),
|
disabled: !input.tabs().active(),
|
||||||
onSelect: () => {
|
onSelect: () => {
|
||||||
const active = tabs().active()
|
const active = input.tabs().active()
|
||||||
if (!active) return
|
if (!active) return
|
||||||
tabs().close(active)
|
input.tabs().close(active)
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
@@ -126,30 +101,30 @@ export const useSessionCommands = (args: SessionCommandContext) => {
|
|||||||
const contextCommands = createMemo(() => [
|
const contextCommands = createMemo(() => [
|
||||||
contextCommand({
|
contextCommand({
|
||||||
id: "context.addSelection",
|
id: "context.addSelection",
|
||||||
title: language.t("command.context.addSelection"),
|
title: input.language.t("command.context.addSelection"),
|
||||||
description: language.t("command.context.addSelection.description"),
|
description: input.language.t("command.context.addSelection.description"),
|
||||||
keybind: "mod+shift+l",
|
keybind: "mod+shift+l",
|
||||||
disabled: !canAddSelectionContext({
|
disabled: !canAddSelectionContext({
|
||||||
active: tabs().active(),
|
active: input.tabs().active(),
|
||||||
pathFromTab: file.pathFromTab,
|
pathFromTab: input.file.pathFromTab,
|
||||||
selectedLines: file.selectedLines,
|
selectedLines: input.file.selectedLines,
|
||||||
}),
|
}),
|
||||||
onSelect: () => {
|
onSelect: () => {
|
||||||
const active = tabs().active()
|
const active = input.tabs().active()
|
||||||
if (!active) return
|
if (!active) return
|
||||||
const path = file.pathFromTab(active)
|
const path = input.file.pathFromTab(active)
|
||||||
if (!path) return
|
if (!path) return
|
||||||
|
|
||||||
const range = file.selectedLines(path) as SelectedLineRange | null | undefined
|
const range = input.file.selectedLines(path) as SelectedLineRange | null | undefined
|
||||||
if (!range) {
|
if (!range) {
|
||||||
showToast({
|
showToast({
|
||||||
title: language.t("toast.context.noLineSelection.title"),
|
title: input.language.t("toast.context.noLineSelection.title"),
|
||||||
description: language.t("toast.context.noLineSelection.description"),
|
description: input.language.t("toast.context.noLineSelection.description"),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
addSelectionToContext(path, selectionFromLines(range))
|
input.addSelectionToContext(path, selectionFromLines(range))
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
@@ -157,50 +132,37 @@ export const useSessionCommands = (args: SessionCommandContext) => {
|
|||||||
const viewCommands = createMemo(() => [
|
const viewCommands = createMemo(() => [
|
||||||
viewCommand({
|
viewCommand({
|
||||||
id: "terminal.toggle",
|
id: "terminal.toggle",
|
||||||
title: language.t("command.terminal.toggle"),
|
title: input.language.t("command.terminal.toggle"),
|
||||||
keybind: "ctrl+`",
|
keybind: "ctrl+`",
|
||||||
slash: "terminal",
|
slash: "terminal",
|
||||||
onSelect: () => view().terminal.toggle(),
|
onSelect: () => input.view().terminal.toggle(),
|
||||||
}),
|
}),
|
||||||
viewCommand({
|
viewCommand({
|
||||||
id: "review.toggle",
|
id: "review.toggle",
|
||||||
title: language.t("command.review.toggle"),
|
title: input.language.t("command.review.toggle"),
|
||||||
keybind: "mod+shift+r",
|
keybind: "mod+shift+r",
|
||||||
onSelect: () => view().reviewPanel.toggle(),
|
onSelect: () => input.view().reviewPanel.toggle(),
|
||||||
}),
|
}),
|
||||||
viewCommand({
|
viewCommand({
|
||||||
id: "fileTree.toggle",
|
id: "fileTree.toggle",
|
||||||
title: language.t("command.fileTree.toggle"),
|
title: input.language.t("command.fileTree.toggle"),
|
||||||
keybind: "mod+\\",
|
keybind: "mod+\\",
|
||||||
onSelect: () => layout.fileTree.toggle(),
|
onSelect: () => input.layout.fileTree.toggle(),
|
||||||
}),
|
}),
|
||||||
viewCommand({
|
viewCommand({
|
||||||
id: "input.focus",
|
id: "input.focus",
|
||||||
title: language.t("command.input.focus"),
|
title: input.language.t("command.input.focus"),
|
||||||
keybind: "ctrl+l",
|
keybind: "ctrl+l",
|
||||||
onSelect: () => args.focusInput(),
|
onSelect: () => input.focusInput(),
|
||||||
}),
|
}),
|
||||||
terminalCommand({
|
terminalCommand({
|
||||||
id: "terminal.new",
|
id: "terminal.new",
|
||||||
title: language.t("command.terminal.new"),
|
title: input.language.t("command.terminal.new"),
|
||||||
description: language.t("command.terminal.new.description"),
|
description: input.language.t("command.terminal.new.description"),
|
||||||
keybind: "ctrl+alt+t",
|
keybind: "ctrl+alt+t",
|
||||||
onSelect: () => {
|
onSelect: () => {
|
||||||
if (terminal.all().length > 0) terminal.new()
|
if (input.terminal.all().length > 0) input.terminal.new()
|
||||||
view().terminal.open()
|
input.view().terminal.open()
|
||||||
},
|
|
||||||
}),
|
|
||||||
viewCommand({
|
|
||||||
id: "steps.toggle",
|
|
||||||
title: language.t("command.steps.toggle"),
|
|
||||||
description: language.t("command.steps.toggle.description"),
|
|
||||||
keybind: "mod+e",
|
|
||||||
slash: "steps",
|
|
||||||
disabled: !params.id,
|
|
||||||
onSelect: () => {
|
|
||||||
const msg = args.activeMessage()
|
|
||||||
if (!msg) return
|
|
||||||
args.setExpanded(msg.id, (open: boolean | undefined) => !open)
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
@@ -208,61 +170,61 @@ export const useSessionCommands = (args: SessionCommandContext) => {
|
|||||||
const messageCommands = createMemo(() => [
|
const messageCommands = createMemo(() => [
|
||||||
sessionCommand({
|
sessionCommand({
|
||||||
id: "message.previous",
|
id: "message.previous",
|
||||||
title: language.t("command.message.previous"),
|
title: input.language.t("command.message.previous"),
|
||||||
description: language.t("command.message.previous.description"),
|
description: input.language.t("command.message.previous.description"),
|
||||||
keybind: "mod+arrowup",
|
keybind: "mod+arrowup",
|
||||||
disabled: !params.id,
|
disabled: !input.params.id,
|
||||||
onSelect: () => args.navigateMessageByOffset(-1),
|
onSelect: () => input.navigateMessageByOffset(-1),
|
||||||
}),
|
}),
|
||||||
sessionCommand({
|
sessionCommand({
|
||||||
id: "message.next",
|
id: "message.next",
|
||||||
title: language.t("command.message.next"),
|
title: input.language.t("command.message.next"),
|
||||||
description: language.t("command.message.next.description"),
|
description: input.language.t("command.message.next.description"),
|
||||||
keybind: "mod+arrowdown",
|
keybind: "mod+arrowdown",
|
||||||
disabled: !params.id,
|
disabled: !input.params.id,
|
||||||
onSelect: () => args.navigateMessageByOffset(1),
|
onSelect: () => input.navigateMessageByOffset(1),
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
|
|
||||||
const agentCommands = createMemo(() => [
|
const agentCommands = createMemo(() => [
|
||||||
modelCommand({
|
modelCommand({
|
||||||
id: "model.choose",
|
id: "model.choose",
|
||||||
title: language.t("command.model.choose"),
|
title: input.language.t("command.model.choose"),
|
||||||
description: language.t("command.model.choose.description"),
|
description: input.language.t("command.model.choose.description"),
|
||||||
keybind: "mod+'",
|
keybind: "mod+'",
|
||||||
slash: "model",
|
slash: "model",
|
||||||
onSelect: () => dialog.show(() => <DialogSelectModel />),
|
onSelect: () => input.dialog.show(() => <DialogSelectModel />),
|
||||||
}),
|
}),
|
||||||
mcpCommand({
|
mcpCommand({
|
||||||
id: "mcp.toggle",
|
id: "mcp.toggle",
|
||||||
title: language.t("command.mcp.toggle"),
|
title: input.language.t("command.mcp.toggle"),
|
||||||
description: language.t("command.mcp.toggle.description"),
|
description: input.language.t("command.mcp.toggle.description"),
|
||||||
keybind: "mod+;",
|
keybind: "mod+;",
|
||||||
slash: "mcp",
|
slash: "mcp",
|
||||||
onSelect: () => dialog.show(() => <DialogSelectMcp />),
|
onSelect: () => input.dialog.show(() => <DialogSelectMcp />),
|
||||||
}),
|
}),
|
||||||
agentCommand({
|
agentCommand({
|
||||||
id: "agent.cycle",
|
id: "agent.cycle",
|
||||||
title: language.t("command.agent.cycle"),
|
title: input.language.t("command.agent.cycle"),
|
||||||
description: language.t("command.agent.cycle.description"),
|
description: input.language.t("command.agent.cycle.description"),
|
||||||
keybind: "mod+.",
|
keybind: "mod+.",
|
||||||
slash: "agent",
|
slash: "agent",
|
||||||
onSelect: () => local.agent.move(1),
|
onSelect: () => input.local.agent.move(1),
|
||||||
}),
|
}),
|
||||||
agentCommand({
|
agentCommand({
|
||||||
id: "agent.cycle.reverse",
|
id: "agent.cycle.reverse",
|
||||||
title: language.t("command.agent.cycle.reverse"),
|
title: input.language.t("command.agent.cycle.reverse"),
|
||||||
description: language.t("command.agent.cycle.reverse.description"),
|
description: input.language.t("command.agent.cycle.reverse.description"),
|
||||||
keybind: "shift+mod+.",
|
keybind: "shift+mod+.",
|
||||||
onSelect: () => local.agent.move(-1),
|
onSelect: () => input.local.agent.move(-1),
|
||||||
}),
|
}),
|
||||||
modelCommand({
|
modelCommand({
|
||||||
id: "model.variant.cycle",
|
id: "model.variant.cycle",
|
||||||
title: language.t("command.model.variant.cycle"),
|
title: input.language.t("command.model.variant.cycle"),
|
||||||
description: language.t("command.model.variant.cycle.description"),
|
description: input.language.t("command.model.variant.cycle.description"),
|
||||||
keybind: "shift+mod+d",
|
keybind: "shift+mod+d",
|
||||||
onSelect: () => {
|
onSelect: () => {
|
||||||
local.model.variant.cycle()
|
input.local.model.variant.cycle()
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
@@ -271,22 +233,22 @@ export const useSessionCommands = (args: SessionCommandContext) => {
|
|||||||
permissionsCommand({
|
permissionsCommand({
|
||||||
id: "permissions.autoaccept",
|
id: "permissions.autoaccept",
|
||||||
title:
|
title:
|
||||||
params.id && permission.isAutoAccepting(params.id, sdk.directory)
|
input.params.id && input.permission.isAutoAccepting(input.params.id, input.sdk.directory)
|
||||||
? language.t("command.permissions.autoaccept.disable")
|
? input.language.t("command.permissions.autoaccept.disable")
|
||||||
: language.t("command.permissions.autoaccept.enable"),
|
: input.language.t("command.permissions.autoaccept.enable"),
|
||||||
keybind: "mod+shift+a",
|
keybind: "mod+shift+a",
|
||||||
disabled: !params.id || !permission.permissionsEnabled(),
|
disabled: !input.params.id || !input.permission.permissionsEnabled(),
|
||||||
onSelect: () => {
|
onSelect: () => {
|
||||||
const sessionID = params.id
|
const sessionID = input.params.id
|
||||||
if (!sessionID) return
|
if (!sessionID) return
|
||||||
permission.toggleAutoAccept(sessionID, sdk.directory)
|
input.permission.toggleAutoAccept(sessionID, input.sdk.directory)
|
||||||
showToast({
|
showToast({
|
||||||
title: permission.isAutoAccepting(sessionID, sdk.directory)
|
title: input.permission.isAutoAccepting(sessionID, input.sdk.directory)
|
||||||
? language.t("toast.permissions.autoaccept.on.title")
|
? input.language.t("toast.permissions.autoaccept.on.title")
|
||||||
: language.t("toast.permissions.autoaccept.off.title"),
|
: input.language.t("toast.permissions.autoaccept.off.title"),
|
||||||
description: permission.isAutoAccepting(sessionID, sdk.directory)
|
description: input.permission.isAutoAccepting(sessionID, input.sdk.directory)
|
||||||
? language.t("toast.permissions.autoaccept.on.description")
|
? input.language.t("toast.permissions.autoaccept.on.description")
|
||||||
: language.t("toast.permissions.autoaccept.off.description"),
|
: input.language.t("toast.permissions.autoaccept.off.description"),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -295,71 +257,71 @@ export const useSessionCommands = (args: SessionCommandContext) => {
|
|||||||
const sessionActionCommands = createMemo(() => [
|
const sessionActionCommands = createMemo(() => [
|
||||||
sessionCommand({
|
sessionCommand({
|
||||||
id: "session.undo",
|
id: "session.undo",
|
||||||
title: language.t("command.session.undo"),
|
title: input.language.t("command.session.undo"),
|
||||||
description: language.t("command.session.undo.description"),
|
description: input.language.t("command.session.undo.description"),
|
||||||
slash: "undo",
|
slash: "undo",
|
||||||
disabled: !params.id || visibleUserMessages().length === 0,
|
disabled: !input.params.id || input.visibleUserMessages().length === 0,
|
||||||
onSelect: async () => {
|
onSelect: async () => {
|
||||||
const sessionID = params.id
|
const sessionID = input.params.id
|
||||||
if (!sessionID) return
|
if (!sessionID) return
|
||||||
if (status()?.type !== "idle") {
|
if (input.status()?.type !== "idle") {
|
||||||
await sdk.client.session.abort({ sessionID }).catch(() => {})
|
await input.sdk.client.session.abort({ sessionID }).catch(() => {})
|
||||||
}
|
}
|
||||||
const revert = info()?.revert?.messageID
|
const revert = input.info()?.revert?.messageID
|
||||||
const message = findLast(userMessages(), (x) => !revert || x.id < revert)
|
const message = findLast(input.userMessages(), (x) => !revert || x.id < revert)
|
||||||
if (!message) return
|
if (!message) return
|
||||||
await sdk.client.session.revert({ sessionID, messageID: message.id })
|
await input.sdk.client.session.revert({ sessionID, messageID: message.id })
|
||||||
const parts = sync.data.part[message.id]
|
const parts = input.sync.data.part[message.id]
|
||||||
if (parts) {
|
if (parts) {
|
||||||
const restored = extractPromptFromParts(parts, { directory: sdk.directory })
|
const restored = extractPromptFromParts(parts, { directory: input.sdk.directory })
|
||||||
prompt.set(restored)
|
input.prompt.set(restored)
|
||||||
}
|
}
|
||||||
const priorMessage = findLast(userMessages(), (x) => x.id < message.id)
|
const priorMessage = findLast(input.userMessages(), (x) => x.id < message.id)
|
||||||
args.setActiveMessage(priorMessage)
|
input.setActiveMessage(priorMessage)
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
sessionCommand({
|
sessionCommand({
|
||||||
id: "session.redo",
|
id: "session.redo",
|
||||||
title: language.t("command.session.redo"),
|
title: input.language.t("command.session.redo"),
|
||||||
description: language.t("command.session.redo.description"),
|
description: input.language.t("command.session.redo.description"),
|
||||||
slash: "redo",
|
slash: "redo",
|
||||||
disabled: !params.id || !info()?.revert?.messageID,
|
disabled: !input.params.id || !input.info()?.revert?.messageID,
|
||||||
onSelect: async () => {
|
onSelect: async () => {
|
||||||
const sessionID = params.id
|
const sessionID = input.params.id
|
||||||
if (!sessionID) return
|
if (!sessionID) return
|
||||||
const revertMessageID = info()?.revert?.messageID
|
const revertMessageID = input.info()?.revert?.messageID
|
||||||
if (!revertMessageID) return
|
if (!revertMessageID) return
|
||||||
const nextMessage = userMessages().find((x) => x.id > revertMessageID)
|
const nextMessage = input.userMessages().find((x) => x.id > revertMessageID)
|
||||||
if (!nextMessage) {
|
if (!nextMessage) {
|
||||||
await sdk.client.session.unrevert({ sessionID })
|
await input.sdk.client.session.unrevert({ sessionID })
|
||||||
prompt.reset()
|
input.prompt.reset()
|
||||||
const lastMsg = findLast(userMessages(), (x) => x.id >= revertMessageID)
|
const lastMsg = findLast(input.userMessages(), (x) => x.id >= revertMessageID)
|
||||||
args.setActiveMessage(lastMsg)
|
input.setActiveMessage(lastMsg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await sdk.client.session.revert({ sessionID, messageID: nextMessage.id })
|
await input.sdk.client.session.revert({ sessionID, messageID: nextMessage.id })
|
||||||
const priorMsg = findLast(userMessages(), (x) => x.id < nextMessage.id)
|
const priorMsg = findLast(input.userMessages(), (x) => x.id < nextMessage.id)
|
||||||
args.setActiveMessage(priorMsg)
|
input.setActiveMessage(priorMsg)
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
sessionCommand({
|
sessionCommand({
|
||||||
id: "session.compact",
|
id: "session.compact",
|
||||||
title: language.t("command.session.compact"),
|
title: input.language.t("command.session.compact"),
|
||||||
description: language.t("command.session.compact.description"),
|
description: input.language.t("command.session.compact.description"),
|
||||||
slash: "compact",
|
slash: "compact",
|
||||||
disabled: !params.id || visibleUserMessages().length === 0,
|
disabled: !input.params.id || input.visibleUserMessages().length === 0,
|
||||||
onSelect: async () => {
|
onSelect: async () => {
|
||||||
const sessionID = params.id
|
const sessionID = input.params.id
|
||||||
if (!sessionID) return
|
if (!sessionID) return
|
||||||
const model = local.model.current()
|
const model = input.local.model.current()
|
||||||
if (!model) {
|
if (!model) {
|
||||||
showToast({
|
showToast({
|
||||||
title: language.t("toast.model.none.title"),
|
title: input.language.t("toast.model.none.title"),
|
||||||
description: language.t("toast.model.none.description"),
|
description: input.language.t("toast.model.none.description"),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await sdk.client.session.summarize({
|
await input.sdk.client.session.summarize({
|
||||||
sessionID,
|
sessionID,
|
||||||
modelID: model.id,
|
modelID: model.id,
|
||||||
providerID: model.provider.id,
|
providerID: model.provider.id,
|
||||||
@@ -368,27 +330,29 @@ export const useSessionCommands = (args: SessionCommandContext) => {
|
|||||||
}),
|
}),
|
||||||
sessionCommand({
|
sessionCommand({
|
||||||
id: "session.fork",
|
id: "session.fork",
|
||||||
title: language.t("command.session.fork"),
|
title: input.language.t("command.session.fork"),
|
||||||
description: language.t("command.session.fork.description"),
|
description: input.language.t("command.session.fork.description"),
|
||||||
slash: "fork",
|
slash: "fork",
|
||||||
disabled: !params.id || visibleUserMessages().length === 0,
|
disabled: !input.params.id || input.visibleUserMessages().length === 0,
|
||||||
onSelect: () => dialog.show(() => <DialogFork />),
|
onSelect: () => input.dialog.show(() => <DialogFork />),
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
|
|
||||||
const shareCommands = createMemo(() => {
|
const shareCommands = createMemo(() => {
|
||||||
if (sync.data.config.share === "disabled") return []
|
if (input.sync.data.config.share === "disabled") return []
|
||||||
return [
|
return [
|
||||||
sessionCommand({
|
sessionCommand({
|
||||||
id: "session.share",
|
id: "session.share",
|
||||||
title: info()?.share?.url ? language.t("session.share.copy.copyLink") : language.t("command.session.share"),
|
title: input.info()?.share?.url
|
||||||
description: info()?.share?.url
|
? input.language.t("session.share.copy.copyLink")
|
||||||
? language.t("toast.session.share.success.description")
|
: input.language.t("command.session.share"),
|
||||||
: language.t("command.session.share.description"),
|
description: input.info()?.share?.url
|
||||||
|
? input.language.t("toast.session.share.success.description")
|
||||||
|
: input.language.t("command.session.share.description"),
|
||||||
slash: "share",
|
slash: "share",
|
||||||
disabled: !params.id,
|
disabled: !input.params.id,
|
||||||
onSelect: async () => {
|
onSelect: async () => {
|
||||||
if (!params.id) return
|
if (!input.params.id) return
|
||||||
|
|
||||||
const write = (value: string) => {
|
const write = (value: string) => {
|
||||||
const body = typeof document === "undefined" ? undefined : document.body
|
const body = typeof document === "undefined" ? undefined : document.body
|
||||||
@@ -418,7 +382,7 @@ export const useSessionCommands = (args: SessionCommandContext) => {
|
|||||||
const ok = await write(url)
|
const ok = await write(url)
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
showToast({
|
showToast({
|
||||||
title: language.t("toast.session.share.copyFailed.title"),
|
title: input.language.t("toast.session.share.copyFailed.title"),
|
||||||
variant: "error",
|
variant: "error",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -426,27 +390,27 @@ export const useSessionCommands = (args: SessionCommandContext) => {
|
|||||||
|
|
||||||
showToast({
|
showToast({
|
||||||
title: existing
|
title: existing
|
||||||
? language.t("session.share.copy.copied")
|
? input.language.t("session.share.copy.copied")
|
||||||
: language.t("toast.session.share.success.title"),
|
: input.language.t("toast.session.share.success.title"),
|
||||||
description: language.t("toast.session.share.success.description"),
|
description: input.language.t("toast.session.share.success.description"),
|
||||||
variant: "success",
|
variant: "success",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const existing = info()?.share?.url
|
const existing = input.info()?.share?.url
|
||||||
if (existing) {
|
if (existing) {
|
||||||
await copy(existing, true)
|
await copy(existing, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = await sdk.client.session
|
const url = await input.sdk.client.session
|
||||||
.share({ sessionID: params.id })
|
.share({ sessionID: input.params.id })
|
||||||
.then((res) => res.data?.share?.url)
|
.then((res) => res.data?.share?.url)
|
||||||
.catch(() => undefined)
|
.catch(() => undefined)
|
||||||
if (!url) {
|
if (!url) {
|
||||||
showToast({
|
showToast({
|
||||||
title: language.t("toast.session.share.failed.title"),
|
title: input.language.t("toast.session.share.failed.title"),
|
||||||
description: language.t("toast.session.share.failed.description"),
|
description: input.language.t("toast.session.share.failed.description"),
|
||||||
variant: "error",
|
variant: "error",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -457,25 +421,25 @@ export const useSessionCommands = (args: SessionCommandContext) => {
|
|||||||
}),
|
}),
|
||||||
sessionCommand({
|
sessionCommand({
|
||||||
id: "session.unshare",
|
id: "session.unshare",
|
||||||
title: language.t("command.session.unshare"),
|
title: input.language.t("command.session.unshare"),
|
||||||
description: language.t("command.session.unshare.description"),
|
description: input.language.t("command.session.unshare.description"),
|
||||||
slash: "unshare",
|
slash: "unshare",
|
||||||
disabled: !params.id || !info()?.share?.url,
|
disabled: !input.params.id || !input.info()?.share?.url,
|
||||||
onSelect: async () => {
|
onSelect: async () => {
|
||||||
if (!params.id) return
|
if (!input.params.id) return
|
||||||
await sdk.client.session
|
await input.sdk.client.session
|
||||||
.unshare({ sessionID: params.id })
|
.unshare({ sessionID: input.params.id })
|
||||||
.then(() =>
|
.then(() =>
|
||||||
showToast({
|
showToast({
|
||||||
title: language.t("toast.session.unshare.success.title"),
|
title: input.language.t("toast.session.unshare.success.title"),
|
||||||
description: language.t("toast.session.unshare.success.description"),
|
description: input.language.t("toast.session.unshare.success.description"),
|
||||||
variant: "success",
|
variant: "success",
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.catch(() =>
|
.catch(() =>
|
||||||
showToast({
|
showToast({
|
||||||
title: language.t("toast.session.unshare.failed.title"),
|
title: input.language.t("toast.session.unshare.failed.title"),
|
||||||
description: language.t("toast.session.unshare.failed.description"),
|
description: input.language.t("toast.session.unshare.failed.description"),
|
||||||
variant: "error",
|
variant: "error",
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -484,7 +448,7 @@ export const useSessionCommands = (args: SessionCommandContext) => {
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
command.register("session", () =>
|
input.command.register("session", () =>
|
||||||
[
|
[
|
||||||
sessionCommands(),
|
sessionCommands(),
|
||||||
fileCommands(),
|
fileCommands(),
|
||||||
@@ -495,6 +459,6 @@ export const useSessionCommands = (args: SessionCommandContext) => {
|
|||||||
permissionCommands(),
|
permissionCommands(),
|
||||||
sessionActionCommands(),
|
sessionActionCommands(),
|
||||||
shareCommands(),
|
shareCommands(),
|
||||||
].flatMap((section) => section),
|
].flatMap((x) => x),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -224,7 +224,6 @@ export default function () {
|
|||||||
{iife(() => {
|
{iife(() => {
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
messageId: undefined as string | undefined,
|
messageId: undefined as string | undefined,
|
||||||
expandedSteps: {} as Record<string, boolean>,
|
|
||||||
})
|
})
|
||||||
const messages = createMemo(() =>
|
const messages = createMemo(() =>
|
||||||
data().sessionID
|
data().sessionID
|
||||||
@@ -296,10 +295,7 @@ export default function () {
|
|||||||
{(message) => (
|
{(message) => (
|
||||||
<SessionTurn
|
<SessionTurn
|
||||||
sessionID={data().sessionID}
|
sessionID={data().sessionID}
|
||||||
sessionTitle={info().title}
|
|
||||||
messageID={message.id}
|
messageID={message.id}
|
||||||
stepsExpanded={store.expandedSteps[message.id] ?? false}
|
|
||||||
onStepsExpandedToggle={() => setStore("expandedSteps", message.id, (v) => !v)}
|
|
||||||
classes={{
|
classes={{
|
||||||
root: "min-w-0 w-full relative",
|
root: "min-w-0 w-full relative",
|
||||||
content: "flex flex-col justify-between !overflow-visible",
|
content: "flex flex-col justify-between !overflow-visible",
|
||||||
@@ -375,13 +371,6 @@ export default function () {
|
|||||||
<SessionTurn
|
<SessionTurn
|
||||||
sessionID={data().sessionID}
|
sessionID={data().sessionID}
|
||||||
messageID={store.messageId ?? firstUserMessage()!.id!}
|
messageID={store.messageId ?? firstUserMessage()!.id!}
|
||||||
stepsExpanded={
|
|
||||||
store.expandedSteps[store.messageId ?? firstUserMessage()!.id!] ?? false
|
|
||||||
}
|
|
||||||
onStepsExpandedToggle={() => {
|
|
||||||
const id = store.messageId ?? firstUserMessage()!.id!
|
|
||||||
setStore("expandedSteps", id, (v) => !v)
|
|
||||||
}}
|
|
||||||
classes={{
|
classes={{
|
||||||
root: "grow",
|
root: "grow",
|
||||||
content: "flex flex-col justify-between",
|
content: "flex flex-col justify-between",
|
||||||
|
|||||||
@@ -4,15 +4,44 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
gap: 20px;
|
gap: 0px;
|
||||||
justify-content: space-between;
|
justify-content: flex-start;
|
||||||
|
|
||||||
[data-slot="basic-tool-tool-trigger-content"] {
|
[data-slot="basic-tool-tool-trigger-content"] {
|
||||||
width: 100%;
|
width: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
gap: 20px;
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="basic-tool-tool-indicator"] {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
[data-component="spinner"] {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="basic-tool-tool-spinner"] {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: var(--text-weak);
|
||||||
|
|
||||||
|
[data-component="spinner"] {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="icon-svg"] {
|
[data-slot="icon-svg"] {
|
||||||
@@ -20,16 +49,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="basic-tool-tool-info"] {
|
[data-slot="basic-tool-tool-info"] {
|
||||||
flex-grow: 1;
|
flex: 0 1 auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="basic-tool-tool-info-structured"] {
|
[data-slot="basic-tool-tool-info-structured"] {
|
||||||
width: 100%;
|
width: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
justify-content: space-between;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="basic-tool-tool-info-main"] {
|
[data-slot="basic-tool-tool-info-main"] {
|
||||||
@@ -43,16 +73,21 @@
|
|||||||
[data-slot="basic-tool-tool-title"] {
|
[data-slot="basic-tool-tool-title"] {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
font-family: var(--font-family-sans);
|
font-family: var(--font-family-sans);
|
||||||
font-size: var(--font-size-small);
|
font-size: 14px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
line-height: var(--line-height-large);
|
line-height: var(--line-height-large);
|
||||||
letter-spacing: var(--letter-spacing-normal);
|
letter-spacing: var(--letter-spacing-normal);
|
||||||
color: var(--text-base);
|
color: var(--text-strong);
|
||||||
|
|
||||||
&.capitalize {
|
&.capitalize {
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.agent-title {
|
||||||
|
color: var(--text-strong);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="basic-tool-tool-subtitle"] {
|
[data-slot="basic-tool-tool-subtitle"] {
|
||||||
@@ -62,12 +97,12 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-family: var(--font-family-sans);
|
font-family: var(--font-family-sans);
|
||||||
font-size: var(--font-size-small);
|
font-size: 14px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-regular);
|
||||||
line-height: var(--line-height-large);
|
line-height: var(--line-height-large);
|
||||||
letter-spacing: var(--letter-spacing-normal);
|
letter-spacing: var(--letter-spacing-normal);
|
||||||
color: var(--text-weak);
|
color: var(--text-base);
|
||||||
|
|
||||||
&.clickable {
|
&.clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -78,6 +113,26 @@
|
|||||||
color: var(--text-base);
|
color: var(--text-base);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.subagent-link {
|
||||||
|
color: var(--text-interactive-base);
|
||||||
|
text-decoration: none;
|
||||||
|
text-underline-offset: 2px;
|
||||||
|
font-weight: var(--font-weight-regular);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--text-interactive-base);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
color: var(--text-interactive-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:visited {
|
||||||
|
color: var(--text-interactive-base);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="basic-tool-tool-arg"] {
|
[data-slot="basic-tool-tool-arg"] {
|
||||||
@@ -87,11 +142,11 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-family: var(--font-family-sans);
|
font-family: var(--font-family-sans);
|
||||||
font-size: var(--font-size-small);
|
font-size: 14px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: var(--font-weight-regular);
|
font-weight: var(--font-weight-regular);
|
||||||
line-height: var(--line-height-large);
|
line-height: var(--line-height-large);
|
||||||
letter-spacing: var(--letter-spacing-normal);
|
letter-spacing: var(--letter-spacing-normal);
|
||||||
color: var(--text-weak);
|
color: var(--text-base);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createEffect, createSignal, For, Match, Show, Switch, type JSX } from "solid-js"
|
import { createEffect, createSignal, For, Match, Show, Switch, type JSX } from "solid-js"
|
||||||
import { Collapsible } from "./collapsible"
|
import { Collapsible } from "./collapsible"
|
||||||
import { Icon, IconProps } from "./icon"
|
import type { IconProps } from "./icon"
|
||||||
|
import { TextShimmer } from "./text-shimmer"
|
||||||
|
|
||||||
export type TriggerTitle = {
|
export type TriggerTitle = {
|
||||||
title: string
|
title: string
|
||||||
@@ -22,6 +23,7 @@ export interface BasicToolProps {
|
|||||||
icon: IconProps["name"]
|
icon: IconProps["name"]
|
||||||
trigger: TriggerTitle | JSX.Element
|
trigger: TriggerTitle | JSX.Element
|
||||||
children?: JSX.Element
|
children?: JSX.Element
|
||||||
|
status?: string
|
||||||
hideDetails?: boolean
|
hideDetails?: boolean
|
||||||
defaultOpen?: boolean
|
defaultOpen?: boolean
|
||||||
forceOpen?: boolean
|
forceOpen?: boolean
|
||||||
@@ -31,22 +33,23 @@ export interface BasicToolProps {
|
|||||||
|
|
||||||
export function BasicTool(props: BasicToolProps) {
|
export function BasicTool(props: BasicToolProps) {
|
||||||
const [open, setOpen] = createSignal(props.defaultOpen ?? false)
|
const [open, setOpen] = createSignal(props.defaultOpen ?? false)
|
||||||
|
const pending = () => props.status === "pending" || props.status === "running"
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (props.forceOpen) setOpen(true)
|
if (props.forceOpen) setOpen(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleOpenChange = (value: boolean) => {
|
const handleOpenChange = (value: boolean) => {
|
||||||
|
if (pending()) return
|
||||||
if (props.locked && !value) return
|
if (props.locked && !value) return
|
||||||
setOpen(value)
|
setOpen(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible open={open()} onOpenChange={handleOpenChange}>
|
<Collapsible open={open()} onOpenChange={handleOpenChange} class="tool-collapsible">
|
||||||
<Collapsible.Trigger>
|
<Collapsible.Trigger>
|
||||||
<div data-component="tool-trigger">
|
<div data-component="tool-trigger">
|
||||||
<div data-slot="basic-tool-tool-trigger-content">
|
<div data-slot="basic-tool-tool-trigger-content">
|
||||||
<Icon name={props.icon} size="small" />
|
|
||||||
<div data-slot="basic-tool-tool-info">
|
<div data-slot="basic-tool-tool-info">
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={isTriggerTitle(props.trigger) && props.trigger}>
|
<Match when={isTriggerTitle(props.trigger) && props.trigger}>
|
||||||
@@ -59,41 +62,45 @@ export function BasicTool(props: BasicToolProps) {
|
|||||||
[trigger().titleClass ?? ""]: !!trigger().titleClass,
|
[trigger().titleClass ?? ""]: !!trigger().titleClass,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{trigger().title}
|
<Show when={pending()} fallback={trigger().title}>
|
||||||
|
<TextShimmer text={trigger().title} />
|
||||||
|
</Show>
|
||||||
</span>
|
</span>
|
||||||
<Show when={trigger().subtitle}>
|
<Show when={!pending()}>
|
||||||
<span
|
<Show when={trigger().subtitle}>
|
||||||
data-slot="basic-tool-tool-subtitle"
|
<span
|
||||||
classList={{
|
data-slot="basic-tool-tool-subtitle"
|
||||||
[trigger().subtitleClass ?? ""]: !!trigger().subtitleClass,
|
classList={{
|
||||||
clickable: !!props.onSubtitleClick,
|
[trigger().subtitleClass ?? ""]: !!trigger().subtitleClass,
|
||||||
}}
|
clickable: !!props.onSubtitleClick,
|
||||||
onClick={(e) => {
|
}}
|
||||||
if (props.onSubtitleClick) {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
if (props.onSubtitleClick) {
|
||||||
props.onSubtitleClick()
|
e.stopPropagation()
|
||||||
}
|
props.onSubtitleClick()
|
||||||
}}
|
}
|
||||||
>
|
}}
|
||||||
{trigger().subtitle}
|
>
|
||||||
</span>
|
{trigger().subtitle}
|
||||||
</Show>
|
</span>
|
||||||
<Show when={trigger().args?.length}>
|
</Show>
|
||||||
<For each={trigger().args}>
|
<Show when={trigger().args?.length}>
|
||||||
{(arg) => (
|
<For each={trigger().args}>
|
||||||
<span
|
{(arg) => (
|
||||||
data-slot="basic-tool-tool-arg"
|
<span
|
||||||
classList={{
|
data-slot="basic-tool-tool-arg"
|
||||||
[trigger().argsClass ?? ""]: !!trigger().argsClass,
|
classList={{
|
||||||
}}
|
[trigger().argsClass ?? ""]: !!trigger().argsClass,
|
||||||
>
|
}}
|
||||||
{arg}
|
>
|
||||||
</span>
|
{arg}
|
||||||
)}
|
</span>
|
||||||
</For>
|
)}
|
||||||
|
</For>
|
||||||
|
</Show>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Show when={trigger().action}>{trigger().action}</Show>
|
<Show when={!pending() && trigger().action}>{trigger().action}</Show>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Match>
|
</Match>
|
||||||
@@ -101,7 +108,7 @@ export function BasicTool(props: BasicToolProps) {
|
|||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Show when={props.children && !props.hideDetails && !props.locked}>
|
<Show when={props.children && !props.hideDetails && !props.locked && !pending()}>
|
||||||
<Collapsible.Arrow />
|
<Collapsible.Arrow />
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
@@ -113,6 +120,6 @@ export function BasicTool(props: BasicToolProps) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GenericTool(props: { tool: string; hideDetails?: boolean }) {
|
export function GenericTool(props: { tool: string; status?: string; hideDetails?: boolean }) {
|
||||||
return <BasicTool icon="mcp" trigger={{ title: props.tool }} hideDetails={props.hideDetails} />
|
return <BasicTool icon="mcp" status={props.status} trigger={{ title: props.tool }} hideDetails={props.hideDetails} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,3 +170,15 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-component="button"].titlebar-icon[data-variant="ghost"]:hover:not(:disabled) {
|
||||||
|
background-color: var(--surface-raised-base-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="button"].titlebar-icon[data-variant="ghost"][aria-expanded="true"] {
|
||||||
|
background-color: var(--surface-base-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="button"].titlebar-icon[data-variant="ghost"][aria-expanded="true"]:hover:not(:disabled) {
|
||||||
|
background-color: var(--surface-base-active);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[data-component="checkbox"] {
|
[data-component="checkbox"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: var(--checkbox-align, center);
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
|
margin-top: var(--checkbox-offset, 0px);
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
|
|||||||
@@ -2,23 +2,44 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: var(--surface-inset-base);
|
background-color: transparent;
|
||||||
border: 1px solid var(--border-weaker-base);
|
border: none;
|
||||||
transition: background-color 0.15s ease;
|
transition: background-color 0.15s ease;
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
overflow: clip;
|
overflow: clip;
|
||||||
|
|
||||||
|
&.tool-collapsible {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
[data-slot="collapsible-trigger"] {
|
[data-slot="collapsible-trigger"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
padding: 6px 8px 6px 12px;
|
padding: 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: var(--text-base);
|
color: var(--text-base);
|
||||||
|
|
||||||
|
[data-slot="collapsible-arrow"] {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="collapsible-arrow-icon"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="collapsible-arrow-icon"][data-direction="right"] {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover [data-slot="collapsible-arrow"] {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* text-12-medium */
|
/* text-12-medium */
|
||||||
font-family: var(--font-family-sans);
|
font-family: var(--font-family-sans);
|
||||||
font-size: var(--font-size-small);
|
font-size: var(--font-size-small);
|
||||||
@@ -48,6 +69,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-slot="collapsible-trigger"][aria-expanded="true"] {
|
||||||
|
[data-slot="collapsible-arrow"] {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="collapsible-arrow-icon"][data-direction="right"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="collapsible-arrow-icon"][data-direction="down"] {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[data-slot="collapsible-content"] {
|
[data-slot="collapsible-content"] {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
/* animation: slideUp 250ms ease-out; */
|
/* animation: slideUp 250ms ease-out; */
|
||||||
|
|||||||
@@ -34,7 +34,12 @@ function CollapsibleContent(props: ComponentProps<typeof Kobalte.Content>) {
|
|||||||
function CollapsibleArrow(props?: ComponentProps<"div">) {
|
function CollapsibleArrow(props?: ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div data-slot="collapsible-arrow" {...(props || {})}>
|
<div data-slot="collapsible-arrow" {...(props || {})}>
|
||||||
<Icon name="chevron-grabber-vertical" size="small" />
|
<span data-slot="collapsible-arrow-icon" data-direction="right">
|
||||||
|
<Icon name="chevron-right" size="small" />
|
||||||
|
</span>
|
||||||
|
<span data-slot="collapsible-arrow-icon" data-direction="down">
|
||||||
|
<Icon name="chevron-down" size="small" />
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
[data-slot="diff-changes-additions"] {
|
[data-slot="diff-changes-additions"] {
|
||||||
font-family: var(--font-family-mono);
|
font-family: var(--font-family-mono);
|
||||||
font-feature-settings: var(--font-family-mono--font-feature-settings);
|
font-feature-settings: var(--font-family-mono--font-feature-settings);
|
||||||
font-size: var(--font-size-small);
|
font-size: 14px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: var(--font-weight-regular);
|
font-weight: var(--font-weight-regular);
|
||||||
line-height: var(--line-height-large);
|
line-height: var(--line-height-large);
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
[data-slot="diff-changes-deletions"] {
|
[data-slot="diff-changes-deletions"] {
|
||||||
font-family: var(--font-family-mono);
|
font-family: var(--font-family-mono);
|
||||||
font-feature-settings: var(--font-family-mono--font-feature-settings);
|
font-feature-settings: var(--font-family-mono--font-feature-settings);
|
||||||
font-size: var(--font-size-small);
|
font-size: 14px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: var(--font-weight-regular);
|
font-weight: var(--font-weight-regular);
|
||||||
line-height: var(--line-height-large);
|
line-height: var(--line-height-large);
|
||||||
@@ -31,11 +31,12 @@
|
|||||||
|
|
||||||
[data-component="diff-changes"][data-variant="bars"] {
|
[data-component="diff-changes"][data-variant="bars"] {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
|
height: 14px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,10 +96,10 @@ export function DiffChanges(props: {
|
|||||||
<div data-component="diff-changes" data-variant={variant()} classList={{ [props.class ?? ""]: true }}>
|
<div data-component="diff-changes" data-variant={variant()} classList={{ [props.class ?? ""]: true }}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={variant() === "bars"}>
|
<Match when={variant() === "bars"}>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 12" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 14" fill="none">
|
||||||
<g>
|
<g>
|
||||||
<For each={visibleBlocks()}>
|
<For each={visibleBlocks()}>
|
||||||
{(color, i) => <rect x={i() * 4} width="2" height="12" rx="1" fill={color} />}
|
{(color, i) => <rect x={i() * 4} width="2" height="14" rx="1" fill={color} />}
|
||||||
</For>
|
</For>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -143,3 +143,39 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
[data-component="icon-button"][data-icon="stop"] [data-slot="icon-svg"] rect {
|
||||||
|
transform-origin: center;
|
||||||
|
transform-box: fill-box;
|
||||||
|
animation: stop-pulse 1.8s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes stop-pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="icon-button"].titlebar-icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 24px;
|
||||||
|
aspect-ratio: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="icon-button"].titlebar-icon[data-variant="ghost"]:hover:not(:disabled) {
|
||||||
|
background-color: var(--surface-raised-base-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="icon-button"].titlebar-icon[data-variant="ghost"][aria-expanded="true"] {
|
||||||
|
background-color: var(--surface-base-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="icon-button"].titlebar-icon[data-variant="ghost"][aria-expanded="true"]:hover:not(:disabled) {
|
||||||
|
background-color: var(--surface-base-active);
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export function IconButton(props: ComponentProps<"button"> & IconButtonProps) {
|
|||||||
<Kobalte
|
<Kobalte
|
||||||
{...rest}
|
{...rest}
|
||||||
data-component="icon-button"
|
data-component="icon-button"
|
||||||
|
data-icon={props.icon}
|
||||||
data-size={split.size || "normal"}
|
data-size={split.size || "normal"}
|
||||||
data-variant={split.variant || "secondary"}
|
data-variant={split.variant || "secondary"}
|
||||||
classList={{
|
classList={{
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ const icons = {
|
|||||||
"arrow-right": `<path d="M11.6654 4.58398L17.082 10.0007L11.6654 15.4173M16.6654 10.0007H2.91536" stroke="currentColor" stroke-linecap="square"/>`,
|
"arrow-right": `<path d="M11.6654 4.58398L17.082 10.0007L11.6654 15.4173M16.6654 10.0007H2.91536" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
archive: `<path d="M16.8747 6.24935H17.3747V5.74935H16.8747V6.24935ZM16.8747 16.8743V17.3743H17.3747V16.8743H16.8747ZM3.12467 16.8743H2.62467V17.3743H3.12467V16.8743ZM3.12467 6.24935V5.74935H2.62467V6.24935H3.12467ZM2.08301 2.91602V2.41602H1.58301V2.91602H2.08301ZM17.9163 2.91602H18.4163V2.41602H17.9163V2.91602ZM17.9163 6.24935V6.74935H18.4163V6.24935H17.9163ZM2.08301 6.24935H1.58301V6.74935H2.08301V6.24935ZM8.33301 9.08268H7.83301V10.0827H8.33301V9.58268V9.08268ZM11.6663 10.0827H12.1663V9.08268H11.6663V9.58268V10.0827ZM16.8747 6.24935H16.3747V16.8743H16.8747H17.3747V6.24935H16.8747ZM16.8747 16.8743V16.3743H3.12467V16.8743V17.3743H16.8747V16.8743ZM3.12467 16.8743H3.62467V6.24935H3.12467H2.62467V16.8743H3.12467ZM3.12467 6.24935V6.74935H16.8747V6.24935V5.74935H3.12467V6.24935ZM2.08301 2.91602V3.41602H17.9163V2.91602V2.41602H2.08301V2.91602ZM17.9163 2.91602H17.4163V6.24935H17.9163H18.4163V2.91602H17.9163ZM17.9163 6.24935V5.74935H2.08301V6.24935V6.74935H17.9163V6.24935ZM2.08301 6.24935H2.58301V2.91602H2.08301H1.58301V6.24935H2.08301ZM8.33301 9.58268V10.0827H11.6663V9.58268V9.08268H8.33301V9.58268Z" fill="currentColor"/>`,
|
archive: `<path d="M16.8747 6.24935H17.3747V5.74935H16.8747V6.24935ZM16.8747 16.8743V17.3743H17.3747V16.8743H16.8747ZM3.12467 16.8743H2.62467V17.3743H3.12467V16.8743ZM3.12467 6.24935V5.74935H2.62467V6.24935H3.12467ZM2.08301 2.91602V2.41602H1.58301V2.91602H2.08301ZM17.9163 2.91602H18.4163V2.41602H17.9163V2.91602ZM17.9163 6.24935V6.74935H18.4163V6.24935H17.9163ZM2.08301 6.24935H1.58301V6.74935H2.08301V6.24935ZM8.33301 9.08268H7.83301V10.0827H8.33301V9.58268V9.08268ZM11.6663 10.0827H12.1663V9.08268H11.6663V9.58268V10.0827ZM16.8747 6.24935H16.3747V16.8743H16.8747H17.3747V6.24935H16.8747ZM16.8747 16.8743V16.3743H3.12467V16.8743V17.3743H16.8747V16.8743ZM3.12467 16.8743H3.62467V6.24935H3.12467H2.62467V16.8743H3.12467ZM3.12467 6.24935V6.74935H16.8747V6.24935V5.74935H3.12467V6.24935ZM2.08301 2.91602V3.41602H17.9163V2.91602V2.41602H2.08301V2.91602ZM17.9163 2.91602H17.4163V6.24935H17.9163H18.4163V2.91602H17.9163ZM17.9163 6.24935V5.74935H2.08301V6.24935V6.74935H17.9163V6.24935ZM2.08301 6.24935H2.58301V2.91602H2.08301H1.58301V6.24935H2.08301ZM8.33301 9.58268V10.0827H11.6663V9.58268V9.08268H8.33301V9.58268Z" fill="currentColor"/>`,
|
||||||
"bubble-5": `<path d="M18.3327 9.99935C18.3327 5.57227 15.0919 2.91602 9.99935 2.91602C4.90676 2.91602 1.66602 5.57227 1.66602 9.99935C1.66602 11.1487 2.45505 13.1006 2.57637 13.3939C2.58707 13.4197 2.59766 13.4434 2.60729 13.4697C2.69121 13.6987 3.04209 14.9354 1.66602 16.7674C3.51787 17.6528 5.48453 16.1973 5.48453 16.1973C6.84518 16.9193 8.46417 17.0827 9.99935 17.0827C15.0919 17.0827 18.3327 14.4264 18.3327 9.99935Z" stroke="currentColor" stroke-linecap="square"/>`,
|
"bubble-5": `<path d="M18.3327 9.99935C18.3327 5.57227 15.0919 2.91602 9.99935 2.91602C4.90676 2.91602 1.66602 5.57227 1.66602 9.99935C1.66602 11.1487 2.45505 13.1006 2.57637 13.3939C2.58707 13.4197 2.59766 13.4434 2.60729 13.4697C2.69121 13.6987 3.04209 14.9354 1.66602 16.7674C3.51787 17.6528 5.48453 16.1973 5.48453 16.1973C6.84518 16.9193 8.46417 17.0827 9.99935 17.0827C15.0919 17.0827 18.3327 14.4264 18.3327 9.99935Z" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
|
prompt: `<path d="M14.5841 12.0807H17.9193V2.91406H5.6276V6.2474M14.5859 6.2474H2.08594V15.4141H5.0026V17.4974L8.7526 15.4141H14.5859V6.2474Z" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
brain: `<path d="M13.332 8.7487C11.4911 8.7487 9.9987 7.25631 9.9987 5.41536M6.66536 11.2487C8.50631 11.2487 9.9987 12.7411 9.9987 14.582M9.9987 2.78209L9.9987 17.0658M16.004 15.0475C17.1255 14.5876 17.9154 13.4849 17.9154 12.1978C17.9154 11.3363 17.5615 10.5575 16.9913 9.9987C17.5615 9.43991 17.9154 8.66108 17.9154 7.79962C17.9154 6.21199 16.7136 4.90504 15.1702 4.73878C14.7858 3.21216 13.4039 2.08203 11.758 2.08203C11.1171 2.08203 10.5162 2.25337 9.9987 2.55275C9.48117 2.25337 8.88032 2.08203 8.23944 2.08203C6.59353 2.08203 5.21157 3.21216 4.82722 4.73878C3.28377 4.90504 2.08203 6.21199 2.08203 7.79962C2.08203 8.66108 2.43585 9.43991 3.00609 9.9987C2.43585 10.5575 2.08203 11.3363 2.08203 12.1978C2.08203 13.4849 2.87191 14.5876 3.99339 15.0475C4.46688 16.7033 5.9917 17.9154 7.79962 17.9154C8.61335 17.9154 9.36972 17.6698 9.9987 17.2488C10.6277 17.6698 11.384 17.9154 12.1978 17.9154C14.0057 17.9154 15.5305 16.7033 16.004 15.0475Z" stroke="currentColor"/>`,
|
brain: `<path d="M13.332 8.7487C11.4911 8.7487 9.9987 7.25631 9.9987 5.41536M6.66536 11.2487C8.50631 11.2487 9.9987 12.7411 9.9987 14.582M9.9987 2.78209L9.9987 17.0658M16.004 15.0475C17.1255 14.5876 17.9154 13.4849 17.9154 12.1978C17.9154 11.3363 17.5615 10.5575 16.9913 9.9987C17.5615 9.43991 17.9154 8.66108 17.9154 7.79962C17.9154 6.21199 16.7136 4.90504 15.1702 4.73878C14.7858 3.21216 13.4039 2.08203 11.758 2.08203C11.1171 2.08203 10.5162 2.25337 9.9987 2.55275C9.48117 2.25337 8.88032 2.08203 8.23944 2.08203C6.59353 2.08203 5.21157 3.21216 4.82722 4.73878C3.28377 4.90504 2.08203 6.21199 2.08203 7.79962C2.08203 8.66108 2.43585 9.43991 3.00609 9.9987C2.43585 10.5575 2.08203 11.3363 2.08203 12.1978C2.08203 13.4849 2.87191 14.5876 3.99339 15.0475C4.46688 16.7033 5.9917 17.9154 7.79962 17.9154C8.61335 17.9154 9.36972 17.6698 9.9987 17.2488C10.6277 17.6698 11.384 17.9154 12.1978 17.9154C14.0057 17.9154 15.5305 16.7033 16.004 15.0475Z" stroke="currentColor"/>`,
|
||||||
"bullet-list": `<path d="M9.58329 13.7497H17.0833M9.58329 6.24967H17.0833M6.24996 6.24967C6.24996 7.17015 5.50377 7.91634 4.58329 7.91634C3.66282 7.91634 2.91663 7.17015 2.91663 6.24967C2.91663 5.3292 3.66282 4.58301 4.58329 4.58301C5.50377 4.58301 6.24996 5.3292 6.24996 6.24967ZM6.24996 13.7497C6.24996 14.6701 5.50377 15.4163 4.58329 15.4163C3.66282 15.4163 2.91663 14.6701 2.91663 13.7497C2.91663 12.8292 3.66282 12.083 4.58329 12.083C5.50377 12.083 6.24996 12.8292 6.24996 13.7497Z" stroke="currentColor" stroke-linecap="square"/>`,
|
"bullet-list": `<path d="M9.58329 13.7497H17.0833M9.58329 6.24967H17.0833M6.24996 6.24967C6.24996 7.17015 5.50377 7.91634 4.58329 7.91634C3.66282 7.91634 2.91663 7.17015 2.91663 6.24967C2.91663 5.3292 3.66282 4.58301 4.58329 4.58301C5.50377 4.58301 6.24996 5.3292 6.24996 6.24967ZM6.24996 13.7497C6.24996 14.6701 5.50377 15.4163 4.58329 15.4163C3.66282 15.4163 2.91663 14.6701 2.91663 13.7497C2.91663 12.8292 3.66282 12.083 4.58329 12.083C5.50377 12.083 6.24996 12.8292 6.24996 13.7497Z" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
"check-small": `<path d="M6.5 11.4412L8.97059 13.5L13.5 6.5" stroke="currentColor" stroke-linecap="square"/>`,
|
"check-small": `<path d="M6.5 11.4412L8.97059 13.5L13.5 6.5" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
"chevron-down": `<path d="M6.6665 8.33325L9.99984 11.6666L13.3332 8.33325" stroke="currentColor" stroke-linecap="square"/>`,
|
"chevron-down": `<path d="M6.6665 8.33325L9.99984 11.6666L13.3332 8.33325" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
"chevron-right": `<path d="M8.33301 13.3327L11.6663 9.99935L8.33301 6.66602" stroke="currentColor" stroke-linecap="square"/>`,
|
"chevron-left": `<path d="M12 15L7 10L12 5" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
|
"chevron-right": `<path d="M8 15L13 10L8 5" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
"chevron-grabber-vertical": `<path d="M6.66675 12.4998L10.0001 15.8332L13.3334 12.4998M6.66675 7.49984L10.0001 4.1665L13.3334 7.49984" stroke="currentColor" stroke-linecap="square"/>`,
|
"chevron-grabber-vertical": `<path d="M6.66675 12.4998L10.0001 15.8332L13.3334 12.4998M6.66675 7.49984L10.0001 4.1665L13.3334 7.49984" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
"chevron-double-right": `<path d="M11.6654 13.3346L14.9987 10.0013L11.6654 6.66797M5.83203 13.3346L9.16536 10.0013L5.83203 6.66797" stroke="currentColor" stroke-linecap="square"/>`,
|
"chevron-double-right": `<path d="M11.6654 13.3346L14.9987 10.0013L11.6654 6.66797M5.83203 13.3346L9.16536 10.0013L5.83203 6.66797" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
"circle-x": `<path fill-rule="evenodd" clip-rule="evenodd" d="M1.6665 10.0003C1.6665 5.39795 5.39746 1.66699 9.99984 1.66699C14.6022 1.66699 18.3332 5.39795 18.3332 10.0003C18.3332 14.6027 14.6022 18.3337 9.99984 18.3337C5.39746 18.3337 1.6665 14.6027 1.6665 10.0003ZM7.49984 6.91107L6.91058 7.50033L9.41058 10.0003L6.91058 12.5003L7.49984 13.0896L9.99984 10.5896L12.4998 13.0896L13.0891 12.5003L10.5891 10.0003L13.0891 7.50033L12.4998 6.91107L9.99984 9.41107L7.49984 6.91107Z" fill="currentColor"/>`,
|
"circle-x": `<path fill-rule="evenodd" clip-rule="evenodd" d="M1.6665 10.0003C1.6665 5.39795 5.39746 1.66699 9.99984 1.66699C14.6022 1.66699 18.3332 5.39795 18.3332 10.0003C18.3332 14.6027 14.6022 18.3337 9.99984 18.3337C5.39746 18.3337 1.6665 14.6027 1.6665 10.0003ZM7.49984 6.91107L6.91058 7.50033L9.41058 10.0003L6.91058 12.5003L7.49984 13.0896L9.99984 10.5896L12.4998 13.0896L13.0891 12.5003L10.5891 10.0003L13.0891 7.50033L12.4998 6.91107L9.99984 9.41107L7.49984 6.91107Z" fill="currentColor"/>`,
|
||||||
@@ -28,9 +30,12 @@ const icons = {
|
|||||||
eye: `<path d="M10 4.58325C5.83333 4.58325 2.5 9.99992 2.5 9.99992C2.5 9.99992 5.83333 15.4166 10 15.4166C14.1667 15.4166 17.5 9.99992 17.5 9.99992C17.5 9.99992 14.1667 4.58325 10 4.58325Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><circle cx="10" cy="10" r="2.5" stroke="currentColor"/>`,
|
eye: `<path d="M10 4.58325C5.83333 4.58325 2.5 9.99992 2.5 9.99992C2.5 9.99992 5.83333 15.4166 10 15.4166C14.1667 15.4166 17.5 9.99992 17.5 9.99992C17.5 9.99992 14.1667 4.58325 10 4.58325Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><circle cx="10" cy="10" r="2.5" stroke="currentColor"/>`,
|
||||||
enter: `<path d="M5.83333 15.8334L2.5 12.5L5.83333 9.16671M3.33333 12.5H17.9167V4.58337H10" stroke="currentColor" stroke-linecap="square"/>`,
|
enter: `<path d="M5.83333 15.8334L2.5 12.5L5.83333 9.16671M3.33333 12.5H17.9167V4.58337H10" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
folder: `<path d="M2.08301 2.91675V16.2501H17.9163V5.41675H9.99967L8.33301 2.91675H2.08301Z" stroke="currentColor" stroke-linecap="round"/>`,
|
folder: `<path d="M2.08301 2.91675V16.2501H17.9163V5.41675H9.99967L8.33301 2.91675H2.08301Z" stroke="currentColor" stroke-linecap="round"/>`,
|
||||||
|
"file-tree": `<path d="M4.58203 16.6693L6.66536 9.58594H17.082M4.58203 16.6693H16.457L18.5404 9.58594H17.082M4.58203 16.6693H2.08203V3.33594H8.33203L9.9987 5.83594H17.082V9.58594" stroke="currentColor" stroke-linecap="round"/>`,
|
||||||
|
"file-tree-active": `<path d="M6.66536 9.58594L4.58203 16.6693H16.457L18.5404 9.58594H17.082H6.66536Z" fill="currentColor" fill-opacity="40%"/><path d="M4.58203 16.6693L6.66536 9.58594H17.082M4.58203 16.6693H16.457L18.5404 9.58594H17.082M4.58203 16.6693H2.08203V3.33594H8.33203L9.9987 5.83594H17.082V9.58594" stroke="currentColor" stroke-linecap="round"/>`,
|
||||||
"magnifying-glass": `<path d="M15.8332 15.8337L13.0819 13.0824M14.6143 9.39088C14.6143 12.2759 12.2755 14.6148 9.39039 14.6148C6.50532 14.6148 4.1665 12.2759 4.1665 9.39088C4.1665 6.5058 6.50532 4.16699 9.39039 4.16699C12.2755 4.16699 14.6143 6.5058 14.6143 9.39088Z" stroke="currentColor" stroke-linecap="square"/>`,
|
"magnifying-glass": `<path d="M15.8332 15.8337L13.0819 13.0824M14.6143 9.39088C14.6143 12.2759 12.2755 14.6148 9.39039 14.6148C6.50532 14.6148 4.1665 12.2759 4.1665 9.39088C4.1665 6.5058 6.50532 4.16699 9.39039 4.16699C12.2755 4.16699 14.6143 6.5058 14.6143 9.39088Z" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
"plus-small": `<path d="M9.99984 5.41699V10.0003M9.99984 10.0003V14.5837M9.99984 10.0003H5.4165M9.99984 10.0003H14.5832" stroke="currentColor" stroke-linecap="square"/>`,
|
"plus-small": `<path d="M9.99984 5.41699V10.0003M9.99984 10.0003V14.5837M9.99984 10.0003H5.4165M9.99984 10.0003H14.5832" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
plus: `<path d="M9.9987 2.20703V9.9987M9.9987 9.9987V17.7904M9.9987 9.9987H2.20703M9.9987 9.9987H17.7904" stroke="currentColor" stroke-linecap="square"/>`,
|
plus: `<path d="M9.9987 2.20703V9.9987M9.9987 9.9987V17.7904M9.9987 9.9987H2.20703M9.9987 9.9987H17.7904" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
|
"new-session": `<path d="M17.0827 17.0807V17.5807H17.5827V17.0807H17.0827ZM2.91602 17.0807H2.41602L2.41602 17.5807H2.91602L2.91602 17.0807ZM2.91602 2.91406V2.41406H2.41602V2.91406H2.91602ZM9.58268 3.41406H10.0827V2.41406L9.58268 2.41406V2.91406V3.41406ZM17.5827 10.4141V9.91406L16.5827 9.91406V10.4141H17.0827H17.5827ZM6.24935 11.2474L5.8958 10.8938L5.74935 11.0403V11.2474H6.24935ZM6.24935 13.7474H5.74935V14.2474H6.24935V13.7474ZM8.74935 13.7474V14.2474H8.95646L9.1029 14.101L8.74935 13.7474ZM15.2077 2.28906L15.5612 1.93551L15.2077 1.58196L14.8541 1.93551L15.2077 2.28906ZM17.7077 4.78906L18.0612 5.14262L18.4148 4.78906L18.0612 4.43551L17.7077 4.78906ZM17.0827 17.0807V16.5807H2.91602V17.0807L2.91602 17.5807H17.0827V17.0807ZM2.91602 17.0807H3.41602L3.41602 2.91406H2.91602H2.41602L2.41602 17.0807H2.91602ZM2.91602 2.91406V3.41406L9.58268 3.41406V2.91406V2.41406L2.91602 2.41406V2.91406ZM17.0827 10.4141H16.5827V17.0807H17.0827H17.5827V10.4141H17.0827ZM6.24935 11.2474H5.74935V13.7474H6.24935H6.74935V11.2474H6.24935ZM6.24935 13.7474V14.2474L8.74935 14.2474V13.7474V13.2474L6.24935 13.2474V13.7474ZM6.24935 11.2474L6.6029 11.6009L15.5612 2.64262L15.2077 2.28906L14.8541 1.93551L5.8958 10.8938L6.24935 11.2474ZM15.2077 2.28906L14.8541 2.64262L17.3541 5.14262L17.7077 4.78906L18.0612 4.43551L15.5612 1.93551L15.2077 2.28906ZM17.7077 4.78906L17.3541 4.43551L8.3958 13.3938L8.74935 13.7474L9.1029 14.101L18.0612 5.14262L17.7077 4.78906Z" fill="currentColor"/>`,
|
||||||
"pencil-line": `<path d="M9.58301 17.9166H17.9163M17.9163 5.83325L14.1663 2.08325L2.08301 14.1666V17.9166H5.83301L17.9163 5.83325Z" stroke="currentColor" stroke-linecap="square"/>`,
|
"pencil-line": `<path d="M9.58301 17.9166H17.9163M17.9163 5.83325L14.1663 2.08325L2.08301 14.1666V17.9166H5.83301L17.9163 5.83325Z" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
mcp: `<g><path d="M0.972656 9.37176L9.5214 1.60019C10.7018 0.527151 12.6155 0.527151 13.7957 1.60019C14.9761 2.67321 14.9761 4.41295 13.7957 5.48599L7.3397 11.3552" stroke="currentColor" stroke-linecap="round"/><path d="M7.42871 11.2747L13.7957 5.48643C14.9761 4.41338 16.8898 4.41338 18.0702 5.48643L18.1147 5.52688C19.2951 6.59993 19.2951 8.33966 18.1147 9.4127L10.3831 16.4414C9.98966 16.7991 9.98966 17.379 10.3831 17.7366L11.9707 19.1799" stroke="currentColor" stroke-linecap="round"/><path d="M11.6587 3.54346L5.33619 9.29119C4.15584 10.3642 4.15584 12.1039 5.33619 13.177C6.51649 14.25 8.43019 14.25 9.61054 13.177L15.9331 7.42923" stroke="currentColor" stroke-linecap="round"/></g>`,
|
mcp: `<g><path d="M0.972656 9.37176L9.5214 1.60019C10.7018 0.527151 12.6155 0.527151 13.7957 1.60019C14.9761 2.67321 14.9761 4.41295 13.7957 5.48599L7.3397 11.3552" stroke="currentColor" stroke-linecap="round"/><path d="M7.42871 11.2747L13.7957 5.48643C14.9761 4.41338 16.8898 4.41338 18.0702 5.48643L18.1147 5.52688C19.2951 6.59993 19.2951 8.33966 18.1147 9.4127L10.3831 16.4414C9.98966 16.7991 9.98966 17.379 10.3831 17.7366L11.9707 19.1799" stroke="currentColor" stroke-linecap="round"/><path d="M11.6587 3.54346L5.33619 9.29119C4.15584 10.3642 4.15584 12.1039 5.33619 13.177C6.51649 14.25 8.43019 14.25 9.61054 13.177L15.9331 7.42923" stroke="currentColor" stroke-linecap="round"/></g>`,
|
||||||
glasses: `<path d="M0.416626 7.91667H1.66663M19.5833 7.91667H18.3333M11.866 7.57987C11.3165 7.26398 10.6793 7.08333 9.99996 7.08333C9.32061 7.08333 8.68344 7.26398 8.13389 7.57987M8.74996 10C8.74996 12.0711 7.07103 13.75 4.99996 13.75C2.92889 13.75 1.24996 12.0711 1.24996 10C1.24996 7.92893 2.92889 6.25 4.99996 6.25C7.07103 6.25 8.74996 7.92893 8.74996 10ZM18.75 10C18.75 12.0711 17.071 13.75 15 13.75C12.9289 13.75 11.25 12.0711 11.25 10C11.25 7.92893 12.9289 6.25 15 6.25C17.071 6.25 18.75 7.92893 18.75 10Z" stroke="currentColor" stroke-linecap="square"/>`,
|
glasses: `<path d="M0.416626 7.91667H1.66663M19.5833 7.91667H18.3333M11.866 7.57987C11.3165 7.26398 10.6793 7.08333 9.99996 7.08333C9.32061 7.08333 8.68344 7.26398 8.13389 7.57987M8.74996 10C8.74996 12.0711 7.07103 13.75 4.99996 13.75C2.92889 13.75 1.24996 12.0711 1.24996 10C1.24996 7.92893 2.92889 6.25 4.99996 6.25C7.07103 6.25 8.74996 7.92893 8.74996 10ZM18.75 10C18.75 12.0711 17.071 13.75 15 13.75C12.9289 13.75 11.25 12.0711 11.25 10C11.25 7.92893 12.9289 6.25 15 6.25C17.071 6.25 18.75 7.92893 18.75 10Z" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
@@ -41,9 +46,9 @@ const icons = {
|
|||||||
"layout-left": `<path d="M2.91675 2.91699L2.91675 2.41699L2.41675 2.41699L2.41675 2.91699L2.91675 2.91699ZM17.0834 2.91699L17.5834 2.91699L17.5834 2.41699L17.0834 2.41699L17.0834 2.91699ZM17.0834 17.0837L17.0834 17.5837L17.5834 17.5837L17.5834 17.0837L17.0834 17.0837ZM2.91675 17.0837L2.41675 17.0837L2.41675 17.5837L2.91675 17.5837L2.91675 17.0837ZM7.41674 17.0837L7.41674 17.5837L8.41674 17.5837L8.41674 17.0837L7.91674 17.0837L7.41674 17.0837ZM8.41674 2.91699L8.41674 2.41699L7.41674 2.41699L7.41674 2.91699L7.91674 2.91699L8.41674 2.91699ZM2.91675 2.91699L2.91675 3.41699L17.0834 3.41699L17.0834 2.91699L17.0834 2.41699L2.91675 2.41699L2.91675 2.91699ZM17.0834 2.91699L16.5834 2.91699L16.5834 17.0837L17.0834 17.0837L17.5834 17.0837L17.5834 2.91699L17.0834 2.91699ZM17.0834 17.0837L17.0834 16.5837L2.91675 16.5837L2.91675 17.0837L2.91675 17.5837L17.0834 17.5837L17.0834 17.0837ZM2.91675 17.0837L3.41675 17.0837L3.41675 2.91699L2.91675 2.91699L2.41675 2.91699L2.41675 17.0837L2.91675 17.0837ZM7.91674 17.0837L8.41674 17.0837L8.41674 2.91699L7.91674 2.91699L7.41674 2.91699L7.41674 17.0837L7.91674 17.0837Z" fill="currentColor"/>`,
|
"layout-left": `<path d="M2.91675 2.91699L2.91675 2.41699L2.41675 2.41699L2.41675 2.91699L2.91675 2.91699ZM17.0834 2.91699L17.5834 2.91699L17.5834 2.41699L17.0834 2.41699L17.0834 2.91699ZM17.0834 17.0837L17.0834 17.5837L17.5834 17.5837L17.5834 17.0837L17.0834 17.0837ZM2.91675 17.0837L2.41675 17.0837L2.41675 17.5837L2.91675 17.5837L2.91675 17.0837ZM7.41674 17.0837L7.41674 17.5837L8.41674 17.5837L8.41674 17.0837L7.91674 17.0837L7.41674 17.0837ZM8.41674 2.91699L8.41674 2.41699L7.41674 2.41699L7.41674 2.91699L7.91674 2.91699L8.41674 2.91699ZM2.91675 2.91699L2.91675 3.41699L17.0834 3.41699L17.0834 2.91699L17.0834 2.41699L2.91675 2.41699L2.91675 2.91699ZM17.0834 2.91699L16.5834 2.91699L16.5834 17.0837L17.0834 17.0837L17.5834 17.0837L17.5834 2.91699L17.0834 2.91699ZM17.0834 17.0837L17.0834 16.5837L2.91675 16.5837L2.91675 17.0837L2.91675 17.5837L17.0834 17.5837L17.0834 17.0837ZM2.91675 17.0837L3.41675 17.0837L3.41675 2.91699L2.91675 2.91699L2.41675 2.91699L2.41675 17.0837L2.91675 17.0837ZM7.91674 17.0837L8.41674 17.0837L8.41674 2.91699L7.91674 2.91699L7.41674 2.91699L7.41674 17.0837L7.91674 17.0837Z" fill="currentColor"/>`,
|
||||||
"layout-left-partial": `<path d="M2.91732 2.91602L7.91732 2.91602L7.91732 17.0827H2.91732L2.91732 2.91602Z" fill="currentColor" fill-opacity="40%" /><path d="M2.91732 2.91602L17.084 2.91602M2.91732 2.91602L2.91732 17.0827M2.91732 2.91602L7.91732 2.91602M17.084 2.91602L17.084 17.0827M17.084 2.91602L7.91732 2.91602M17.084 17.0827L2.91732 17.0827M17.084 17.0827L7.91732 17.0827M2.91732 17.0827H7.91732M7.91732 17.0827L7.91732 2.91602" stroke="currentColor" stroke-linecap="square"/>`,
|
"layout-left-partial": `<path d="M2.91732 2.91602L7.91732 2.91602L7.91732 17.0827H2.91732L2.91732 2.91602Z" fill="currentColor" fill-opacity="40%" /><path d="M2.91732 2.91602L17.084 2.91602M2.91732 2.91602L2.91732 17.0827M2.91732 2.91602L7.91732 2.91602M17.084 2.91602L17.084 17.0827M17.084 2.91602L7.91732 2.91602M17.084 17.0827L2.91732 17.0827M17.084 17.0827L7.91732 17.0827M2.91732 17.0827H7.91732M7.91732 17.0827L7.91732 2.91602" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
"layout-left-full": `<path d="M2.91732 2.91602L7.91732 2.91602L7.91732 17.0827H2.91732L2.91732 2.91602Z" fill="currentColor"/><path d="M2.91732 2.91602L17.084 2.91602M2.91732 2.91602L2.91732 17.0827M2.91732 2.91602L7.91732 2.91602M17.084 2.91602L17.084 17.0827M17.084 2.91602L7.91732 2.91602M17.084 17.0827L2.91732 17.0827M17.084 17.0827L7.91732 17.0827M2.91732 17.0827H7.91732M7.91732 17.0827L7.91732 2.91602" stroke="currentColor" stroke-linecap="square"/>`,
|
"layout-left-full": `<path d="M2.91732 2.91602L7.91732 2.91602L7.91732 17.0827H2.91732L2.91732 2.91602Z" fill="currentColor"/><path d="M2.91732 2.91602L17.084 2.91602M2.91732 2.91602L2.91732 17.0827M2.91732 2.91602L7.91732 2.91602M17.084 2.91602L17.084 17.0827M17.084 2.91602L7.91732 2.91602M17.084 17.0827L2.91732 17.0827M17.084 17.0827L7.91732 17.0827M2.91732 17.0827H7.91732M7.91732 17.0827L7.91732 2.91602" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
"layout-right": `<path d="M17.0832 2.91699L17.5832 2.91699L17.5832 2.41699L17.0832 2.41699L17.0832 2.91699ZM2.91651 2.91699L2.91651 2.41699L2.41651 2.41699L2.41651 2.91699L2.91651 2.91699ZM2.9165 17.0837L2.4165 17.0837L2.4165 17.5837L2.9165 17.5837L2.9165 17.0837ZM17.0832 17.0837L17.0832 17.5837L17.5832 17.5837L17.5832 17.0837L17.0832 17.0837ZM11.5832 17.0837L11.5832 17.5837L12.5832 17.5837L12.5832 17.0837L12.0832 17.0837L11.5832 17.0837ZM12.5832 2.91699L12.5832 2.41699L11.5832 2.41699L11.5832 2.91699L12.0832 2.91699L12.5832 2.91699ZM17.0832 2.91699L17.0832 2.41699L2.91651 2.41699L2.91651 2.91699L2.91651 3.41699L17.0832 3.41699L17.0832 2.91699ZM2.91651 2.91699L2.41651 2.91699L2.4165 17.0837L2.9165 17.0837L3.4165 17.0837L3.41651 2.91699L2.91651 2.91699ZM2.9165 17.0837L2.9165 17.5837L17.0832 17.5837L17.0832 17.0837L17.0832 16.5837L2.9165 16.5837L2.9165 17.0837ZM17.0832 17.0837L17.5832 17.0837L17.5832 2.91699L17.0832 2.91699L16.5832 2.91699L16.5832 17.0837L17.0832 17.0837ZM12.0832 17.0837L12.5832 17.0837L12.5832 2.91699L12.0832 2.91699L11.5832 2.91699L11.5832 17.0837L12.0832 17.0837Z" fill="currentColor"/>`,
|
"layout-right": `<path d="M2.91536 2.91406H2.36536V2.36406H2.91536V2.91406ZM2.91536 17.0807V17.6307H2.36536V17.0807H2.91536ZM17.082 17.0807H17.632V17.6307H17.082V17.0807ZM17.082 2.91406V2.36406H17.632V2.91406H17.082ZM6.9987 2.91406H6.4487V2.36406H6.9987V2.91406ZM6.9987 17.0807V17.6307H6.4487V17.0807H6.9987ZM2.91536 2.91406H3.46536V17.0807H2.91536H2.36536V2.91406H2.91536ZM2.91536 17.0807V16.5307H17.082V17.0807V17.6307H2.91536V17.0807ZM17.082 17.0807H16.532V2.91406H17.082H17.632V17.0807H17.082ZM17.082 2.91406V3.46406H2.91536V2.91406V2.36406H17.082V2.91406ZM6.9987 2.91406H7.5487V17.0807H6.9987H6.4487V2.91406H6.9987ZM17.082 17.0807L17.082 17.6307L6.9987 17.6307V17.0807V16.5307L17.082 16.5307L17.082 17.0807ZM6.9987 2.91406V2.36406H17.082V2.91406V3.46406H6.9987V2.91406Z" fill="currentColor"/>`,
|
||||||
"layout-right-partial": `<path d="M12.0827 2.91602L2.91602 2.91602L2.91602 17.0827L12.0827 17.0827L12.0827 2.91602Z" fill="currentColor" fill-opacity="40%" /><path d="M2.91602 2.91602L17.0827 2.91602L17.0827 17.0827L2.91602 17.0827M2.91602 2.91602L2.91602 17.0827M2.91602 2.91602L12.0827 2.91602L12.0827 17.0827L2.91602 17.0827" stroke="currentColor" stroke-linecap="square"/>`,
|
"layout-right-partial": `<path d="M17.082 17.0807L6.9987 17.0807V2.91406H17.082V17.0807Z" fill="currentColor" fill-opacity="40%" /><path d="M2.91536 2.91406H2.36536V2.36406H2.91536V2.91406ZM2.91536 17.0807V17.6307H2.36536V17.0807H2.91536ZM17.082 17.0807H17.632V17.6307H17.082V17.0807ZM17.082 2.91406V2.36406H17.632V2.91406H17.082ZM6.9987 2.91406H6.4487V2.36406H6.9987V2.91406ZM6.9987 17.0807V17.6307H6.4487V17.0807H6.9987ZM2.91536 2.91406H3.46536V17.0807H2.91536H2.36536V2.91406H2.91536ZM2.91536 17.0807V16.5307H17.082V17.0807V17.6307H2.91536V17.0807ZM17.082 17.0807H16.532V2.91406H17.082H17.632V17.0807H17.082ZM17.082 2.91406V3.46406H2.91536V2.91406V2.36406H17.082V2.91406ZM6.9987 2.91406H7.5487V17.0807H6.9987H6.4487V2.91406H6.9987ZM17.082 17.0807L17.082 17.6307L6.9987 17.6307V17.0807V16.5307L17.082 16.5307L17.082 17.0807ZM6.9987 2.91406V2.36406H17.082V2.91406V3.46406H6.9987V2.91406Z" fill="currentColor" />`,
|
||||||
"layout-right-full": `<path d="M12.0827 2.91602L2.91602 2.91602L2.91602 17.0827L12.0827 17.0827L12.0827 2.91602Z" fill="currentColor"/><path d="M2.91602 2.91602L17.0827 2.91602L17.0827 17.0827L2.91602 17.0827M2.91602 2.91602L2.91602 17.0827M2.91602 2.91602L12.0827 2.91602L12.0827 17.0827L2.91602 17.0827" stroke="currentColor" stroke-linecap="square"/>`,
|
"layout-right-full": `<path d="M17.082 17.0807L6.9987 17.0807V2.91406H17.082V17.0807Z" fill="currentColor" /><path d="M2.91536 2.91406H2.36536V2.36406H2.91536V2.91406ZM2.91536 17.0807V17.6307H2.36536V17.0807H2.91536ZM17.082 17.0807H17.632V17.6307H17.082V17.0807ZM17.082 2.91406V2.36406H17.632V2.91406H17.082ZM6.9987 2.91406H6.4487V2.36406H6.9987V2.91406ZM6.9987 17.0807V17.6307H6.4487V17.0807H6.9987ZM2.91536 2.91406H3.46536V17.0807H2.91536H2.36536V2.91406H2.91536ZM2.91536 17.0807V16.5307H17.082V17.0807V17.6307H2.91536V17.0807ZM17.082 17.0807H16.532V2.91406H17.082H17.632V17.0807H17.082ZM17.082 2.91406V3.46406H2.91536V2.91406V2.36406H17.082V2.91406ZM6.9987 2.91406H7.5487V17.0807H6.9987H6.4487V2.91406H6.9987ZM17.082 17.0807L17.082 17.6307L6.9987 17.6307V17.0807V16.5307L17.082 16.5307L17.082 17.0807ZM6.9987 2.91406V2.36406H17.082V2.91406V3.46406H6.9987V2.91406Z" fill="currentColor" />`,
|
||||||
"square-arrow-top-right": `<path d="M7.91675 2.9165H2.91675V17.0832H17.0834V12.0832M12.0834 2.9165H17.0834V7.9165M9.58342 10.4165L16.6667 3.33317" stroke="currentColor" stroke-linecap="square"/>`,
|
"square-arrow-top-right": `<path d="M7.91675 2.9165H2.91675V17.0832H17.0834V12.0832M12.0834 2.9165H17.0834V7.9165M9.58342 10.4165L16.6667 3.33317" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
"speech-bubble": `<path d="M18.3334 10.0003C18.3334 5.57324 15.0927 2.91699 10.0001 2.91699C4.90749 2.91699 1.66675 5.57324 1.66675 10.0003C1.66675 11.1497 2.45578 13.1016 2.5771 13.3949C2.5878 13.4207 2.59839 13.4444 2.60802 13.4706C2.69194 13.6996 3.04282 14.9364 1.66675 16.7684C3.5186 17.6538 5.48526 16.1982 5.48526 16.1982C6.84592 16.9202 8.46491 17.0837 10.0001 17.0837C15.0927 17.0837 18.3334 14.4274 18.3334 10.0003Z" stroke="currentColor" stroke-linecap="square"/>`,
|
"speech-bubble": `<path d="M18.3334 10.0003C18.3334 5.57324 15.0927 2.91699 10.0001 2.91699C4.90749 2.91699 1.66675 5.57324 1.66675 10.0003C1.66675 11.1497 2.45578 13.1016 2.5771 13.3949C2.5878 13.4207 2.59839 13.4444 2.60802 13.4706C2.69194 13.6996 3.04282 14.9364 1.66675 16.7684C3.5186 17.6538 5.48526 16.1982 5.48526 16.1982C6.84592 16.9202 8.46491 17.0837 10.0001 17.0837C15.0927 17.0837 18.3334 14.4274 18.3334 10.0003Z" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
comment: `<path d="M16.25 3.75H3.75V16.25L6.875 14.4643H16.25V3.75Z" stroke="currentColor" stroke-linecap="square"/>`,
|
comment: `<path d="M16.25 3.75H3.75V16.25L6.875 14.4643H16.25V3.75Z" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
|
|||||||
@@ -12,6 +12,6 @@
|
|||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: 0 0 0 1px var(--border-interactive-focus);
|
box-shadow: var(--inline-input-shadow, 0 0 0 1px var(--border-interactive-focus));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,17 @@ export type InlineInputProps = ComponentProps<"input"> & {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function InlineInput(props: InlineInputProps) {
|
export function InlineInput(props: InlineInputProps) {
|
||||||
const [local, others] = splitProps(props, ["class", "width"])
|
const [local, others] = splitProps(props, ["class", "width", "style"])
|
||||||
return <input data-component="inline-input" class={local.class} style={{ width: local.width }} {...others} />
|
|
||||||
|
const style = () => {
|
||||||
|
if (!local.style) return { width: local.width }
|
||||||
|
if (typeof local.style === "string") {
|
||||||
|
if (!local.width) return local.style
|
||||||
|
return `${local.style};width:${local.width}`
|
||||||
|
}
|
||||||
|
if (!local.width) return local.style
|
||||||
|
return { ...local.style, width: local.width }
|
||||||
|
}
|
||||||
|
|
||||||
|
return <input data-component="inline-input" class={local.class} style={style()} {...others} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
color: var(--text-base);
|
color: var(--text-strong);
|
||||||
font-family: var(--font-family-sans);
|
font-family: var(--font-family-sans);
|
||||||
font-size: var(--font-size-base); /* 14px */
|
font-size: var(--font-size-base); /* 14px */
|
||||||
line-height: var(--line-height-x-large);
|
line-height: var(--line-height-x-large);
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
.shiki {
|
.shiki {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
border: 0.5px solid var(--border-weak-base);
|
border: 0.5px solid var(--border-weak-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,11 +127,55 @@
|
|||||||
|
|
||||||
[data-slot="markdown-copy-button"] {
|
[data-slot="markdown-copy-button"] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 4px;
|
||||||
right: 8px;
|
right: 4px;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.15s ease;
|
transition: opacity 0.15s ease;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: attr(data-tooltip);
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: calc(100% + 4px);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 1000;
|
||||||
|
|
||||||
|
max-width: 320px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: var(--surface-float-base);
|
||||||
|
color: var(--text-invert-strong);
|
||||||
|
padding: 2px 8px;
|
||||||
|
border: 1px solid var(--border-weak-base, rgba(0, 0, 0, 0.07));
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
|
||||||
|
pointer-events: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
font-family: var(--font-family-sans);
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: var(--line-height-large);
|
||||||
|
letter-spacing: var(--letter-spacing-normal);
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="markdown-copy-button"]:hover::after,
|
||||||
|
[data-slot="markdown-copy-button"]:focus-visible::after {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="markdown-copy-button"][data-variant="secondary"] {
|
||||||
|
box-shadow: none;
|
||||||
|
border: 1px solid var(--border-weak-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="markdown-copy-button"][data-variant="secondary"] [data-slot="icon-svg"] {
|
||||||
|
color: var(--icon-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-component="markdown-code"]:hover [data-slot="markdown-copy-button"] {
|
[data-component="markdown-code"]:hover [data-slot="markdown-copy-button"] {
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ function createCopyButton(labels: CopyLabels) {
|
|||||||
button.setAttribute("data-size", "small")
|
button.setAttribute("data-size", "small")
|
||||||
button.setAttribute("data-slot", "markdown-copy-button")
|
button.setAttribute("data-slot", "markdown-copy-button")
|
||||||
button.setAttribute("aria-label", labels.copy)
|
button.setAttribute("aria-label", labels.copy)
|
||||||
button.setAttribute("title", labels.copy)
|
button.setAttribute("data-tooltip", labels.copy)
|
||||||
button.appendChild(createIcon(iconPaths.copy, "copy-icon"))
|
button.appendChild(createIcon(iconPaths.copy, "copy-icon"))
|
||||||
button.appendChild(createIcon(iconPaths.check, "check-icon"))
|
button.appendChild(createIcon(iconPaths.check, "check-icon"))
|
||||||
return button
|
return button
|
||||||
@@ -95,12 +95,12 @@ function setCopyState(button: HTMLButtonElement, labels: CopyLabels, copied: boo
|
|||||||
if (copied) {
|
if (copied) {
|
||||||
button.setAttribute("data-copied", "true")
|
button.setAttribute("data-copied", "true")
|
||||||
button.setAttribute("aria-label", labels.copied)
|
button.setAttribute("aria-label", labels.copied)
|
||||||
button.setAttribute("title", labels.copied)
|
button.setAttribute("data-tooltip", labels.copied)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
button.removeAttribute("data-copied")
|
button.removeAttribute("data-copied")
|
||||||
button.setAttribute("aria-label", labels.copy)
|
button.setAttribute("aria-label", labels.copy)
|
||||||
button.setAttribute("title", labels.copy)
|
button.setAttribute("data-tooltip", labels.copy)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupCodeCopy(root: HTMLDivElement, labels: CopyLabels) {
|
function setupCodeCopy(root: HTMLDivElement, labels: CopyLabels) {
|
||||||
|
|||||||
@@ -14,15 +14,27 @@
|
|||||||
font-weight: var(--font-weight-regular);
|
font-weight: var(--font-weight-regular);
|
||||||
line-height: var(--line-height-large);
|
line-height: var(--line-height-large);
|
||||||
letter-spacing: var(--letter-spacing-normal);
|
letter-spacing: var(--letter-spacing-normal);
|
||||||
color: var(--text-base);
|
color: var(--text-strong);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
align-self: stretch;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
|
&[data-interrupted] {
|
||||||
|
color: var(--text-weak);
|
||||||
|
}
|
||||||
|
|
||||||
[data-slot="user-message-attachments"] {
|
[data-slot="user-message-attachments"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-end;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
width: fit-content;
|
||||||
|
max-width: min(82%, 64ch);
|
||||||
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="user-message-attachment"] {
|
[data-slot="user-message-attachment"] {
|
||||||
@@ -71,15 +83,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-slot="user-message-body"] {
|
||||||
|
width: fit-content;
|
||||||
|
max-width: min(82%, 64ch);
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
[data-slot="user-message-text"] {
|
[data-slot="user-message-text"] {
|
||||||
position: relative;
|
display: inline-block;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--surface-weak);
|
background: var(--surface-base);
|
||||||
border: 1px solid var(--border-weak-base);
|
border: 1px solid var(--border-weak-base);
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
|
|
||||||
[data-highlight="file"] {
|
[data-highlight="file"] {
|
||||||
color: var(--syntax-property);
|
color: var(--syntax-property);
|
||||||
@@ -89,19 +110,36 @@
|
|||||||
color: var(--syntax-type);
|
color: var(--syntax-type);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="user-message-copy-wrapper"] {
|
max-width: 100%;
|
||||||
position: absolute;
|
}
|
||||||
top: 7px;
|
|
||||||
right: 7px;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.15s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover [data-slot="user-message-copy-wrapper"] {
|
[data-slot="user-message-copy-wrapper"] {
|
||||||
opacity: 1;
|
min-height: 24px;
|
||||||
|
margin-top: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
will-change: opacity;
|
||||||
|
|
||||||
|
[data-component="tooltip-trigger"] {
|
||||||
|
display: inline-flex;
|
||||||
|
width: fit-content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-slot="user-message-copy-wrapper"][data-interrupted] {
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover [data-slot="user-message-copy-wrapper"],
|
||||||
|
&:focus-within [data-slot="user-message-copy-wrapper"] {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.text-text-strong {
|
.text-text-strong {
|
||||||
color: var(--text-strong);
|
color: var(--text-strong);
|
||||||
}
|
}
|
||||||
@@ -115,21 +153,36 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
[data-slot="text-part-body"] {
|
[data-slot="text-part-body"] {
|
||||||
position: relative;
|
margin-top: 0;
|
||||||
margin-top: 32px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="text-part-copy-wrapper"] {
|
[data-slot="text-part-copy-wrapper"] {
|
||||||
position: absolute;
|
min-height: 24px;
|
||||||
top: -28px;
|
margin-top: 4px;
|
||||||
right: 8px;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
transition: opacity 0.15s ease;
|
transition: opacity 0.15s ease;
|
||||||
z-index: 1;
|
will-change: opacity;
|
||||||
|
|
||||||
|
[data-component="tooltip-trigger"] {
|
||||||
|
display: inline-flex;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="text-part-body"]:hover [data-slot="text-part-copy-wrapper"] {
|
[data-slot="text-part-copy-wrapper"][data-interrupted] {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover [data-slot="text-part-copy-wrapper"],
|
||||||
|
&:focus-within [data-slot="text-part-copy-wrapper"] {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-component="markdown"] {
|
[data-component="markdown"] {
|
||||||
@@ -146,7 +199,7 @@
|
|||||||
|
|
||||||
[data-component="markdown"] {
|
[data-component="markdown"] {
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
font-style: italic !important;
|
font-style: normal;
|
||||||
|
|
||||||
p:has(strong) {
|
p:has(strong) {
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
@@ -196,7 +249,8 @@
|
|||||||
|
|
||||||
[data-component="tool-output"] {
|
[data-component="tool-output"] {
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
padding: 8px 12px;
|
padding: 0;
|
||||||
|
margin-bottom: 24px;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -238,6 +292,79 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-slot="collapsible-content"]:has([data-component="edit-content"]),
|
||||||
|
[data-slot="collapsible-content"]:has([data-component="write-content"]),
|
||||||
|
[data-slot="collapsible-content"]:has([data-component="apply-patch-files"]) {
|
||||||
|
border: 1px solid var(--border-weak-base);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: transparent;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="bash-output"] {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid var(--border-weak-base);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: transparent;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
[data-slot="bash-copy"] {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: 4px;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover [data-slot="bash-copy"],
|
||||||
|
&:focus-within [data-slot="bash-copy"] {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="bash-copy"] [data-component="icon-button"][data-variant="secondary"] {
|
||||||
|
box-shadow: none;
|
||||||
|
border: 1px solid var(--border-weak-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="bash-copy"] [data-component="icon-button"][data-variant="secondary"] [data-slot="icon-svg"] {
|
||||||
|
color: var(--icon-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="bash-scroll"] {
|
||||||
|
width: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
max-height: 240px;
|
||||||
|
|
||||||
|
scrollbar-width: none;
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="bash-pre"] {
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="bash-pre"] code {
|
||||||
|
font-family: var(--font-family-mono);
|
||||||
|
font-feature-settings: var(--font-family-mono--font-feature-settings);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: var(--line-height-large);
|
||||||
|
white-space: pre-wrap;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="collapsible-content"]:has([data-component="edit-content"]) [data-component="edit-content"],
|
||||||
|
[data-slot="collapsible-content"]:has([data-component="write-content"]) [data-component="write-content"] {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
[data-component="edit-trigger"],
|
[data-component="edit-trigger"],
|
||||||
[data-component="write-trigger"] {
|
[data-component="write-trigger"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -258,9 +385,9 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 8px;
|
||||||
font-family: var(--font-family-sans);
|
font-family: var(--font-family-sans);
|
||||||
font-size: var(--font-size-base);
|
font-size: 14px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
line-height: var(--line-height-large);
|
line-height: var(--line-height-large);
|
||||||
@@ -268,18 +395,37 @@
|
|||||||
color: var(--text-base);
|
color: var(--text-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-slot="message-part-title-spinner"] {
|
||||||
|
margin-left: 4px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: var(--text-weak);
|
||||||
|
|
||||||
|
[data-component="spinner"] {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[data-slot="message-part-title-text"] {
|
[data-slot="message-part-title-text"] {
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
|
color: var(--text-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="message-part-title-filename"] {
|
[data-slot="message-part-title-filename"] {
|
||||||
/* No text-transform - preserve original filename casing */
|
/* No text-transform - preserve original filename casing */
|
||||||
|
font-weight: var(--font-weight-regular);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="message-part-path"] {
|
[data-slot="message-part-path"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
font-weight: var(--font-weight-regular);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="message-part-directory"] {
|
[data-slot="message-part-directory"] {
|
||||||
@@ -344,12 +490,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[data-component="todos"] {
|
[data-component="todos"] {
|
||||||
padding: 10px 12px 24px 48px;
|
padding: 10px 0 24px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
|
[data-component="checkbox"] {
|
||||||
|
--checkbox-align: flex-start;
|
||||||
|
--checkbox-offset: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
[data-slot="message-part-todo-content"] {
|
[data-slot="message-part-todo-content"] {
|
||||||
|
line-height: var(--line-height-normal);
|
||||||
|
|
||||||
&[data-completed="completed"] {
|
&[data-completed="completed"] {
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
color: var(--text-weaker);
|
color: var(--text-weaker);
|
||||||
@@ -357,41 +510,55 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-component="task-tools"] {
|
[data-component="context-tool-group-trigger"] {
|
||||||
padding: 8px 12px;
|
width: 100%;
|
||||||
|
min-height: 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
gap: 6px;
|
justify-content: flex-start;
|
||||||
|
gap: 0px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
[data-slot="task-tool-item"] {
|
[data-slot="context-tool-group-title"] {
|
||||||
|
min-width: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
color: var(--text-weak);
|
|
||||||
|
|
||||||
[data-slot="icon-svg"] {
|
|
||||||
flex-shrink: 0;
|
|
||||||
color: var(--icon-weak);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="task-tool-title"] {
|
|
||||||
font-family: var(--font-family-sans);
|
font-family: var(--font-family-sans);
|
||||||
font-size: var(--font-size-small);
|
font-size: 14px;
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
line-height: var(--line-height-large);
|
line-height: var(--line-height-large);
|
||||||
color: var(--text-weak);
|
color: var(--text-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="task-tool-subtitle"] {
|
[data-slot="context-tool-group-label"] {
|
||||||
font-family: var(--font-family-sans);
|
flex-shrink: 0;
|
||||||
font-size: var(--font-size-small);
|
}
|
||||||
font-weight: var(--font-weight-regular);
|
|
||||||
line-height: var(--line-height-large);
|
[data-slot="context-tool-group-summary"] {
|
||||||
color: var(--text-weaker);
|
flex-shrink: 1;
|
||||||
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
font-weight: var(--font-weight-regular);
|
||||||
|
color: var(--text-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="collapsible-arrow"] {
|
||||||
|
color: var(--icon-weaker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="context-tool-group-list"] {
|
||||||
|
padding: 6px 0 4px 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
|
||||||
|
[data-slot="context-tool-group-item"] {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 6px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,170 +716,322 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[data-component="question-prompt"] {
|
[data-component="question-prompt"] {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 12px;
|
gap: 0;
|
||||||
background-color: var(--surface-inset-base);
|
min-height: 0;
|
||||||
border-radius: 0 0 6px 6px;
|
max-height: var(--question-prompt-max-height, 100dvh);
|
||||||
gap: 12px;
|
|
||||||
|
|
||||||
[data-slot="question-tabs"] {
|
[data-slot="question-body"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
flex-direction: column;
|
||||||
flex-wrap: wrap;
|
gap: 16px;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 8px 8px 0;
|
||||||
|
background-color: var(--surface-raised-stronger-non-alpha);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: var(--shadow-xs-border);
|
||||||
|
overflow: clip;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
[data-slot="question-tab"] {
|
[data-slot="question-header"] {
|
||||||
padding: 4px 12px;
|
display: flex;
|
||||||
font-size: 13px;
|
align-items: center;
|
||||||
border-radius: 4px;
|
justify-content: space-between;
|
||||||
background-color: var(--surface-base);
|
gap: 12px;
|
||||||
color: var(--text-base);
|
padding: 0 10px;
|
||||||
border: none;
|
}
|
||||||
cursor: pointer;
|
|
||||||
transition:
|
|
||||||
color 0.15s,
|
|
||||||
background-color 0.15s;
|
|
||||||
|
|
||||||
&:hover {
|
[data-slot="question-header-title"] {
|
||||||
background-color: var(--surface-base-hover);
|
font-family: var(--font-family-sans);
|
||||||
}
|
font-size: 14px;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: var(--line-height-large);
|
||||||
|
color: var(--text-strong);
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&[data-active="true"] {
|
[data-slot="question-progress"] {
|
||||||
background-color: var(--surface-raised-base);
|
display: flex;
|
||||||
}
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&[data-answered="true"] {
|
[data-slot="question-progress-segment"] {
|
||||||
color: var(--text-strong);
|
width: 16px;
|
||||||
}
|
height: 16px;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 999px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
touch-action: manipulation;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
width: 16px;
|
||||||
|
height: 2px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background-color: var(--icon-weak-base);
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-active="true"]::after {
|
||||||
|
background-color: var(--icon-strong-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-answered="true"]::after {
|
||||||
|
background-color: var(--icon-interactive-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="question-content"] {
|
[data-slot="question-content"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 4px;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
[data-slot="question-text"] {
|
[data-slot="question-text"] {
|
||||||
font-size: 14px;
|
font-family: var(--font-family-sans);
|
||||||
color: var(--text-base);
|
font-size: 14px;
|
||||||
line-height: 1.5;
|
font-weight: var(--font-weight-medium);
|
||||||
}
|
line-height: var(--line-height-large);
|
||||||
|
color: var(--text-strong);
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="question-hint"] {
|
||||||
|
font-family: var(--font-family-sans);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: var(--font-weight-regular);
|
||||||
|
line-height: var(--line-height-large);
|
||||||
|
color: var(--text-weak);
|
||||||
|
padding: 0 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="question-options"] {
|
[data-slot="question-options"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 6px;
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 1px 1px 8px;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
|
||||||
[data-slot="question-option"] {
|
&::-webkit-scrollbar {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 2px;
|
|
||||||
padding: 8px 12px;
|
|
||||||
background-color: var(--surface-base);
|
|
||||||
border: 1px solid var(--border-weaker-base);
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: left;
|
|
||||||
width: 100%;
|
|
||||||
transition:
|
|
||||||
background-color 0.15s,
|
|
||||||
border-color 0.15s;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--surface-base-hover);
|
|
||||||
border-color: var(--border-default);
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-picked="true"] {
|
|
||||||
[data-component="icon"] {
|
|
||||||
position: absolute;
|
|
||||||
right: 12px;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
color: var(--text-strong);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="option-label"] {
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--text-base);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="option-description"] {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--text-weak);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="custom-input-form"] {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 8px 0;
|
|
||||||
align-items: stretch;
|
|
||||||
|
|
||||||
[data-slot="custom-input"] {
|
|
||||||
flex: 1;
|
|
||||||
padding: 8px 12px;
|
|
||||||
font-size: 14px;
|
|
||||||
border: 1px solid var(--border-default);
|
|
||||||
border-radius: 6px;
|
|
||||||
background-color: var(--surface-base);
|
|
||||||
color: var(--text-base);
|
|
||||||
outline: none;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-color: var(--border-focus);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: var(--text-weak);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="button"] {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="question-review"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
|
|
||||||
[data-slot="review-title"] {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[data-slot="review-item"] {
|
[data-slot="question-option"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: flex-start;
|
||||||
gap: 2px;
|
gap: 12px;
|
||||||
font-size: 13px;
|
padding: 8px 8px 8px 10px;
|
||||||
|
background-color: var(--surface-raised-stronger-non-alpha);
|
||||||
|
border: 1px solid var(--border-weak-base);
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: none;
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
background-color 0.15s ease,
|
||||||
|
border-color 0.15s ease,
|
||||||
|
box-shadow 0.15s ease;
|
||||||
|
|
||||||
[data-slot="review-label"] {
|
&:hover:not([data-picked="true"]) {
|
||||||
color: var(--text-weak);
|
background-color: var(--background-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-picked="true"] {
|
||||||
|
background-color: var(--surface-interactive-weak);
|
||||||
|
border-color: transparent;
|
||||||
|
box-shadow: var(--shadow-xs-border-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="question-option-check"] {
|
||||||
|
display: inline-flex;
|
||||||
|
transform: translateY(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="question-option-box"] {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
border: 1px solid var(--border-weak-base);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
transition:
|
||||||
|
background-color 0.15s ease,
|
||||||
|
border-color 0.15s ease;
|
||||||
|
|
||||||
|
[data-component="icon"] {
|
||||||
|
opacity: 0;
|
||||||
|
color: var(--icon-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-type="radio"] {
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="question-option-radio-dot"] {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background-color: var(--background-stronger);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-picked="true"] {
|
||||||
|
border-color: var(--icon-interactive-base);
|
||||||
|
|
||||||
|
[data-component="icon"] {
|
||||||
|
opacity: 1;
|
||||||
|
color: var(--icon-invert-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="review-value"] {
|
&[data-type="checkbox"] {
|
||||||
color: var(--text-strong);
|
background-color: var(--icon-interactive-base);
|
||||||
|
}
|
||||||
|
|
||||||
&[data-answered="false"] {
|
&[data-type="radio"] {
|
||||||
color: var(--text-weak);
|
background-color: var(--icon-interactive-base);
|
||||||
|
[data-slot="question-option-radio-dot"] {
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="question-actions"] {
|
[data-slot="question-option-main"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="option-label"] {
|
||||||
|
font-family: var(--font-family-sans);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: var(--line-height-large);
|
||||||
|
color: var(--text-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="option-description"] {
|
||||||
|
font-family: var(--font-family-sans);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: var(--font-weight-regular);
|
||||||
|
line-height: var(--line-height-large);
|
||||||
|
color: var(--text-base);
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="question-option"][data-custom="true"] {
|
||||||
|
[data-slot="option-description"] {
|
||||||
|
overflow: visible;
|
||||||
|
text-overflow: clip;
|
||||||
|
white-space: normal;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="question-custom"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="question-custom-input-wrap"] {
|
||||||
|
padding-left: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="question-custom-input"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
outline: none;
|
||||||
|
font-family: var(--font-family-sans);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: var(--font-weight-regular);
|
||||||
|
line-height: var(--line-height-large);
|
||||||
|
color: var(--text-base);
|
||||||
|
min-width: 0;
|
||||||
|
cursor: text;
|
||||||
|
resize: none;
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: var(--text-weak);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: 1px solid var(--border-interactive-base);
|
||||||
|
outline-offset: 2px;
|
||||||
|
border-radius: var(--radius-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="question-footer"] {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 32px 8px 8px;
|
||||||
|
background-color: var(--background-base);
|
||||||
|
border: 1px solid var(--border-weak-base);
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: clip;
|
||||||
|
margin-top: -24px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="question-footer-actions"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -720,7 +1039,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 8px 12px;
|
padding: 8px 0;
|
||||||
|
|
||||||
[data-slot="question-answer-item"] {
|
[data-slot="question-answer-item"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -746,18 +1065,13 @@
|
|||||||
[data-component="apply-patch-file"] {
|
[data-component="apply-patch-file"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-top: 1px solid var(--border-weaker-base);
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
border-top: 1px solid var(--border-weaker-base);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="apply-patch-file-header"] {
|
[data-slot="apply-patch-file-header"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
background-color: var(--surface-inset-base);
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="apply-patch-file-action"] {
|
[data-slot="apply-patch-file-action"] {
|
||||||
@@ -799,7 +1113,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-component="apply-patch-file"] + [data-component="apply-patch-file"] {
|
||||||
|
border-top: 1px solid var(--border-weaker-base);
|
||||||
|
}
|
||||||
|
|
||||||
[data-component="apply-patch-file-diff"] {
|
[data-component="apply-patch-file-diff"] {
|
||||||
|
border-top: 1px solid var(--border-weaker-base);
|
||||||
max-height: 420px;
|
max-height: 420px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,5 @@
|
|||||||
[data-component="session-turn"] {
|
[data-component="session-turn"] {
|
||||||
--session-turn-sticky-height: 0px;
|
--sticky-header-height: calc(var(--session-title-height, 0px) + 24px);
|
||||||
--sticky-header-height: calc(var(--session-title-height, 0px) + var(--session-turn-sticky-height, 0px) + 24px);
|
|
||||||
/* flex: 1; */
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@@ -30,525 +28,30 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
gap: 18px;
|
gap: 18px;
|
||||||
overflow-anchor: none;
|
overflow-anchor: none;
|
||||||
|
|
||||||
[data-slot="session-turn-badge"] {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: var(--font-family-mono);
|
|
||||||
font-size: var(--font-size-x-small);
|
|
||||||
font-weight: var(--font-weight-medium);
|
|
||||||
line-height: var(--line-height-normal);
|
|
||||||
white-space: nowrap;
|
|
||||||
color: var(--text-base);
|
|
||||||
background: var(--surface-raised-base);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-attachments"] {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
align-self: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-sticky"] {
|
|
||||||
width: calc(100% + 9px);
|
|
||||||
position: sticky;
|
|
||||||
top: var(--session-title-height, 0px);
|
|
||||||
z-index: 20;
|
|
||||||
background-color: var(--background-stronger);
|
|
||||||
margin-left: -9px;
|
|
||||||
padding-left: 9px;
|
|
||||||
/* padding-bottom: 12px; */
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background-color: var(--background-stronger);
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 32px;
|
|
||||||
background: linear-gradient(to bottom, var(--background-stronger), transparent);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-message-header"] {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
align-self: stretch;
|
|
||||||
height: 32px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="session-turn-message-content"] {
|
[data-slot="session-turn-message-content"] {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-component="user-message"] [data-slot="user-message-text"] {
|
[data-slot="session-turn-thinking"] {
|
||||||
max-height: var(--user-message-collapsed-height, 64px);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="user-message"][data-expanded="true"] [data-slot="user-message-text"] {
|
|
||||||
max-height: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="user-message"][data-can-expand="true"] [data-slot="user-message-text"] {
|
|
||||||
padding-right: 36px;
|
|
||||||
padding-bottom: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="user-message"][data-can-expand="true"]:not([data-expanded="true"])
|
|
||||||
[data-slot="user-message-text"]::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 8px;
|
|
||||||
bottom: 0px;
|
|
||||||
background:
|
|
||||||
linear-gradient(to bottom, transparent, var(--surface-weak)),
|
|
||||||
linear-gradient(to bottom, transparent, var(--surface-weak));
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="user-message"] [data-slot="user-message-text"] [data-slot="user-message-expand"] {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 6px;
|
|
||||||
right: 6px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="user-message"][data-can-expand="true"]
|
|
||||||
[data-slot="user-message-text"]
|
|
||||||
[data-slot="user-message-expand"],
|
|
||||||
[data-component="user-message"][data-expanded="true"]
|
|
||||||
[data-slot="user-message-text"]
|
|
||||||
[data-slot="user-message-expand"] {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 22px;
|
|
||||||
width: 22px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
background: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--text-weak);
|
|
||||||
|
|
||||||
[data-slot="icon-svg"] {
|
|
||||||
transition: transform 0.15s ease;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="user-message"][data-expanded="true"]
|
|
||||||
[data-slot="user-message-text"]
|
|
||||||
[data-slot="user-message-expand"]
|
|
||||||
[data-slot="icon-svg"] {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="user-message"] [data-slot="user-message-text"] [data-slot="user-message-expand"]:hover {
|
|
||||||
background: var(--surface-raised-base);
|
|
||||||
color: var(--text-base);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-user-badges"] {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding-left: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-message-title"] {
|
|
||||||
width: 100%;
|
|
||||||
font-size: var(--font-size-large);
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--text-strong);
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
min-width: 0;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-message-title"] h1 {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
min-width: 0;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-size: inherit;
|
|
||||||
font-weight: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-typewriter"] {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
min-width: 0;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-summary-section"] {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 24px;
|
|
||||||
align-items: flex-start;
|
|
||||||
align-self: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-summary-header"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 4px;
|
|
||||||
align-self: stretch;
|
|
||||||
|
|
||||||
[data-slot="session-turn-summary-title-row"] {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-response"] {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-response-copy-wrapper"] {
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
transition: opacity 0.15s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover [data-slot="session-turn-response-copy-wrapper"],
|
|
||||||
&:focus-within [data-slot="session-turn-response-copy-wrapper"] {
|
|
||||||
opacity: 1;
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: var(--font-size-base);
|
|
||||||
line-height: var(--line-height-x-large);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-summary-title"] {
|
|
||||||
font-size: 13px;
|
|
||||||
/* text-12-medium */
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--text-weak);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-markdown"],
|
|
||||||
[data-slot="session-turn-accordion"] [data-slot="accordion-content"] {
|
|
||||||
-webkit-user-select: text;
|
|
||||||
user-select: text;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-markdown"] {
|
|
||||||
&[data-diffs="true"] {
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-fade="true"] > * {
|
|
||||||
animation: fadeUp 0.4s ease-out forwards;
|
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
&:nth-child(1) {
|
|
||||||
animation-delay: 0.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(2) {
|
|
||||||
animation-delay: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(3) {
|
|
||||||
animation-delay: 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(4) {
|
|
||||||
animation-delay: 0.4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(5) {
|
|
||||||
animation-delay: 0.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(6) {
|
|
||||||
animation-delay: 0.6s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(7) {
|
|
||||||
animation-delay: 0.7s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(8) {
|
|
||||||
animation-delay: 0.8s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(9) {
|
|
||||||
animation-delay: 0.9s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(10) {
|
|
||||||
animation-delay: 1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(11) {
|
|
||||||
animation-delay: 1.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(12) {
|
|
||||||
animation-delay: 1.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(13) {
|
|
||||||
animation-delay: 1.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(14) {
|
|
||||||
animation-delay: 1.4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(15) {
|
|
||||||
animation-delay: 1.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(16) {
|
|
||||||
animation-delay: 1.6s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(17) {
|
|
||||||
animation-delay: 1.7s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(18) {
|
|
||||||
animation-delay: 1.8s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(19) {
|
|
||||||
animation-delay: 1.9s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(20) {
|
|
||||||
animation-delay: 2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(21) {
|
|
||||||
animation-delay: 2.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(22) {
|
|
||||||
animation-delay: 2.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(23) {
|
|
||||||
animation-delay: 2.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(24) {
|
|
||||||
animation-delay: 2.4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(25) {
|
|
||||||
animation-delay: 2.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(26) {
|
|
||||||
animation-delay: 2.6s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(27) {
|
|
||||||
animation-delay: 2.7s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(28) {
|
|
||||||
animation-delay: 2.8s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(29) {
|
|
||||||
animation-delay: 2.9s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(30) {
|
|
||||||
animation-delay: 3s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-summary-section"] {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
[data-slot="session-turn-summary-copy"] {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.15s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover [data-slot="session-turn-summary-copy"] {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-accordion"] {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="sticky-accordion-header"] {
|
|
||||||
top: var(--sticky-header-height, 0px);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="sticky-accordion-header"][data-expanded]::before,
|
|
||||||
[data-slot="accordion-item"][data-expanded] [data-component="sticky-accordion-header"]::before {
|
|
||||||
top: calc(-1 * var(--sticky-header-height, 0px));
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-accordion-trigger-content"] {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
gap: 20px;
|
|
||||||
|
|
||||||
[data-expandable="false"] {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-file-info"] {
|
|
||||||
flex-grow: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 20px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-file-icon"] {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-file-path"] {
|
|
||||||
display: flex;
|
|
||||||
flex-grow: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-directory"] {
|
|
||||||
color: var(--text-base);
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
direction: rtl;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-filename"] {
|
|
||||||
color: var(--text-strong);
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-accordion-actions"] {
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
gap: 16px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-accordion-content"] {
|
|
||||||
max-height: 240px;
|
|
||||||
/* max-h-60 */
|
|
||||||
overflow-y: auto;
|
|
||||||
scrollbar-width: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-accordion-content"]::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-response-section"] {
|
|
||||||
width: calc(100% + 9px);
|
|
||||||
min-width: 0;
|
|
||||||
margin-left: -9px;
|
|
||||||
padding-left: 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-collapsible"] {
|
|
||||||
gap: 32px;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-collapsible-trigger-content"] {
|
|
||||||
max-width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
color: var(--text-weak);
|
color: var(--text-weak);
|
||||||
|
font-family: var(--font-family-sans);
|
||||||
[data-slot="session-turn-trigger-icon"] {
|
font-size: var(--font-size-base);
|
||||||
color: var(--icon-base);
|
font-weight: var(--font-weight-medium);
|
||||||
}
|
line-height: var(--line-height-large);
|
||||||
|
min-height: 20px;
|
||||||
|
|
||||||
[data-component="spinner"] {
|
[data-component="spinner"] {
|
||||||
width: 12px;
|
width: 16px;
|
||||||
height: 12px;
|
height: 16px;
|
||||||
margin-right: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-component="icon"] {
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-retry-message"] {
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--syntax-critical);
|
|
||||||
min-width: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-retry-seconds"] {
|
|
||||||
color: var(--text-weak);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-retry-attempt"] {
|
|
||||||
color: var(--text-weak);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-status-text"] {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-details-text"] {
|
|
||||||
font-size: 13px;
|
|
||||||
/* text-12-medium */
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-card {
|
.error-card {
|
||||||
@@ -560,50 +63,112 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.retry-error-link,
|
[data-slot="session-turn-assistant-content"] {
|
||||||
.error-card-link {
|
|
||||||
color: var(--text-strong);
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-turn-collapsible-content-inner"] {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
margin-left: 12px;
|
|
||||||
padding-left: 12px;
|
|
||||||
padding-right: 12px;
|
|
||||||
border-left: 1px solid var(--border-base);
|
|
||||||
|
|
||||||
> :first-child > [data-component="markdown"]:first-child {
|
> :first-child > [data-component="markdown"]:first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="session-turn-permission-parts"] {
|
[data-slot="session-turn-diffs"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="session-turn-diffs-trigger"] {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="session-turn-diffs-title"] {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="session-turn-diffs-label"] {
|
||||||
|
color: var(--text-strong);
|
||||||
|
font-family: var(--font-family-sans);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: var(--line-height-large);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="session-turn-diffs-count"] {
|
||||||
|
color: var(--text-base);
|
||||||
|
font-family: var(--font-family-sans);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
font-weight: var(--font-weight-regular);
|
||||||
|
line-height: var(--line-height-x-large);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="session-turn-diffs-meta"] {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
[data-component="diff-changes"][data-variant="bars"] {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="session-turn-diffs-content"] {
|
||||||
|
padding-top: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="session-turn-question-parts"] {
|
[data-component="session-turn-diff"] {
|
||||||
width: 100%;
|
border: 1px solid var(--border-weaker-base);
|
||||||
min-width: 0;
|
border-radius: var(--radius-md);
|
||||||
display: flex;
|
overflow: clip;
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="session-turn-answered-question-parts"] {
|
[data-slot="session-turn-diff-header"] {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-bottom: 1px solid var(--border-weaker-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="session-turn-diff-path"] {
|
||||||
|
display: inline-flex;
|
||||||
|
min-width: 0;
|
||||||
|
align-items: baseline;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-family: var(--font-family-sans);
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
line-height: var(--line-height-large);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="session-turn-diff-directory"] {
|
||||||
|
color: var(--text-weak);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="session-turn-diff-filename"] {
|
||||||
|
color: var(--text-strong);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="session-turn-diff-view"] {
|
||||||
|
background-color: var(--surface-inset-base);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,18 @@
|
|||||||
import {
|
import { AssistantMessage, type FileDiff, Message as MessageType, Part as PartType } from "@opencode-ai/sdk/v2/client"
|
||||||
AssistantMessage,
|
|
||||||
FilePart,
|
|
||||||
Message as MessageType,
|
|
||||||
Part as PartType,
|
|
||||||
type PermissionRequest,
|
|
||||||
type QuestionRequest,
|
|
||||||
TextPart,
|
|
||||||
ToolPart,
|
|
||||||
} from "@opencode-ai/sdk/v2/client"
|
|
||||||
import { useData } from "../context"
|
import { useData } from "../context"
|
||||||
import { type UiI18nKey, type UiI18nParams, useI18n } from "../context/i18n"
|
import { useDiffComponent } from "../context/diff"
|
||||||
|
|
||||||
import { Binary } from "@opencode-ai/util/binary"
|
import { Binary } from "@opencode-ai/util/binary"
|
||||||
import { createEffect, createMemo, createSignal, For, Match, on, onCleanup, ParentProps, Show, Switch } from "solid-js"
|
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
||||||
import { Message, Part } from "./message-part"
|
import { createMemo, createSignal, For, ParentProps, Show } from "solid-js"
|
||||||
import { Markdown } from "./markdown"
|
import { Dynamic } from "solid-js/web"
|
||||||
import { IconButton } from "./icon-button"
|
import { Message } from "./message-part"
|
||||||
import { Card } from "./card"
|
import { Card } from "./card"
|
||||||
import { Button } from "./button"
|
import { Collapsible } from "./collapsible"
|
||||||
import { Spinner } from "./spinner"
|
import { DiffChanges } from "./diff-changes"
|
||||||
import { Tooltip } from "./tooltip"
|
import { TextShimmer } from "./text-shimmer"
|
||||||
import { createStore } from "solid-js/store"
|
|
||||||
import { DateTime, DurationUnit, Interval } from "luxon"
|
|
||||||
import { createAutoScroll } from "../hooks"
|
import { createAutoScroll } from "../hooks"
|
||||||
import { createResizeObserver } from "@solid-primitives/resize-observer"
|
import { useI18n } from "../context/i18n"
|
||||||
|
|
||||||
type Translator = (key: UiI18nKey, params?: UiI18nParams) => string
|
|
||||||
|
|
||||||
function record(value: unknown): value is Record<string, unknown> {
|
function record(value: unknown): value is Record<string, unknown> {
|
||||||
return !!value && typeof value === "object" && !Array.isArray(value)
|
return !!value && typeof value === "object" && !Array.isArray(value)
|
||||||
@@ -80,117 +67,42 @@ function unwrap(message: string) {
|
|||||||
return message
|
return message
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeStatusFromPart(part: PartType | undefined, t: Translator): string | undefined {
|
|
||||||
if (!part) return undefined
|
|
||||||
|
|
||||||
if (part.type === "tool") {
|
|
||||||
switch (part.tool) {
|
|
||||||
case "task":
|
|
||||||
return t("ui.sessionTurn.status.delegating")
|
|
||||||
case "todowrite":
|
|
||||||
case "todoread":
|
|
||||||
return t("ui.sessionTurn.status.planning")
|
|
||||||
case "read":
|
|
||||||
return t("ui.sessionTurn.status.gatheringContext")
|
|
||||||
case "list":
|
|
||||||
case "grep":
|
|
||||||
case "glob":
|
|
||||||
return t("ui.sessionTurn.status.searchingCodebase")
|
|
||||||
case "webfetch":
|
|
||||||
return t("ui.sessionTurn.status.searchingWeb")
|
|
||||||
case "edit":
|
|
||||||
case "write":
|
|
||||||
return t("ui.sessionTurn.status.makingEdits")
|
|
||||||
case "bash":
|
|
||||||
return t("ui.sessionTurn.status.runningCommands")
|
|
||||||
default:
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (part.type === "reasoning") {
|
|
||||||
const text = part.text ?? ""
|
|
||||||
const match = text.trimStart().match(/^\*\*(.+?)\*\*/)
|
|
||||||
if (match) return t("ui.sessionTurn.status.thinkingWithTopic", { topic: match[1].trim() })
|
|
||||||
return t("ui.sessionTurn.status.thinking")
|
|
||||||
}
|
|
||||||
if (part.type === "text") {
|
|
||||||
return t("ui.sessionTurn.status.gatheringThoughts")
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
function same<T>(a: readonly T[], b: readonly T[]) {
|
function same<T>(a: readonly T[], b: readonly T[]) {
|
||||||
if (a === b) return true
|
if (a === b) return true
|
||||||
if (a.length !== b.length) return false
|
if (a.length !== b.length) return false
|
||||||
return a.every((x, i) => x === b[i])
|
return a.every((x, i) => x === b[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAttachment(part: PartType | undefined) {
|
|
||||||
if (part?.type !== "file") return false
|
|
||||||
const mime = (part as FilePart).mime ?? ""
|
|
||||||
return mime.startsWith("image/") || mime === "application/pdf"
|
|
||||||
}
|
|
||||||
|
|
||||||
function list<T>(value: T[] | undefined | null, fallback: T[]) {
|
function list<T>(value: T[] | undefined | null, fallback: T[]) {
|
||||||
if (Array.isArray(value)) return value
|
if (Array.isArray(value)) return value
|
||||||
return fallback
|
return fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
function AssistantMessageItem(props: {
|
const hidden = new Set(["todowrite", "todoread"])
|
||||||
message: AssistantMessage
|
|
||||||
responsePartId: string | undefined
|
function visible(part: PartType) {
|
||||||
hideResponsePart: boolean
|
if (part.type === "tool") {
|
||||||
hideReasoning: boolean
|
if (hidden.has(part.tool)) return false
|
||||||
hidden?: () => readonly { messageID: string; callID: string }[]
|
if (part.tool === "question") return part.state.status !== "pending" && part.state.status !== "running"
|
||||||
}) {
|
return true
|
||||||
|
}
|
||||||
|
if (part.type === "text") return !!part.text?.trim()
|
||||||
|
if (part.type === "reasoning") return !!part.text?.trim()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function AssistantMessageItem(props: { message: AssistantMessage; showAssistantCopyPartID?: string | null }) {
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const emptyParts: PartType[] = []
|
const emptyParts: PartType[] = []
|
||||||
const msgParts = createMemo(() => list(data.store.part?.[props.message.id], emptyParts))
|
const msgParts = createMemo(() => list(data.store.part?.[props.message.id], emptyParts))
|
||||||
const lastTextPart = createMemo(() => {
|
return <Message message={props.message} parts={msgParts()} showAssistantCopyPartID={props.showAssistantCopyPartID} />
|
||||||
const parts = msgParts()
|
|
||||||
for (let i = parts.length - 1; i >= 0; i--) {
|
|
||||||
const part = parts[i]
|
|
||||||
if (part?.type === "text") return part as TextPart
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
const filteredParts = createMemo(() => {
|
|
||||||
let parts = msgParts()
|
|
||||||
|
|
||||||
if (props.hideReasoning) {
|
|
||||||
parts = parts.filter((part) => part?.type !== "reasoning")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.hideResponsePart) {
|
|
||||||
const responsePartId = props.responsePartId
|
|
||||||
if (responsePartId && responsePartId === lastTextPart()?.id) {
|
|
||||||
parts = parts.filter((part) => part?.id !== responsePartId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const hidden = props.hidden?.() ?? []
|
|
||||||
if (hidden.length === 0) return parts
|
|
||||||
|
|
||||||
const id = props.message.id
|
|
||||||
return parts.filter((part) => {
|
|
||||||
if (part?.type !== "tool") return true
|
|
||||||
const tool = part as ToolPart
|
|
||||||
return !hidden.some((h) => h.messageID === id && h.callID === tool.callID)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return <Message message={props.message} parts={filteredParts()} />
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SessionTurn(
|
export function SessionTurn(
|
||||||
props: ParentProps<{
|
props: ParentProps<{
|
||||||
sessionID: string
|
sessionID: string
|
||||||
sessionTitle?: string
|
|
||||||
messageID: string
|
messageID: string
|
||||||
lastUserMessageID?: string
|
lastUserMessageID?: string
|
||||||
stepsExpanded?: boolean
|
|
||||||
onStepsExpandedToggle?: () => void
|
|
||||||
onUserInteracted?: () => void
|
onUserInteracted?: () => void
|
||||||
classes?: {
|
classes?: {
|
||||||
root?: string
|
root?: string
|
||||||
@@ -199,16 +111,14 @@ export function SessionTurn(
|
|||||||
}
|
}
|
||||||
}>,
|
}>,
|
||||||
) {
|
) {
|
||||||
const i18n = useI18n()
|
|
||||||
const data = useData()
|
const data = useData()
|
||||||
|
const i18n = useI18n()
|
||||||
|
const diffComponent = useDiffComponent()
|
||||||
|
|
||||||
const emptyMessages: MessageType[] = []
|
const emptyMessages: MessageType[] = []
|
||||||
const emptyParts: PartType[] = []
|
const emptyParts: PartType[] = []
|
||||||
const emptyFiles: FilePart[] = []
|
|
||||||
const emptyAssistant: AssistantMessage[] = []
|
const emptyAssistant: AssistantMessage[] = []
|
||||||
const emptyPermissions: PermissionRequest[] = []
|
const emptyDiffs: FileDiff[] = []
|
||||||
const emptyQuestions: QuestionRequest[] = []
|
|
||||||
const emptyQuestionParts: { part: ToolPart; message: AssistantMessage }[] = []
|
|
||||||
const idle = { type: "idle" as const }
|
const idle = { type: "idle" as const }
|
||||||
|
|
||||||
const allMessages = createMemo(() => list(data.store.message?.[props.sessionID], emptyMessages))
|
const allMessages = createMemo(() => list(data.store.message?.[props.sessionID], emptyMessages))
|
||||||
@@ -256,18 +166,22 @@ export function SessionTurn(
|
|||||||
return list(data.store.part?.[msg.id], emptyParts)
|
return list(data.store.part?.[msg.id], emptyParts)
|
||||||
})
|
})
|
||||||
|
|
||||||
const attachmentParts = createMemo(() => {
|
const diffs = createMemo(() => {
|
||||||
const msgParts = parts()
|
const files = message()?.summary?.diffs
|
||||||
if (msgParts.length === 0) return emptyFiles
|
if (!files?.length) return emptyDiffs
|
||||||
return msgParts.filter((part) => isAttachment(part)) as FilePart[]
|
|
||||||
})
|
|
||||||
|
|
||||||
const stickyParts = createMemo(() => {
|
const seen = new Set<string>()
|
||||||
const msgParts = parts()
|
return files
|
||||||
if (msgParts.length === 0) return emptyParts
|
.reduceRight<FileDiff[]>((result, diff) => {
|
||||||
if (attachmentParts().length === 0) return msgParts
|
if (seen.has(diff.file)) return result
|
||||||
return msgParts.filter((part) => !isAttachment(part))
|
seen.add(diff.file)
|
||||||
|
result.push(diff)
|
||||||
|
return result
|
||||||
|
}, [])
|
||||||
|
.reverse()
|
||||||
})
|
})
|
||||||
|
const edited = createMemo(() => diffs().length)
|
||||||
|
const [open, setOpen] = createSignal(false)
|
||||||
|
|
||||||
const assistantMessages = createMemo(
|
const assistantMessages = createMemo(
|
||||||
() => {
|
() => {
|
||||||
@@ -291,9 +205,27 @@ export function SessionTurn(
|
|||||||
{ equals: same },
|
{ equals: same },
|
||||||
)
|
)
|
||||||
|
|
||||||
const lastAssistantMessage = createMemo(() => assistantMessages().at(-1))
|
const interrupted = createMemo(() => assistantMessages().some((m) => m.error?.name === "MessageAbortedError"))
|
||||||
|
const error = createMemo(
|
||||||
|
() => assistantMessages().find((m) => m.error && m.error.name !== "MessageAbortedError")?.error,
|
||||||
|
)
|
||||||
|
const showAssistantCopyPartID = createMemo(() => {
|
||||||
|
const messages = assistantMessages()
|
||||||
|
|
||||||
const error = createMemo(() => assistantMessages().find((m) => m.error)?.error)
|
for (let i = messages.length - 1; i >= 0; i--) {
|
||||||
|
const message = messages[i]
|
||||||
|
if (!message) continue
|
||||||
|
|
||||||
|
const parts = list(data.store.part?.[message.id], emptyParts)
|
||||||
|
for (let j = parts.length - 1; j >= 0; j--) {
|
||||||
|
const part = parts[j]
|
||||||
|
if (!part || part.type !== "text" || !part.text?.trim()) continue
|
||||||
|
return part.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
const errorText = createMemo(() => {
|
const errorText = createMemo(() => {
|
||||||
const msg = error()?.data?.message
|
const msg = error()?.data?.message
|
||||||
if (typeof msg === "string") return unwrap(msg)
|
if (typeof msg === "string") return unwrap(msg)
|
||||||
@@ -301,314 +233,29 @@ export function SessionTurn(
|
|||||||
return unwrap(String(msg))
|
return unwrap(String(msg))
|
||||||
})
|
})
|
||||||
|
|
||||||
const lastTextPart = createMemo(() => {
|
|
||||||
const msgs = assistantMessages()
|
|
||||||
for (let mi = msgs.length - 1; mi >= 0; mi--) {
|
|
||||||
const msgParts = list(data.store.part?.[msgs[mi].id], emptyParts)
|
|
||||||
for (let pi = msgParts.length - 1; pi >= 0; pi--) {
|
|
||||||
const part = msgParts[pi]
|
|
||||||
if (part?.type === "text") return part as TextPart
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
const hasSteps = createMemo(() => {
|
|
||||||
for (const m of assistantMessages()) {
|
|
||||||
const msgParts = list(data.store.part?.[m.id], emptyParts)
|
|
||||||
for (const p of msgParts) {
|
|
||||||
if (p?.type === "tool") return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
const permissions = createMemo(() => list(data.store.permission?.[props.sessionID], emptyPermissions))
|
|
||||||
const nextPermission = createMemo(() => permissions()[0])
|
|
||||||
|
|
||||||
const questions = createMemo(() => list(data.store.question?.[props.sessionID], emptyQuestions))
|
|
||||||
const nextQuestion = createMemo(() => questions()[0])
|
|
||||||
|
|
||||||
const hidden = createMemo(() => {
|
|
||||||
const out: { messageID: string; callID: string }[] = []
|
|
||||||
const perm = nextPermission()
|
|
||||||
if (perm?.tool) out.push(perm.tool)
|
|
||||||
const question = nextQuestion()
|
|
||||||
if (question?.tool) out.push(question.tool)
|
|
||||||
return out
|
|
||||||
})
|
|
||||||
|
|
||||||
const answeredQuestionParts = createMemo(() => {
|
|
||||||
if (props.stepsExpanded) return emptyQuestionParts
|
|
||||||
if (questions().length > 0) return emptyQuestionParts
|
|
||||||
|
|
||||||
const result: { part: ToolPart; message: AssistantMessage }[] = []
|
|
||||||
|
|
||||||
for (const msg of assistantMessages()) {
|
|
||||||
const parts = list(data.store.part?.[msg.id], emptyParts)
|
|
||||||
for (const part of parts) {
|
|
||||||
if (part?.type !== "tool") continue
|
|
||||||
const tool = part as ToolPart
|
|
||||||
if (tool.tool !== "question") continue
|
|
||||||
// @ts-expect-error metadata may not exist on all tool states
|
|
||||||
const answers = tool.state?.metadata?.answers
|
|
||||||
if (answers && answers.length > 0) {
|
|
||||||
result.push({ part: tool, message: msg })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
|
|
||||||
const shellModePart = createMemo(() => {
|
|
||||||
const p = parts()
|
|
||||||
if (p.length === 0) return
|
|
||||||
if (!p.every((part) => part?.type === "text" && part?.synthetic)) return
|
|
||||||
|
|
||||||
const msgs = assistantMessages()
|
|
||||||
if (msgs.length !== 1) return
|
|
||||||
|
|
||||||
const msgParts = list(data.store.part?.[msgs[0].id], emptyParts)
|
|
||||||
if (msgParts.length !== 1) return
|
|
||||||
|
|
||||||
const assistantPart = msgParts[0]
|
|
||||||
if (assistantPart?.type === "tool" && assistantPart.tool === "bash") return assistantPart
|
|
||||||
})
|
|
||||||
|
|
||||||
const isShellMode = createMemo(() => !!shellModePart())
|
|
||||||
|
|
||||||
const rawStatus = createMemo(() => {
|
|
||||||
const msgs = assistantMessages()
|
|
||||||
let last: PartType | undefined
|
|
||||||
let currentTask: ToolPart | undefined
|
|
||||||
|
|
||||||
for (let mi = msgs.length - 1; mi >= 0; mi--) {
|
|
||||||
const msgParts = list(data.store.part?.[msgs[mi].id], emptyParts)
|
|
||||||
for (let pi = msgParts.length - 1; pi >= 0; pi--) {
|
|
||||||
const part = msgParts[pi]
|
|
||||||
if (!part) continue
|
|
||||||
if (!last) last = part
|
|
||||||
|
|
||||||
if (
|
|
||||||
part.type === "tool" &&
|
|
||||||
part.tool === "task" &&
|
|
||||||
part.state &&
|
|
||||||
"metadata" in part.state &&
|
|
||||||
part.state.metadata?.sessionId &&
|
|
||||||
part.state.status === "running"
|
|
||||||
) {
|
|
||||||
currentTask = part as ToolPart
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (currentTask) break
|
|
||||||
}
|
|
||||||
|
|
||||||
const taskSessionId =
|
|
||||||
currentTask?.state && "metadata" in currentTask.state
|
|
||||||
? (currentTask.state.metadata?.sessionId as string | undefined)
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
if (taskSessionId) {
|
|
||||||
const taskMessages = list(data.store.message?.[taskSessionId], emptyMessages)
|
|
||||||
for (let mi = taskMessages.length - 1; mi >= 0; mi--) {
|
|
||||||
const msg = taskMessages[mi]
|
|
||||||
if (!msg || msg.role !== "assistant") continue
|
|
||||||
|
|
||||||
const msgParts = list(data.store.part?.[msg.id], emptyParts)
|
|
||||||
for (let pi = msgParts.length - 1; pi >= 0; pi--) {
|
|
||||||
const part = msgParts[pi]
|
|
||||||
if (part) return computeStatusFromPart(part, i18n.t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return computeStatusFromPart(last, i18n.t)
|
|
||||||
})
|
|
||||||
|
|
||||||
const status = createMemo(() => data.store.session_status[props.sessionID] ?? idle)
|
const status = createMemo(() => data.store.session_status[props.sessionID] ?? idle)
|
||||||
const working = createMemo(() => status().type !== "idle" && isLastUserMessage())
|
const working = createMemo(() => status().type !== "idle" && isLastUserMessage())
|
||||||
const retry = createMemo(() => {
|
|
||||||
// session_status is session-scoped; only show retry on the active (last) turn
|
const assistantCopyPartID = createMemo(() => {
|
||||||
if (!isLastUserMessage()) return
|
if (!isLastUserMessage()) return null
|
||||||
const s = status()
|
if (status().type !== "idle") return null
|
||||||
if (s.type !== "retry") return
|
return showAssistantCopyPartID() ?? null
|
||||||
return s
|
|
||||||
})
|
})
|
||||||
const isRetryFreeUsageLimitError = createMemo(() => {
|
const assistantVisible = createMemo(() =>
|
||||||
const r = retry()
|
assistantMessages().reduce((count, message) => {
|
||||||
if (!r) return false
|
const parts = list(data.store.part?.[message.id], emptyParts)
|
||||||
return r.message.includes("Free usage exceeded")
|
return count + parts.filter(visible).length
|
||||||
})
|
}, 0),
|
||||||
|
)
|
||||||
const response = createMemo(() => lastTextPart()?.text)
|
|
||||||
const responsePartId = createMemo(() => lastTextPart()?.id)
|
|
||||||
const hasDiffs = createMemo(() => (message()?.summary?.diffs?.length ?? 0) > 0)
|
|
||||||
const hideResponsePart = createMemo(() => !working() && !!responsePartId())
|
|
||||||
|
|
||||||
const [copied, setCopied] = createSignal(false)
|
|
||||||
|
|
||||||
const handleCopy = async () => {
|
|
||||||
const content = response() ?? ""
|
|
||||||
if (!content) return
|
|
||||||
await navigator.clipboard.writeText(content)
|
|
||||||
setCopied(true)
|
|
||||||
setTimeout(() => setCopied(false), 2000)
|
|
||||||
}
|
|
||||||
|
|
||||||
const [rootRef, setRootRef] = createSignal<HTMLDivElement | undefined>()
|
|
||||||
const [stickyRef, setStickyRef] = createSignal<HTMLDivElement | undefined>()
|
|
||||||
|
|
||||||
const updateStickyHeight = (height: number) => {
|
|
||||||
const root = rootRef()
|
|
||||||
if (!root) return
|
|
||||||
const next = Math.ceil(height)
|
|
||||||
root.style.setProperty("--session-turn-sticky-height", `${next}px`)
|
|
||||||
}
|
|
||||||
|
|
||||||
function duration() {
|
|
||||||
const msg = message()
|
|
||||||
if (!msg) return ""
|
|
||||||
const completed = lastAssistantMessage()?.time.completed
|
|
||||||
const from = DateTime.fromMillis(msg.time.created)
|
|
||||||
const to = completed ? DateTime.fromMillis(completed) : DateTime.now()
|
|
||||||
const interval = Interval.fromDateTimes(from, to)
|
|
||||||
const unit: DurationUnit[] = interval.length("seconds") > 60 ? ["minutes", "seconds"] : ["seconds"]
|
|
||||||
|
|
||||||
const locale = i18n.locale()
|
|
||||||
const human = interval.toDuration(unit).normalize().reconfigure({ locale }).toHuman({
|
|
||||||
notation: "compact",
|
|
||||||
unitDisplay: "narrow",
|
|
||||||
compactDisplay: "short",
|
|
||||||
showZeros: false,
|
|
||||||
})
|
|
||||||
return locale.startsWith("zh") ? human.replaceAll("、", "") : human
|
|
||||||
}
|
|
||||||
|
|
||||||
const autoScroll = createAutoScroll({
|
const autoScroll = createAutoScroll({
|
||||||
working,
|
working,
|
||||||
onUserInteracted: props.onUserInteracted,
|
onUserInteracted: props.onUserInteracted,
|
||||||
overflowAnchor: "auto",
|
overflowAnchor: "dynamic",
|
||||||
})
|
|
||||||
|
|
||||||
createResizeObserver(
|
|
||||||
() => stickyRef(),
|
|
||||||
({ height }) => {
|
|
||||||
updateStickyHeight(height)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
const root = rootRef()
|
|
||||||
if (!root) return
|
|
||||||
const sticky = stickyRef()
|
|
||||||
if (!sticky) {
|
|
||||||
root.style.setProperty("--session-turn-sticky-height", "0px")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
updateStickyHeight(sticky.getBoundingClientRect().height)
|
|
||||||
})
|
|
||||||
|
|
||||||
const [store, setStore] = createStore({
|
|
||||||
retrySeconds: 0,
|
|
||||||
status: rawStatus(),
|
|
||||||
duration: duration(),
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
const r = retry()
|
|
||||||
if (!r) {
|
|
||||||
setStore("retrySeconds", 0)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const updateSeconds = () => {
|
|
||||||
const next = r.next
|
|
||||||
if (next) setStore("retrySeconds", Math.max(0, Math.round((next - Date.now()) / 1000)))
|
|
||||||
}
|
|
||||||
updateSeconds()
|
|
||||||
const timer = setInterval(updateSeconds, 1000)
|
|
||||||
onCleanup(() => clearInterval(timer))
|
|
||||||
})
|
|
||||||
|
|
||||||
let retryLog = ""
|
|
||||||
createEffect(() => {
|
|
||||||
const r = retry()
|
|
||||||
if (!r) return
|
|
||||||
const key = `${r.attempt}:${r.next}:${r.message}`
|
|
||||||
if (key === retryLog) return
|
|
||||||
retryLog = key
|
|
||||||
console.warn("[session-turn] retry", {
|
|
||||||
sessionID: props.sessionID,
|
|
||||||
messageID: props.messageID,
|
|
||||||
attempt: r.attempt,
|
|
||||||
next: r.next,
|
|
||||||
raw: r.message,
|
|
||||||
parsed: unwrap(r.message),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
let errorLog = ""
|
|
||||||
createEffect(() => {
|
|
||||||
const value = error()?.data?.message
|
|
||||||
if (value === undefined || value === null) return
|
|
||||||
const raw = typeof value === "string" ? value : String(value)
|
|
||||||
if (!raw) return
|
|
||||||
if (raw === errorLog) return
|
|
||||||
errorLog = raw
|
|
||||||
console.warn("[session-turn] assistant-error", {
|
|
||||||
sessionID: props.sessionID,
|
|
||||||
messageID: props.messageID,
|
|
||||||
raw,
|
|
||||||
parsed: unwrap(raw),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
const update = () => {
|
|
||||||
setStore("duration", duration())
|
|
||||||
}
|
|
||||||
|
|
||||||
update()
|
|
||||||
|
|
||||||
// Only keep ticking while the active (in-progress) turn is running.
|
|
||||||
if (!working()) return
|
|
||||||
|
|
||||||
const timer = setInterval(update, 1000)
|
|
||||||
onCleanup(() => clearInterval(timer))
|
|
||||||
})
|
|
||||||
|
|
||||||
let lastStatusChange = Date.now()
|
|
||||||
let statusTimeout: number | undefined
|
|
||||||
createEffect(() => {
|
|
||||||
const newStatus = rawStatus()
|
|
||||||
if (newStatus === store.status || !newStatus) return
|
|
||||||
|
|
||||||
const timeSinceLastChange = Date.now() - lastStatusChange
|
|
||||||
if (timeSinceLastChange >= 2500) {
|
|
||||||
setStore("status", newStatus)
|
|
||||||
lastStatusChange = Date.now()
|
|
||||||
if (statusTimeout) {
|
|
||||||
clearTimeout(statusTimeout)
|
|
||||||
statusTimeout = undefined
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (statusTimeout) clearTimeout(statusTimeout)
|
|
||||||
statusTimeout = setTimeout(() => {
|
|
||||||
setStore("status", rawStatus())
|
|
||||||
lastStatusChange = Date.now()
|
|
||||||
statusTimeout = undefined
|
|
||||||
}, 2500 - timeSinceLastChange) as unknown as number
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onCleanup(() => {
|
|
||||||
if (!statusTimeout) return
|
|
||||||
clearTimeout(statusTimeout)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-component="session-turn" class={props.classes?.root} ref={setRootRef}>
|
<div data-component="session-turn" class={props.classes?.root}>
|
||||||
<div
|
<div
|
||||||
ref={autoScroll.scrollRef}
|
ref={autoScroll.scrollRef}
|
||||||
onScroll={autoScroll.handleScroll}
|
onScroll={autoScroll.handleScroll}
|
||||||
@@ -624,197 +271,83 @@ export function SessionTurn(
|
|||||||
data-slot="session-turn-message-container"
|
data-slot="session-turn-message-container"
|
||||||
class={props.classes?.container}
|
class={props.classes?.container}
|
||||||
>
|
>
|
||||||
<Switch>
|
<div data-slot="session-turn-message-content" aria-live="off">
|
||||||
<Match when={isShellMode()}>
|
<Message message={msg()} parts={parts()} interrupted={interrupted()} />
|
||||||
<Part part={shellModePart()!} message={msg()} defaultOpen />
|
</div>
|
||||||
</Match>
|
<Show when={working() && assistantVisible() === 0 && !error()}>
|
||||||
<Match when={true}>
|
<div data-slot="session-turn-thinking">
|
||||||
<Show when={attachmentParts().length > 0}>
|
<TextShimmer text={i18n.t("ui.sessionTurn.status.thinking")} />
|
||||||
<div data-slot="session-turn-attachments" aria-live="off">
|
</div>
|
||||||
<Message message={msg()} parts={attachmentParts()} />
|
</Show>
|
||||||
</div>
|
<Show when={assistantMessages().length > 0}>
|
||||||
</Show>
|
<div data-slot="session-turn-assistant-content" aria-hidden={working()}>
|
||||||
<div data-slot="session-turn-sticky" ref={setStickyRef}>
|
<For each={assistantMessages()}>
|
||||||
{/* User Message */}
|
{(assistantMessage) => (
|
||||||
<div data-slot="session-turn-message-content" aria-live="off">
|
<AssistantMessageItem
|
||||||
<Message message={msg()} parts={stickyParts()} />
|
message={assistantMessage}
|
||||||
</div>
|
showAssistantCopyPartID={assistantCopyPartID()}
|
||||||
|
/>
|
||||||
{/* Trigger (sticky) */}
|
)}
|
||||||
<Show when={working() || hasSteps()}>
|
</For>
|
||||||
<div data-slot="session-turn-response-trigger">
|
</div>
|
||||||
<Button
|
</Show>
|
||||||
data-expandable={assistantMessages().length > 0}
|
<Show when={edited() > 0}>
|
||||||
data-slot="session-turn-collapsible-trigger-content"
|
<div data-slot="session-turn-diffs">
|
||||||
variant="ghost"
|
<Collapsible open={open()} onOpenChange={setOpen} variant="ghost">
|
||||||
size="small"
|
<Collapsible.Trigger>
|
||||||
onClick={props.onStepsExpandedToggle ?? (() => {})}
|
<div data-component="session-turn-diffs-trigger">
|
||||||
aria-expanded={props.stepsExpanded}
|
<div data-slot="session-turn-diffs-title">
|
||||||
>
|
<span data-slot="session-turn-diffs-label">
|
||||||
<Switch>
|
{i18n.t("ui.sessionReview.change.modified")}
|
||||||
<Match when={working()}>
|
</span>
|
||||||
<Spinner />
|
<span data-slot="session-turn-diffs-count">
|
||||||
</Match>
|
{edited()} {i18n.t(edited() === 1 ? "ui.common.file.one" : "ui.common.file.other")}
|
||||||
<Match when={!props.stepsExpanded}>
|
</span>
|
||||||
<svg
|
<div data-slot="session-turn-diffs-meta">
|
||||||
width="10"
|
<DiffChanges changes={diffs()} variant="bars" />
|
||||||
height="10"
|
<Collapsible.Arrow />
|
||||||
viewBox="0 0 10 10"
|
</div>
|
||||||
fill="none"
|
</div>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
data-slot="session-turn-trigger-icon"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M8.125 1.875H1.875L5 8.125L8.125 1.875Z"
|
|
||||||
fill="currentColor"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</Match>
|
|
||||||
<Match when={props.stepsExpanded}>
|
|
||||||
<svg
|
|
||||||
width="10"
|
|
||||||
height="10"
|
|
||||||
viewBox="0 0 10 10"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="text-icon-base"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M8.125 8.125H1.875L5 1.875L8.125 8.125Z"
|
|
||||||
fill="currentColor"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
<Switch>
|
|
||||||
<Match when={retry()}>
|
|
||||||
<span data-slot="session-turn-retry-message">
|
|
||||||
{(() => {
|
|
||||||
const r = retry()
|
|
||||||
if (!r) return ""
|
|
||||||
const msg = isRetryFreeUsageLimitError()
|
|
||||||
? i18n.t("ui.sessionTurn.error.freeUsageExceeded")
|
|
||||||
: unwrap(r.message)
|
|
||||||
return msg.length > 60 ? msg.slice(0, 60) + "..." : msg
|
|
||||||
})()}
|
|
||||||
</span>
|
|
||||||
<Show when={isRetryFreeUsageLimitError()}>
|
|
||||||
<a
|
|
||||||
href="https://opencode.ai/zen"
|
|
||||||
target="_blank"
|
|
||||||
class="retry-error-link"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{i18n.t("ui.sessionTurn.error.addCredits")}
|
|
||||||
</a>
|
|
||||||
</Show>
|
|
||||||
<span data-slot="session-turn-retry-seconds">
|
|
||||||
· {i18n.t("ui.sessionTurn.retry.retrying")}
|
|
||||||
{store.retrySeconds > 0
|
|
||||||
? " " + i18n.t("ui.sessionTurn.retry.inSeconds", { seconds: store.retrySeconds })
|
|
||||||
: ""}
|
|
||||||
</span>
|
|
||||||
<span data-slot="session-turn-retry-attempt">(#{retry()?.attempt})</span>
|
|
||||||
</Match>
|
|
||||||
<Match when={working()}>
|
|
||||||
<span data-slot="session-turn-status-text">
|
|
||||||
{store.status ?? i18n.t("ui.sessionTurn.status.consideringNextSteps")}
|
|
||||||
</span>
|
|
||||||
</Match>
|
|
||||||
<Match when={props.stepsExpanded}>
|
|
||||||
<span data-slot="session-turn-status-text">{i18n.t("ui.sessionTurn.steps.hide")}</span>
|
|
||||||
</Match>
|
|
||||||
<Match when={!props.stepsExpanded}>
|
|
||||||
<span data-slot="session-turn-status-text">{i18n.t("ui.sessionTurn.steps.show")}</span>
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
<span aria-hidden="true">·</span>
|
|
||||||
<span aria-live="off">{store.duration}</span>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Collapsible.Trigger>
|
||||||
</div>
|
<Collapsible.Content>
|
||||||
{/* Response */}
|
<Show when={open()}>
|
||||||
<Show when={props.stepsExpanded && assistantMessages().length > 0}>
|
<div data-component="session-turn-diffs-content">
|
||||||
<div data-slot="session-turn-collapsible-content-inner" aria-hidden={working()}>
|
<For each={diffs()}>
|
||||||
<For each={assistantMessages()}>
|
{(diff) => (
|
||||||
{(assistantMessage) => (
|
<div data-component="session-turn-diff">
|
||||||
<AssistantMessageItem
|
<div data-slot="session-turn-diff-header">
|
||||||
message={assistantMessage}
|
<span data-slot="session-turn-diff-path">
|
||||||
responsePartId={responsePartId()}
|
<Show when={diff.file.includes("/")}>
|
||||||
hideResponsePart={hideResponsePart()}
|
<span data-slot="session-turn-diff-directory">{getDirectory(diff.file)}</span>
|
||||||
hideReasoning={!working()}
|
</Show>
|
||||||
hidden={hidden}
|
<span data-slot="session-turn-diff-filename">{getFilename(diff.file)}</span>
|
||||||
/>
|
</span>
|
||||||
)}
|
<span data-slot="session-turn-diff-changes">
|
||||||
</For>
|
<DiffChanges changes={diff} />
|
||||||
<Show when={error()}>
|
</span>
|
||||||
<Card variant="error" class="error-card">
|
</div>
|
||||||
{errorText()}
|
<div data-slot="session-turn-diff-view">
|
||||||
</Card>
|
<Dynamic
|
||||||
|
component={diffComponent}
|
||||||
|
before={{ name: diff.file, contents: diff.before }}
|
||||||
|
after={{ name: diff.file, contents: diff.after }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</Collapsible.Content>
|
||||||
</Show>
|
</Collapsible>
|
||||||
<Show when={!props.stepsExpanded && answeredQuestionParts().length > 0}>
|
</div>
|
||||||
<div data-slot="session-turn-answered-question-parts">
|
</Show>
|
||||||
<For each={answeredQuestionParts()}>
|
<Show when={error()}>
|
||||||
{({ part, message }) => <Part part={part} message={message} />}
|
<Card variant="error" class="error-card">
|
||||||
</For>
|
{errorText()}
|
||||||
</div>
|
</Card>
|
||||||
</Show>
|
</Show>
|
||||||
{/* Response */}
|
|
||||||
<div class="sr-only" aria-live="polite">
|
|
||||||
{!working() && response() ? response() : ""}
|
|
||||||
</div>
|
|
||||||
<Show when={!working() && response()}>
|
|
||||||
<div data-slot="session-turn-summary-section">
|
|
||||||
<div data-slot="session-turn-summary-header">
|
|
||||||
<div data-slot="session-turn-summary-title-row">
|
|
||||||
<h2 data-slot="session-turn-summary-title">{i18n.t("ui.sessionTurn.summary.response")}</h2>
|
|
||||||
<Show when={response()}>
|
|
||||||
<div data-slot="session-turn-response-copy-wrapper">
|
|
||||||
<Tooltip
|
|
||||||
value={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copy")}
|
|
||||||
placement="top"
|
|
||||||
gutter={8}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
icon={copied() ? "check" : "copy"}
|
|
||||||
size="small"
|
|
||||||
variant="secondary"
|
|
||||||
onMouseDown={(e) => e.preventDefault()}
|
|
||||||
onClick={(event) => {
|
|
||||||
event.stopPropagation()
|
|
||||||
handleCopy()
|
|
||||||
}}
|
|
||||||
aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copy")}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
<div data-slot="session-turn-response">
|
|
||||||
<Markdown
|
|
||||||
data-slot="session-turn-markdown"
|
|
||||||
data-diffs={hasDiffs()}
|
|
||||||
text={response() ?? ""}
|
|
||||||
cacheKey={responsePartId()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
<Show when={error() && !props.stepsExpanded}>
|
|
||||||
<Card variant="error" class="error-card">
|
|
||||||
{errorText()}
|
|
||||||
</Card>
|
|
||||||
</Show>
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
43
packages/ui/src/components/text-shimmer.css
Normal file
43
packages/ui/src/components/text-shimmer.css
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
[data-component="text-shimmer"] {
|
||||||
|
--text-shimmer-step: 45ms;
|
||||||
|
--text-shimmer-duration: 1200ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="text-shimmer"] [data-slot="text-shimmer-char"] {
|
||||||
|
white-space: pre;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="text-shimmer"][data-active="true"] [data-slot="text-shimmer-char"] {
|
||||||
|
animation-name: text-shimmer-char;
|
||||||
|
animation-duration: var(--text-shimmer-duration);
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-timing-function: ease-in-out;
|
||||||
|
animation-delay: calc(var(--text-shimmer-step) * var(--text-shimmer-index));
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes text-shimmer-char {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
color: var(--text-weaker);
|
||||||
|
}
|
||||||
|
|
||||||
|
30% {
|
||||||
|
color: var(--text-weak);
|
||||||
|
}
|
||||||
|
|
||||||
|
55% {
|
||||||
|
color: var(--text-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
color: var(--text-strong);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
[data-component="text-shimmer"] [data-slot="text-shimmer-char"] {
|
||||||
|
animation: none !important;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
36
packages/ui/src/components/text-shimmer.tsx
Normal file
36
packages/ui/src/components/text-shimmer.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { For, createMemo, type ValidComponent } from "solid-js"
|
||||||
|
import { Dynamic } from "solid-js/web"
|
||||||
|
|
||||||
|
export const TextShimmer = <T extends ValidComponent = "span">(props: {
|
||||||
|
text: string
|
||||||
|
class?: string
|
||||||
|
as?: T
|
||||||
|
active?: boolean
|
||||||
|
stepMs?: number
|
||||||
|
durationMs?: number
|
||||||
|
}) => {
|
||||||
|
const chars = createMemo(() => Array.from(props.text))
|
||||||
|
const active = () => props.active ?? true
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dynamic
|
||||||
|
component={props.as || "span"}
|
||||||
|
data-component="text-shimmer"
|
||||||
|
data-active={active()}
|
||||||
|
class={props.class}
|
||||||
|
aria-label={props.text}
|
||||||
|
style={{
|
||||||
|
"--text-shimmer-step": `${props.stepMs ?? 45}ms`,
|
||||||
|
"--text-shimmer-duration": `${props.durationMs ?? 1200}ms`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<For each={chars()}>
|
||||||
|
{(char, index) => (
|
||||||
|
<span data-slot="text-shimmer-char" aria-hidden="true" style={{ "--text-shimmer-index": `${index()}` }}>
|
||||||
|
{char}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</Dynamic>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -50,8 +50,6 @@ export type NavigateToSessionFn = (sessionID: string) => void
|
|||||||
|
|
||||||
export type SessionHrefFn = (sessionID: string) => string
|
export type SessionHrefFn = (sessionID: string) => string
|
||||||
|
|
||||||
export type SyncSessionFn = (sessionID: string) => void | Promise<void>
|
|
||||||
|
|
||||||
export const { use: useData, provider: DataProvider } = createSimpleContext({
|
export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
name: "Data",
|
name: "Data",
|
||||||
init: (props: {
|
init: (props: {
|
||||||
@@ -62,7 +60,6 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||||||
onQuestionReject?: QuestionRejectFn
|
onQuestionReject?: QuestionRejectFn
|
||||||
onNavigateToSession?: NavigateToSessionFn
|
onNavigateToSession?: NavigateToSessionFn
|
||||||
onSessionHref?: SessionHrefFn
|
onSessionHref?: SessionHrefFn
|
||||||
onSyncSession?: SyncSessionFn
|
|
||||||
}) => {
|
}) => {
|
||||||
return {
|
return {
|
||||||
get store() {
|
get store() {
|
||||||
@@ -76,7 +73,6 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||||||
rejectQuestion: props.onQuestionReject,
|
rejectQuestion: props.onQuestionReject,
|
||||||
navigateToSession: props.onNavigateToSession,
|
navigateToSession: props.onNavigateToSession,
|
||||||
sessionHref: props.onSessionHref,
|
sessionHref: props.onSessionHref,
|
||||||
syncSession: props.onSyncSession,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export const dict = {
|
|||||||
"ui.common.question.other": "أسئلة",
|
"ui.common.question.other": "أسئلة",
|
||||||
|
|
||||||
"ui.common.add": "إضافة",
|
"ui.common.add": "إضافة",
|
||||||
|
"ui.common.back": "رجوع",
|
||||||
"ui.common.cancel": "إلغاء",
|
"ui.common.cancel": "إلغاء",
|
||||||
"ui.common.confirm": "تأكيد",
|
"ui.common.confirm": "تأكيد",
|
||||||
"ui.common.dismiss": "رفض",
|
"ui.common.dismiss": "رفض",
|
||||||
@@ -97,6 +98,7 @@ export const dict = {
|
|||||||
"ui.message.collapse": "طي الرسالة",
|
"ui.message.collapse": "طي الرسالة",
|
||||||
"ui.message.copy": "نسخ",
|
"ui.message.copy": "نسخ",
|
||||||
"ui.message.copied": "تم النسخ!",
|
"ui.message.copied": "تم النسخ!",
|
||||||
|
"ui.message.interrupted": "تمت المقاطعة",
|
||||||
"ui.message.attachment.alt": "مرفق",
|
"ui.message.attachment.alt": "مرفق",
|
||||||
|
|
||||||
"ui.patch.action.deleted": "محذوف",
|
"ui.patch.action.deleted": "محذوف",
|
||||||
@@ -107,6 +109,7 @@ export const dict = {
|
|||||||
"ui.question.subtitle.answered": "{{count}} أجيب",
|
"ui.question.subtitle.answered": "{{count}} أجيب",
|
||||||
"ui.question.answer.none": "(لا توجد إجابة)",
|
"ui.question.answer.none": "(لا توجد إجابة)",
|
||||||
"ui.question.review.notAnswered": "(لم يتم الرد)",
|
"ui.question.review.notAnswered": "(لم يتم الرد)",
|
||||||
"ui.question.multiHint": "(حدد كل ما ينطبق)",
|
"ui.question.multiHint": "حدد كل ما ينطبق",
|
||||||
|
"ui.question.singleHint": "حدد إجابة واحدة",
|
||||||
"ui.question.custom.placeholder": "اكتب إجابتك...",
|
"ui.question.custom.placeholder": "اكتب إجابتك...",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export const dict = {
|
|||||||
"ui.common.question.other": "perguntas",
|
"ui.common.question.other": "perguntas",
|
||||||
|
|
||||||
"ui.common.add": "Adicionar",
|
"ui.common.add": "Adicionar",
|
||||||
|
"ui.common.back": "Voltar",
|
||||||
"ui.common.cancel": "Cancelar",
|
"ui.common.cancel": "Cancelar",
|
||||||
"ui.common.confirm": "Confirmar",
|
"ui.common.confirm": "Confirmar",
|
||||||
"ui.common.dismiss": "Descartar",
|
"ui.common.dismiss": "Descartar",
|
||||||
@@ -97,6 +98,7 @@ export const dict = {
|
|||||||
"ui.message.collapse": "Recolher mensagem",
|
"ui.message.collapse": "Recolher mensagem",
|
||||||
"ui.message.copy": "Copiar",
|
"ui.message.copy": "Copiar",
|
||||||
"ui.message.copied": "Copiado!",
|
"ui.message.copied": "Copiado!",
|
||||||
|
"ui.message.interrupted": "Interrompido",
|
||||||
"ui.message.attachment.alt": "anexo",
|
"ui.message.attachment.alt": "anexo",
|
||||||
|
|
||||||
"ui.patch.action.deleted": "Excluído",
|
"ui.patch.action.deleted": "Excluído",
|
||||||
@@ -107,6 +109,7 @@ export const dict = {
|
|||||||
"ui.question.subtitle.answered": "{{count}} respondidas",
|
"ui.question.subtitle.answered": "{{count}} respondidas",
|
||||||
"ui.question.answer.none": "(sem resposta)",
|
"ui.question.answer.none": "(sem resposta)",
|
||||||
"ui.question.review.notAnswered": "(não respondida)",
|
"ui.question.review.notAnswered": "(não respondida)",
|
||||||
"ui.question.multiHint": "(selecione todas que se aplicam)",
|
"ui.question.multiHint": "Selecione todas que se aplicam",
|
||||||
|
"ui.question.singleHint": "Selecione uma resposta",
|
||||||
"ui.question.custom.placeholder": "Digite sua resposta...",
|
"ui.question.custom.placeholder": "Digite sua resposta...",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export const dict = {
|
|||||||
"ui.common.question.other": "pitanja",
|
"ui.common.question.other": "pitanja",
|
||||||
|
|
||||||
"ui.common.add": "Dodaj",
|
"ui.common.add": "Dodaj",
|
||||||
|
"ui.common.back": "Nazad",
|
||||||
"ui.common.cancel": "Otkaži",
|
"ui.common.cancel": "Otkaži",
|
||||||
"ui.common.confirm": "Potvrdi",
|
"ui.common.confirm": "Potvrdi",
|
||||||
"ui.common.dismiss": "Odbaci",
|
"ui.common.dismiss": "Odbaci",
|
||||||
@@ -101,6 +102,7 @@ export const dict = {
|
|||||||
"ui.message.collapse": "Sažmi poruku",
|
"ui.message.collapse": "Sažmi poruku",
|
||||||
"ui.message.copy": "Kopiraj",
|
"ui.message.copy": "Kopiraj",
|
||||||
"ui.message.copied": "Kopirano!",
|
"ui.message.copied": "Kopirano!",
|
||||||
|
"ui.message.interrupted": "Prekinuto",
|
||||||
"ui.message.attachment.alt": "prilog",
|
"ui.message.attachment.alt": "prilog",
|
||||||
|
|
||||||
"ui.patch.action.deleted": "Obrisano",
|
"ui.patch.action.deleted": "Obrisano",
|
||||||
@@ -111,6 +113,7 @@ export const dict = {
|
|||||||
"ui.question.subtitle.answered": "{{count}} odgovoreno",
|
"ui.question.subtitle.answered": "{{count}} odgovoreno",
|
||||||
"ui.question.answer.none": "(nema odgovora)",
|
"ui.question.answer.none": "(nema odgovora)",
|
||||||
"ui.question.review.notAnswered": "(nije odgovoreno)",
|
"ui.question.review.notAnswered": "(nije odgovoreno)",
|
||||||
"ui.question.multiHint": "(odaberi sve što važi)",
|
"ui.question.multiHint": "Odaberi sve što važi",
|
||||||
|
"ui.question.singleHint": "Odaberi jedan odgovor",
|
||||||
"ui.question.custom.placeholder": "Unesi svoj odgovor...",
|
"ui.question.custom.placeholder": "Unesi svoj odgovor...",
|
||||||
} satisfies Partial<Record<Keys, string>>
|
} satisfies Partial<Record<Keys, string>>
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ export const dict = {
|
|||||||
"ui.common.question.other": "spørgsmål",
|
"ui.common.question.other": "spørgsmål",
|
||||||
|
|
||||||
"ui.common.add": "Tilføj",
|
"ui.common.add": "Tilføj",
|
||||||
|
"ui.common.back": "Tilbage",
|
||||||
"ui.common.cancel": "Annuller",
|
"ui.common.cancel": "Annuller",
|
||||||
"ui.common.confirm": "Bekræft",
|
"ui.common.confirm": "Bekræft",
|
||||||
"ui.common.dismiss": "Afvis",
|
"ui.common.dismiss": "Afvis",
|
||||||
@@ -96,6 +97,7 @@ export const dict = {
|
|||||||
"ui.message.collapse": "Skjul besked",
|
"ui.message.collapse": "Skjul besked",
|
||||||
"ui.message.copy": "Kopier",
|
"ui.message.copy": "Kopier",
|
||||||
"ui.message.copied": "Kopieret!",
|
"ui.message.copied": "Kopieret!",
|
||||||
|
"ui.message.interrupted": "Afbrudt",
|
||||||
"ui.message.attachment.alt": "vedhæftning",
|
"ui.message.attachment.alt": "vedhæftning",
|
||||||
|
|
||||||
"ui.patch.action.deleted": "Slettet",
|
"ui.patch.action.deleted": "Slettet",
|
||||||
@@ -106,6 +108,7 @@ export const dict = {
|
|||||||
"ui.question.subtitle.answered": "{{count}} besvaret",
|
"ui.question.subtitle.answered": "{{count}} besvaret",
|
||||||
"ui.question.answer.none": "(intet svar)",
|
"ui.question.answer.none": "(intet svar)",
|
||||||
"ui.question.review.notAnswered": "(ikke besvaret)",
|
"ui.question.review.notAnswered": "(ikke besvaret)",
|
||||||
"ui.question.multiHint": "(vælg alle der gælder)",
|
"ui.question.multiHint": "Vælg alle der gælder",
|
||||||
|
"ui.question.singleHint": "Vælg ét svar",
|
||||||
"ui.question.custom.placeholder": "Skriv dit svar...",
|
"ui.question.custom.placeholder": "Skriv dit svar...",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ export const dict = {
|
|||||||
"ui.common.question.other": "Fragen",
|
"ui.common.question.other": "Fragen",
|
||||||
|
|
||||||
"ui.common.add": "Hinzufügen",
|
"ui.common.add": "Hinzufügen",
|
||||||
|
"ui.common.back": "Zurück",
|
||||||
"ui.common.cancel": "Abbrechen",
|
"ui.common.cancel": "Abbrechen",
|
||||||
"ui.common.confirm": "Bestätigen",
|
"ui.common.confirm": "Bestätigen",
|
||||||
"ui.common.dismiss": "Verwerfen",
|
"ui.common.dismiss": "Verwerfen",
|
||||||
@@ -100,6 +101,7 @@ export const dict = {
|
|||||||
"ui.message.collapse": "Nachricht reduzieren",
|
"ui.message.collapse": "Nachricht reduzieren",
|
||||||
"ui.message.copy": "Kopieren",
|
"ui.message.copy": "Kopieren",
|
||||||
"ui.message.copied": "Kopiert!",
|
"ui.message.copied": "Kopiert!",
|
||||||
|
"ui.message.interrupted": "Unterbrochen",
|
||||||
"ui.message.attachment.alt": "Anhang",
|
"ui.message.attachment.alt": "Anhang",
|
||||||
|
|
||||||
"ui.patch.action.deleted": "Gelöscht",
|
"ui.patch.action.deleted": "Gelöscht",
|
||||||
@@ -110,6 +112,7 @@ export const dict = {
|
|||||||
"ui.question.subtitle.answered": "{{count}} beantwortet",
|
"ui.question.subtitle.answered": "{{count}} beantwortet",
|
||||||
"ui.question.answer.none": "(keine Antwort)",
|
"ui.question.answer.none": "(keine Antwort)",
|
||||||
"ui.question.review.notAnswered": "(nicht beantwortet)",
|
"ui.question.review.notAnswered": "(nicht beantwortet)",
|
||||||
"ui.question.multiHint": "(alle zutreffenden auswählen)",
|
"ui.question.multiHint": "Alle zutreffenden auswählen",
|
||||||
|
"ui.question.singleHint": "Eine Antwort auswählen",
|
||||||
"ui.question.custom.placeholder": "Geben Sie Ihre Antwort ein...",
|
"ui.question.custom.placeholder": "Geben Sie Ihre Antwort ein...",
|
||||||
} satisfies Partial<Record<Keys, string>>
|
} satisfies Partial<Record<Keys, string>>
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export const dict = {
|
|||||||
"ui.common.question.other": "questions",
|
"ui.common.question.other": "questions",
|
||||||
|
|
||||||
"ui.common.add": "Add",
|
"ui.common.add": "Add",
|
||||||
|
"ui.common.back": "Back",
|
||||||
"ui.common.cancel": "Cancel",
|
"ui.common.cancel": "Cancel",
|
||||||
"ui.common.confirm": "Confirm",
|
"ui.common.confirm": "Confirm",
|
||||||
"ui.common.dismiss": "Dismiss",
|
"ui.common.dismiss": "Dismiss",
|
||||||
@@ -96,7 +97,8 @@ export const dict = {
|
|||||||
"ui.message.expand": "Expand message",
|
"ui.message.expand": "Expand message",
|
||||||
"ui.message.collapse": "Collapse message",
|
"ui.message.collapse": "Collapse message",
|
||||||
"ui.message.copy": "Copy",
|
"ui.message.copy": "Copy",
|
||||||
"ui.message.copied": "Copied!",
|
"ui.message.copied": "Copied",
|
||||||
|
"ui.message.interrupted": "Interrupted",
|
||||||
"ui.message.attachment.alt": "attachment",
|
"ui.message.attachment.alt": "attachment",
|
||||||
|
|
||||||
"ui.patch.action.deleted": "Deleted",
|
"ui.patch.action.deleted": "Deleted",
|
||||||
@@ -107,6 +109,7 @@ export const dict = {
|
|||||||
"ui.question.subtitle.answered": "{{count}} answered",
|
"ui.question.subtitle.answered": "{{count}} answered",
|
||||||
"ui.question.answer.none": "(no answer)",
|
"ui.question.answer.none": "(no answer)",
|
||||||
"ui.question.review.notAnswered": "(not answered)",
|
"ui.question.review.notAnswered": "(not answered)",
|
||||||
"ui.question.multiHint": "(select all that apply)",
|
"ui.question.multiHint": "Select all answers that apply",
|
||||||
|
"ui.question.singleHint": "Select one answer",
|
||||||
"ui.question.custom.placeholder": "Type your answer...",
|
"ui.question.custom.placeholder": "Type your answer...",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export const dict = {
|
|||||||
"ui.common.question.other": "preguntas",
|
"ui.common.question.other": "preguntas",
|
||||||
|
|
||||||
"ui.common.add": "Añadir",
|
"ui.common.add": "Añadir",
|
||||||
|
"ui.common.back": "Atrás",
|
||||||
"ui.common.cancel": "Cancelar",
|
"ui.common.cancel": "Cancelar",
|
||||||
"ui.common.confirm": "Confirmar",
|
"ui.common.confirm": "Confirmar",
|
||||||
"ui.common.dismiss": "Descartar",
|
"ui.common.dismiss": "Descartar",
|
||||||
@@ -97,6 +98,7 @@ export const dict = {
|
|||||||
"ui.message.collapse": "Colapsar mensaje",
|
"ui.message.collapse": "Colapsar mensaje",
|
||||||
"ui.message.copy": "Copiar",
|
"ui.message.copy": "Copiar",
|
||||||
"ui.message.copied": "¡Copiado!",
|
"ui.message.copied": "¡Copiado!",
|
||||||
|
"ui.message.interrupted": "Interrumpido",
|
||||||
"ui.message.attachment.alt": "adjunto",
|
"ui.message.attachment.alt": "adjunto",
|
||||||
|
|
||||||
"ui.patch.action.deleted": "Eliminado",
|
"ui.patch.action.deleted": "Eliminado",
|
||||||
@@ -107,6 +109,7 @@ export const dict = {
|
|||||||
"ui.question.subtitle.answered": "{{count}} respondidas",
|
"ui.question.subtitle.answered": "{{count}} respondidas",
|
||||||
"ui.question.answer.none": "(sin respuesta)",
|
"ui.question.answer.none": "(sin respuesta)",
|
||||||
"ui.question.review.notAnswered": "(no respondida)",
|
"ui.question.review.notAnswered": "(no respondida)",
|
||||||
"ui.question.multiHint": "(selecciona todas las que correspondan)",
|
"ui.question.multiHint": "Selecciona todas las que correspondan",
|
||||||
|
"ui.question.singleHint": "Selecciona una respuesta",
|
||||||
"ui.question.custom.placeholder": "Escribe tu respuesta...",
|
"ui.question.custom.placeholder": "Escribe tu respuesta...",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export const dict = {
|
|||||||
"ui.common.question.other": "questions",
|
"ui.common.question.other": "questions",
|
||||||
|
|
||||||
"ui.common.add": "Ajouter",
|
"ui.common.add": "Ajouter",
|
||||||
|
"ui.common.back": "Retour",
|
||||||
"ui.common.cancel": "Annuler",
|
"ui.common.cancel": "Annuler",
|
||||||
"ui.common.confirm": "Confirmer",
|
"ui.common.confirm": "Confirmer",
|
||||||
"ui.common.dismiss": "Ignorer",
|
"ui.common.dismiss": "Ignorer",
|
||||||
@@ -97,6 +98,7 @@ export const dict = {
|
|||||||
"ui.message.collapse": "Réduire le message",
|
"ui.message.collapse": "Réduire le message",
|
||||||
"ui.message.copy": "Copier",
|
"ui.message.copy": "Copier",
|
||||||
"ui.message.copied": "Copié !",
|
"ui.message.copied": "Copié !",
|
||||||
|
"ui.message.interrupted": "Interrompu",
|
||||||
"ui.message.attachment.alt": "pièce jointe",
|
"ui.message.attachment.alt": "pièce jointe",
|
||||||
|
|
||||||
"ui.patch.action.deleted": "Supprimé",
|
"ui.patch.action.deleted": "Supprimé",
|
||||||
@@ -107,6 +109,7 @@ export const dict = {
|
|||||||
"ui.question.subtitle.answered": "{{count}} répondu(s)",
|
"ui.question.subtitle.answered": "{{count}} répondu(s)",
|
||||||
"ui.question.answer.none": "(pas de réponse)",
|
"ui.question.answer.none": "(pas de réponse)",
|
||||||
"ui.question.review.notAnswered": "(non répondu)",
|
"ui.question.review.notAnswered": "(non répondu)",
|
||||||
"ui.question.multiHint": "(sélectionnez tout ce qui s'applique)",
|
"ui.question.multiHint": "Sélectionnez tout ce qui s'applique",
|
||||||
|
"ui.question.singleHint": "Sélectionnez une réponse",
|
||||||
"ui.question.custom.placeholder": "Tapez votre réponse...",
|
"ui.question.custom.placeholder": "Tapez votre réponse...",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ export const dict = {
|
|||||||
"ui.common.question.other": "質問",
|
"ui.common.question.other": "質問",
|
||||||
|
|
||||||
"ui.common.add": "追加",
|
"ui.common.add": "追加",
|
||||||
|
"ui.common.back": "戻る",
|
||||||
"ui.common.cancel": "キャンセル",
|
"ui.common.cancel": "キャンセル",
|
||||||
"ui.common.confirm": "確認",
|
"ui.common.confirm": "確認",
|
||||||
"ui.common.dismiss": "閉じる",
|
"ui.common.dismiss": "閉じる",
|
||||||
@@ -96,6 +97,7 @@ export const dict = {
|
|||||||
"ui.message.collapse": "メッセージを折りたたむ",
|
"ui.message.collapse": "メッセージを折りたたむ",
|
||||||
"ui.message.copy": "コピー",
|
"ui.message.copy": "コピー",
|
||||||
"ui.message.copied": "コピーしました!",
|
"ui.message.copied": "コピーしました!",
|
||||||
|
"ui.message.interrupted": "中断",
|
||||||
"ui.message.attachment.alt": "添付ファイル",
|
"ui.message.attachment.alt": "添付ファイル",
|
||||||
|
|
||||||
"ui.patch.action.deleted": "削除済み",
|
"ui.patch.action.deleted": "削除済み",
|
||||||
@@ -106,6 +108,7 @@ export const dict = {
|
|||||||
"ui.question.subtitle.answered": "{{count}}件回答済み",
|
"ui.question.subtitle.answered": "{{count}}件回答済み",
|
||||||
"ui.question.answer.none": "(回答なし)",
|
"ui.question.answer.none": "(回答なし)",
|
||||||
"ui.question.review.notAnswered": "(未回答)",
|
"ui.question.review.notAnswered": "(未回答)",
|
||||||
"ui.question.multiHint": "(該当するものをすべて選択)",
|
"ui.question.multiHint": "該当するものをすべて選択",
|
||||||
|
"ui.question.singleHint": "1 つ選択",
|
||||||
"ui.question.custom.placeholder": "回答を入力...",
|
"ui.question.custom.placeholder": "回答を入力...",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export const dict = {
|
|||||||
"ui.common.question.other": "질문",
|
"ui.common.question.other": "질문",
|
||||||
|
|
||||||
"ui.common.add": "추가",
|
"ui.common.add": "추가",
|
||||||
|
"ui.common.back": "뒤로",
|
||||||
"ui.common.cancel": "취소",
|
"ui.common.cancel": "취소",
|
||||||
"ui.common.confirm": "확인",
|
"ui.common.confirm": "확인",
|
||||||
"ui.common.dismiss": "닫기",
|
"ui.common.dismiss": "닫기",
|
||||||
@@ -97,6 +98,7 @@ export const dict = {
|
|||||||
"ui.message.collapse": "메시지 접기",
|
"ui.message.collapse": "메시지 접기",
|
||||||
"ui.message.copy": "복사",
|
"ui.message.copy": "복사",
|
||||||
"ui.message.copied": "복사됨!",
|
"ui.message.copied": "복사됨!",
|
||||||
|
"ui.message.interrupted": "중단됨",
|
||||||
"ui.message.attachment.alt": "첨부 파일",
|
"ui.message.attachment.alt": "첨부 파일",
|
||||||
|
|
||||||
"ui.patch.action.deleted": "삭제됨",
|
"ui.patch.action.deleted": "삭제됨",
|
||||||
@@ -107,6 +109,7 @@ export const dict = {
|
|||||||
"ui.question.subtitle.answered": "{{count}}개 답변됨",
|
"ui.question.subtitle.answered": "{{count}}개 답변됨",
|
||||||
"ui.question.answer.none": "(답변 없음)",
|
"ui.question.answer.none": "(답변 없음)",
|
||||||
"ui.question.review.notAnswered": "(답변되지 않음)",
|
"ui.question.review.notAnswered": "(답변되지 않음)",
|
||||||
"ui.question.multiHint": "(해당하는 항목 모두 선택)",
|
"ui.question.multiHint": "해당하는 항목 모두 선택",
|
||||||
|
"ui.question.singleHint": "하나의 답변을 선택",
|
||||||
"ui.question.custom.placeholder": "답변 입력...",
|
"ui.question.custom.placeholder": "답변 입력...",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ export const dict: Record<Keys, string> = {
|
|||||||
"ui.common.question.other": "spørsmål",
|
"ui.common.question.other": "spørsmål",
|
||||||
|
|
||||||
"ui.common.add": "Legg til",
|
"ui.common.add": "Legg til",
|
||||||
|
"ui.common.back": "Tilbake",
|
||||||
"ui.common.cancel": "Avbryt",
|
"ui.common.cancel": "Avbryt",
|
||||||
"ui.common.confirm": "Bekreft",
|
"ui.common.confirm": "Bekreft",
|
||||||
"ui.common.dismiss": "Avvis",
|
"ui.common.dismiss": "Avvis",
|
||||||
@@ -100,6 +101,7 @@ export const dict: Record<Keys, string> = {
|
|||||||
"ui.message.collapse": "Skjul melding",
|
"ui.message.collapse": "Skjul melding",
|
||||||
"ui.message.copy": "Kopier",
|
"ui.message.copy": "Kopier",
|
||||||
"ui.message.copied": "Kopiert!",
|
"ui.message.copied": "Kopiert!",
|
||||||
|
"ui.message.interrupted": "Avbrutt",
|
||||||
"ui.message.attachment.alt": "vedlegg",
|
"ui.message.attachment.alt": "vedlegg",
|
||||||
|
|
||||||
"ui.patch.action.deleted": "Slettet",
|
"ui.patch.action.deleted": "Slettet",
|
||||||
@@ -110,6 +112,7 @@ export const dict: Record<Keys, string> = {
|
|||||||
"ui.question.subtitle.answered": "{{count}} besvart",
|
"ui.question.subtitle.answered": "{{count}} besvart",
|
||||||
"ui.question.answer.none": "(ingen svar)",
|
"ui.question.answer.none": "(ingen svar)",
|
||||||
"ui.question.review.notAnswered": "(ikke besvart)",
|
"ui.question.review.notAnswered": "(ikke besvart)",
|
||||||
"ui.question.multiHint": "(velg alle som gjelder)",
|
"ui.question.multiHint": "Velg alle som gjelder",
|
||||||
|
"ui.question.singleHint": "Velg ett svar",
|
||||||
"ui.question.custom.placeholder": "Skriv svaret ditt...",
|
"ui.question.custom.placeholder": "Skriv svaret ditt...",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ export const dict = {
|
|||||||
"ui.common.question.other": "pytania",
|
"ui.common.question.other": "pytania",
|
||||||
|
|
||||||
"ui.common.add": "Dodaj",
|
"ui.common.add": "Dodaj",
|
||||||
|
"ui.common.back": "Wstecz",
|
||||||
"ui.common.cancel": "Anuluj",
|
"ui.common.cancel": "Anuluj",
|
||||||
"ui.common.confirm": "Potwierdź",
|
"ui.common.confirm": "Potwierdź",
|
||||||
"ui.common.dismiss": "Odrzuć",
|
"ui.common.dismiss": "Odrzuć",
|
||||||
@@ -96,6 +97,7 @@ export const dict = {
|
|||||||
"ui.message.collapse": "Zwiń wiadomość",
|
"ui.message.collapse": "Zwiń wiadomość",
|
||||||
"ui.message.copy": "Kopiuj",
|
"ui.message.copy": "Kopiuj",
|
||||||
"ui.message.copied": "Skopiowano!",
|
"ui.message.copied": "Skopiowano!",
|
||||||
|
"ui.message.interrupted": "Przerwano",
|
||||||
"ui.message.attachment.alt": "załącznik",
|
"ui.message.attachment.alt": "załącznik",
|
||||||
|
|
||||||
"ui.patch.action.deleted": "Usunięto",
|
"ui.patch.action.deleted": "Usunięto",
|
||||||
@@ -106,6 +108,7 @@ export const dict = {
|
|||||||
"ui.question.subtitle.answered": "{{count}} odpowiedzi",
|
"ui.question.subtitle.answered": "{{count}} odpowiedzi",
|
||||||
"ui.question.answer.none": "(brak odpowiedzi)",
|
"ui.question.answer.none": "(brak odpowiedzi)",
|
||||||
"ui.question.review.notAnswered": "(bez odpowiedzi)",
|
"ui.question.review.notAnswered": "(bez odpowiedzi)",
|
||||||
"ui.question.multiHint": "(zaznacz wszystkie pasujące)",
|
"ui.question.multiHint": "Zaznacz wszystkie pasujące",
|
||||||
|
"ui.question.singleHint": "Wybierz jedną odpowiedź",
|
||||||
"ui.question.custom.placeholder": "Wpisz swoją odpowiedź...",
|
"ui.question.custom.placeholder": "Wpisz swoją odpowiedź...",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ export const dict = {
|
|||||||
"ui.common.question.other": "вопросов",
|
"ui.common.question.other": "вопросов",
|
||||||
|
|
||||||
"ui.common.add": "Добавить",
|
"ui.common.add": "Добавить",
|
||||||
|
"ui.common.back": "Назад",
|
||||||
"ui.common.cancel": "Отмена",
|
"ui.common.cancel": "Отмена",
|
||||||
"ui.common.confirm": "Подтвердить",
|
"ui.common.confirm": "Подтвердить",
|
||||||
"ui.common.dismiss": "Закрыть",
|
"ui.common.dismiss": "Закрыть",
|
||||||
@@ -96,6 +97,7 @@ export const dict = {
|
|||||||
"ui.message.collapse": "Свернуть сообщение",
|
"ui.message.collapse": "Свернуть сообщение",
|
||||||
"ui.message.copy": "Копировать",
|
"ui.message.copy": "Копировать",
|
||||||
"ui.message.copied": "Скопировано!",
|
"ui.message.copied": "Скопировано!",
|
||||||
|
"ui.message.interrupted": "Прервано",
|
||||||
"ui.message.attachment.alt": "вложение",
|
"ui.message.attachment.alt": "вложение",
|
||||||
|
|
||||||
"ui.patch.action.deleted": "Удалено",
|
"ui.patch.action.deleted": "Удалено",
|
||||||
@@ -106,6 +108,7 @@ export const dict = {
|
|||||||
"ui.question.subtitle.answered": "{{count}} отвечено",
|
"ui.question.subtitle.answered": "{{count}} отвечено",
|
||||||
"ui.question.answer.none": "(нет ответа)",
|
"ui.question.answer.none": "(нет ответа)",
|
||||||
"ui.question.review.notAnswered": "(не отвечено)",
|
"ui.question.review.notAnswered": "(не отвечено)",
|
||||||
"ui.question.multiHint": "(выберите все подходящие)",
|
"ui.question.multiHint": "Выберите все подходящие",
|
||||||
|
"ui.question.singleHint": "Выберите один ответ",
|
||||||
"ui.question.custom.placeholder": "Введите ваш ответ...",
|
"ui.question.custom.placeholder": "Введите ваш ответ...",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export const dict = {
|
|||||||
"ui.common.question.other": "คำถาม",
|
"ui.common.question.other": "คำถาม",
|
||||||
|
|
||||||
"ui.common.add": "เพิ่ม",
|
"ui.common.add": "เพิ่ม",
|
||||||
|
"ui.common.back": "ย้อนกลับ",
|
||||||
"ui.common.cancel": "ยกเลิก",
|
"ui.common.cancel": "ยกเลิก",
|
||||||
"ui.common.confirm": "ยืนยัน",
|
"ui.common.confirm": "ยืนยัน",
|
||||||
"ui.common.dismiss": "ปิด",
|
"ui.common.dismiss": "ปิด",
|
||||||
@@ -97,6 +98,7 @@ export const dict = {
|
|||||||
"ui.message.collapse": "ย่อข้อความ",
|
"ui.message.collapse": "ย่อข้อความ",
|
||||||
"ui.message.copy": "คัดลอก",
|
"ui.message.copy": "คัดลอก",
|
||||||
"ui.message.copied": "คัดลอกแล้ว!",
|
"ui.message.copied": "คัดลอกแล้ว!",
|
||||||
|
"ui.message.interrupted": "ถูกขัดจังหวะ",
|
||||||
"ui.message.attachment.alt": "ไฟล์แนบ",
|
"ui.message.attachment.alt": "ไฟล์แนบ",
|
||||||
|
|
||||||
"ui.patch.action.deleted": "ลบ",
|
"ui.patch.action.deleted": "ลบ",
|
||||||
@@ -107,6 +109,7 @@ export const dict = {
|
|||||||
"ui.question.subtitle.answered": "{{count}} ตอบแล้ว",
|
"ui.question.subtitle.answered": "{{count}} ตอบแล้ว",
|
||||||
"ui.question.answer.none": "(ไม่มีคำตอบ)",
|
"ui.question.answer.none": "(ไม่มีคำตอบ)",
|
||||||
"ui.question.review.notAnswered": "(ไม่ได้ตอบ)",
|
"ui.question.review.notAnswered": "(ไม่ได้ตอบ)",
|
||||||
"ui.question.multiHint": "(เลือกทั้งหมดที่ใช้)",
|
"ui.question.multiHint": "เลือกทั้งหมดที่ใช้",
|
||||||
|
"ui.question.singleHint": "เลือกหนึ่งคำตอบ",
|
||||||
"ui.question.custom.placeholder": "พิมพ์คำตอบของคุณ...",
|
"ui.question.custom.placeholder": "พิมพ์คำตอบของคุณ...",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export const dict = {
|
|||||||
"ui.common.question.other": "个问题",
|
"ui.common.question.other": "个问题",
|
||||||
|
|
||||||
"ui.common.add": "添加",
|
"ui.common.add": "添加",
|
||||||
|
"ui.common.back": "返回",
|
||||||
"ui.common.cancel": "取消",
|
"ui.common.cancel": "取消",
|
||||||
"ui.common.confirm": "确认",
|
"ui.common.confirm": "确认",
|
||||||
"ui.common.dismiss": "忽略",
|
"ui.common.dismiss": "忽略",
|
||||||
@@ -101,6 +102,7 @@ export const dict = {
|
|||||||
"ui.message.collapse": "收起消息",
|
"ui.message.collapse": "收起消息",
|
||||||
"ui.message.copy": "复制",
|
"ui.message.copy": "复制",
|
||||||
"ui.message.copied": "已复制!",
|
"ui.message.copied": "已复制!",
|
||||||
|
"ui.message.interrupted": "已中断",
|
||||||
"ui.message.attachment.alt": "附件",
|
"ui.message.attachment.alt": "附件",
|
||||||
|
|
||||||
"ui.patch.action.deleted": "已删除",
|
"ui.patch.action.deleted": "已删除",
|
||||||
@@ -111,6 +113,7 @@ export const dict = {
|
|||||||
"ui.question.subtitle.answered": "{{count}} 已回答",
|
"ui.question.subtitle.answered": "{{count}} 已回答",
|
||||||
"ui.question.answer.none": "(无答案)",
|
"ui.question.answer.none": "(无答案)",
|
||||||
"ui.question.review.notAnswered": "(未回答)",
|
"ui.question.review.notAnswered": "(未回答)",
|
||||||
"ui.question.multiHint": "(可多选)",
|
"ui.question.multiHint": "可多选",
|
||||||
|
"ui.question.singleHint": "选择一个答案",
|
||||||
"ui.question.custom.placeholder": "输入你的答案...",
|
"ui.question.custom.placeholder": "输入你的答案...",
|
||||||
} satisfies Partial<Record<Keys, string>>
|
} satisfies Partial<Record<Keys, string>>
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export const dict = {
|
|||||||
"ui.common.question.other": "個問題",
|
"ui.common.question.other": "個問題",
|
||||||
|
|
||||||
"ui.common.add": "新增",
|
"ui.common.add": "新增",
|
||||||
|
"ui.common.back": "返回",
|
||||||
"ui.common.cancel": "取消",
|
"ui.common.cancel": "取消",
|
||||||
"ui.common.confirm": "確認",
|
"ui.common.confirm": "確認",
|
||||||
"ui.common.dismiss": "忽略",
|
"ui.common.dismiss": "忽略",
|
||||||
@@ -101,6 +102,7 @@ export const dict = {
|
|||||||
"ui.message.collapse": "收合訊息",
|
"ui.message.collapse": "收合訊息",
|
||||||
"ui.message.copy": "複製",
|
"ui.message.copy": "複製",
|
||||||
"ui.message.copied": "已複製!",
|
"ui.message.copied": "已複製!",
|
||||||
|
"ui.message.interrupted": "已中斷",
|
||||||
"ui.message.attachment.alt": "附件",
|
"ui.message.attachment.alt": "附件",
|
||||||
|
|
||||||
"ui.patch.action.deleted": "已刪除",
|
"ui.patch.action.deleted": "已刪除",
|
||||||
@@ -111,6 +113,7 @@ export const dict = {
|
|||||||
"ui.question.subtitle.answered": "{{count}} 已回答",
|
"ui.question.subtitle.answered": "{{count}} 已回答",
|
||||||
"ui.question.answer.none": "(無答案)",
|
"ui.question.answer.none": "(無答案)",
|
||||||
"ui.question.review.notAnswered": "(未回答)",
|
"ui.question.review.notAnswered": "(未回答)",
|
||||||
"ui.question.multiHint": "(可多選)",
|
"ui.question.multiHint": "可多選",
|
||||||
|
"ui.question.singleHint": "選擇一個答案",
|
||||||
"ui.question.custom.placeholder": "輸入你的答案...",
|
"ui.question.custom.placeholder": "輸入你的答案...",
|
||||||
} satisfies Partial<Record<Keys, string>>
|
} satisfies Partial<Record<Keys, string>>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
:root {
|
:root {
|
||||||
--animate-pulse: pulse-opacity 2s ease-in-out infinite;
|
--animate-pulse: pulse-opacity 2s ease-in-out infinite;
|
||||||
|
--animate-pulse-scale: pulse-scale 1.2s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse-opacity {
|
@keyframes pulse-opacity {
|
||||||
@@ -12,6 +13,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-scale {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(0.6666667);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes pulse-opacity-dim {
|
@keyframes pulse-opacity-dim {
|
||||||
0%,
|
0%,
|
||||||
100% {
|
100% {
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
@import "../components/sticky-accordion-header.css" layer(components);
|
@import "../components/sticky-accordion-header.css" layer(components);
|
||||||
@import "../components/tabs.css" layer(components);
|
@import "../components/tabs.css" layer(components);
|
||||||
@import "../components/tag.css" layer(components);
|
@import "../components/tag.css" layer(components);
|
||||||
|
@import "../components/text-shimmer.css" layer(components);
|
||||||
@import "../components/toast.css" layer(components);
|
@import "../components/toast.css" layer(components);
|
||||||
@import "../components/tooltip.css" layer(components);
|
@import "../components/tooltip.css" layer(components);
|
||||||
@import "../components/typewriter.css" layer(components);
|
@import "../components/typewriter.css" layer(components);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
--font-size-x-large: 20px;
|
--font-size-x-large: 20px;
|
||||||
--font-weight-regular: 400;
|
--font-weight-regular: 400;
|
||||||
--font-weight-medium: 500;
|
--font-weight-medium: 500;
|
||||||
|
--line-height-normal: 130%;
|
||||||
--line-height-large: 150%;
|
--line-height-large: 150%;
|
||||||
--line-height-x-large: 180%;
|
--line-height-x-large: 180%;
|
||||||
--line-height-2x-large: 200%;
|
--line-height-2x-large: 200%;
|
||||||
|
|||||||
Reference in New Issue
Block a user