Merge branch 'dev' into desktop-poilsh-styles-ui-ux
This commit is contained in:
commit
e3b78fc03a
170 changed files with 12745 additions and 7027 deletions
|
|
@ -16,7 +16,6 @@
|
|||
[data-component="dialog"] {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
margin-left: var(--dialog-left-margin);
|
||||
z-index: 50;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -25,7 +24,7 @@
|
|||
[data-slot="dialog-container"] {
|
||||
position: relative;
|
||||
z-index: 50;
|
||||
width: min(calc(100vw - 16px), 480px);
|
||||
width: min(calc(100vw - 16px), 640px);
|
||||
height: min(calc(100vh - 16px), 512px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -120,9 +119,15 @@
|
|||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
&[data-fit] {
|
||||
[data-slot="dialog-container"] {
|
||||
height: auto;
|
||||
|
||||
[data-slot="dialog-content"] {
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,12 @@ export interface DialogProps extends ParentProps {
|
|||
action?: JSXElement
|
||||
class?: ComponentProps<"div">["class"]
|
||||
classList?: ComponentProps<"div">["classList"]
|
||||
fit?: boolean
|
||||
}
|
||||
|
||||
export function Dialog(props: DialogProps) {
|
||||
return (
|
||||
<div data-component="dialog">
|
||||
<div data-component="dialog" data-fit={props.fit ? true : undefined}>
|
||||
<div data-slot="dialog-container">
|
||||
<Kobalte.Content
|
||||
data-slot="dialog-content"
|
||||
|
|
|
|||
17
packages/ui/src/components/inline-input.css
Normal file
17
packages/ui/src/components/inline-input.css
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[data-component="inline-input"] {
|
||||
color: inherit;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 0;
|
||||
min-width: 0;
|
||||
font: inherit;
|
||||
letter-spacing: inherit;
|
||||
line-height: inherit;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 1px var(--border-interactive-focus);
|
||||
}
|
||||
}
|
||||
11
packages/ui/src/components/inline-input.tsx
Normal file
11
packages/ui/src/components/inline-input.tsx
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import type { ComponentProps } from "solid-js"
|
||||
import { splitProps } from "solid-js"
|
||||
|
||||
export type InlineInputProps = ComponentProps<"input"> & {
|
||||
width?: string
|
||||
}
|
||||
|
||||
export function InlineInput(props: InlineInputProps) {
|
||||
const [local, others] = splitProps(props, ["class", "width"])
|
||||
return <input data-component="inline-input" class={local.class} style={{ width: local.width }} {...others} />
|
||||
}
|
||||
18
packages/ui/src/components/keybind.css
Normal file
18
packages/ui/src/components/keybind.css
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
[data-component="keybind"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
height: 20px;
|
||||
padding: 0 8px;
|
||||
border-radius: 2px;
|
||||
background: var(--surface-base);
|
||||
box-shadow: var(--shadow-xxs-border);
|
||||
|
||||
/* text-12-medium */
|
||||
font-family: var(--font-family-sans);
|
||||
font-size: 12px;
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 1;
|
||||
color: var(--text-weak);
|
||||
}
|
||||
20
packages/ui/src/components/keybind.tsx
Normal file
20
packages/ui/src/components/keybind.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import type { ComponentProps, ParentProps } from "solid-js"
|
||||
|
||||
export interface KeybindProps extends ParentProps {
|
||||
class?: string
|
||||
classList?: ComponentProps<"span">["classList"]
|
||||
}
|
||||
|
||||
export function Keybind(props: KeybindProps) {
|
||||
return (
|
||||
<span
|
||||
data-component="keybind"
|
||||
classList={{
|
||||
...(props.classList ?? {}),
|
||||
[props.class ?? ""]: !!props.class,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
|
@ -56,11 +56,14 @@
|
|||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: transparent;
|
||||
opacity: 0.5;
|
||||
transition: opacity 0.15s ease;
|
||||
|
||||
&:hover:not(:disabled),
|
||||
&:focus:not(:disabled),
|
||||
&:active:not(:disabled) {
|
||||
background-color: transparent;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&:hover:not(:disabled) [data-slot="icon-svg"] {
|
||||
|
|
@ -88,7 +91,7 @@
|
|||
|
||||
[data-slot="list-empty-state"] {
|
||||
display: flex;
|
||||
padding: 32px 0px;
|
||||
padding: 32px 48px;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
|
@ -100,8 +103,9 @@
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
max-width: 100%;
|
||||
color: var(--text-weak);
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
|
||||
/* text-14-regular */
|
||||
font-family: var(--font-family-sans);
|
||||
|
|
@ -114,6 +118,8 @@
|
|||
|
||||
[data-slot="list-filter"] {
|
||||
color: var(--text-strong);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -122,10 +128,14 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
[data-slot="list-header"] {
|
||||
display: flex;
|
||||
z-index: 10;
|
||||
padding: 0 12px 8px 8px;
|
||||
padding: 8px 12px 8px 12px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
|
|
@ -133,7 +143,7 @@
|
|||
position: sticky;
|
||||
top: 0;
|
||||
|
||||
color: var(--text-base);
|
||||
color: var(--text-weak);
|
||||
|
||||
/* text-14-medium */
|
||||
font-family: var(--font-family-sans);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import { TextField } from "./text-field"
|
|||
export interface ListSearchProps {
|
||||
placeholder?: string
|
||||
autofocus?: boolean
|
||||
hideIcon?: boolean
|
||||
class?: string
|
||||
}
|
||||
|
||||
export interface ListProps<T> extends FilteredListProps<T> {
|
||||
|
|
@ -68,7 +70,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
|
|||
if (!props.current) return
|
||||
const key = props.key(props.current)
|
||||
requestAnimationFrame(() => {
|
||||
const element = scrollRef()?.querySelector(`[data-key="${key}"]`)
|
||||
const element = scrollRef()?.querySelector(`[data-key="${CSS.escape(key)}"]`)
|
||||
element?.scrollIntoView({ block: "center" })
|
||||
})
|
||||
})
|
||||
|
|
@ -80,8 +82,8 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
|
|||
scrollRef()?.scrollTo(0, 0)
|
||||
return
|
||||
}
|
||||
const element = scrollRef()?.querySelector(`[data-key="${active()}"]`)
|
||||
element?.scrollIntoView({ block: "center", behavior: "smooth" })
|
||||
const element = scrollRef()?.querySelector(`[data-key="${CSS.escape(active()!)}"]`)
|
||||
element?.scrollIntoView({ block: "center" })
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
|
|
@ -147,9 +149,11 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
|
|||
return (
|
||||
<div data-component="list" classList={{ [props.class ?? ""]: !!props.class }}>
|
||||
<Show when={!!props.search}>
|
||||
<div data-slot="list-search">
|
||||
<div data-slot="list-search" classList={{ [searchProps().class ?? ""]: !!searchProps().class }}>
|
||||
<div data-slot="list-search-container">
|
||||
<Icon name="magnifying-glass" />
|
||||
<Show when={!searchProps().hideIcon}>
|
||||
<Icon name="magnifying-glass" />
|
||||
</Show>
|
||||
<TextField
|
||||
autofocus={searchProps().autofocus}
|
||||
variant="ghost"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,21 @@ export const Mark = (props: { class?: string }) => {
|
|||
)
|
||||
}
|
||||
|
||||
export const Splash = (props: { class?: string }) => {
|
||||
return (
|
||||
<svg
|
||||
data-component="logo-splash"
|
||||
classList={{ [props.class ?? ""]: !!props.class }}
|
||||
viewBox="0 0 80 100"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M60 80H20V40H60V80Z" fill="var(--icon-base)" />
|
||||
<path d="M60 20H20V80H60V20ZM80 100H0V0H80V100Z" fill="var(--icon-strong-base)" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export const Logo = (props: { class?: string }) => {
|
||||
return (
|
||||
<svg
|
||||
|
|
|
|||
|
|
@ -689,3 +689,75 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="apply-patch-files"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
[data-component="apply-patch-file"] {
|
||||
display: flex;
|
||||
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"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background-color: var(--surface-inset-base);
|
||||
}
|
||||
|
||||
[data-slot="apply-patch-file-action"] {
|
||||
font-family: var(--font-family-sans);
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: var(--line-height-large);
|
||||
color: var(--text-base);
|
||||
flex-shrink: 0;
|
||||
|
||||
&[data-type="delete"] {
|
||||
color: var(--text-critical-base);
|
||||
}
|
||||
|
||||
&[data-type="add"] {
|
||||
color: var(--text-success-base);
|
||||
}
|
||||
|
||||
&[data-type="move"] {
|
||||
color: var(--text-warning-base);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="apply-patch-file-path"] {
|
||||
font-family: var(--font-family-mono);
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--text-weak);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
[data-slot="apply-patch-deletion-count"] {
|
||||
font-family: var(--font-family-mono);
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--text-critical-base);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="apply-patch-file-diff"] {
|
||||
max-height: 420px;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
onCleanup,
|
||||
type JSX,
|
||||
} from "solid-js"
|
||||
import stripAnsi from "strip-ansi"
|
||||
import { Dynamic } from "solid-js/web"
|
||||
import {
|
||||
AgentPart,
|
||||
|
|
@ -232,6 +233,12 @@ export function getToolInfo(tool: string, input: any = {}): ToolInfo {
|
|||
title: "Write",
|
||||
subtitle: input.filePath ? getFilename(input.filePath) : undefined,
|
||||
}
|
||||
case "apply_patch":
|
||||
return {
|
||||
icon: "code-lines",
|
||||
title: "Patch",
|
||||
subtitle: input.files?.length ? `${input.files.length} file${input.files.length > 1 ? "s" : ""}` : undefined,
|
||||
}
|
||||
case "todowrite":
|
||||
return {
|
||||
icon: "checklist",
|
||||
|
|
@ -926,7 +933,7 @@ ToolRegistry.register({
|
|||
>
|
||||
<div data-component="tool-output" data-scrollable>
|
||||
<Markdown
|
||||
text={`\`\`\`command\n$ ${props.input.command ?? props.metadata.command ?? ""}${props.output ? "\n\n" + props.output : ""}\n\`\`\``}
|
||||
text={`\`\`\`command\n$ ${props.input.command ?? props.metadata.command ?? ""}${props.output || props.metadata.output ? "\n\n" + stripAnsi(props.output || props.metadata.output) : ""}\n\`\`\``}
|
||||
/>
|
||||
</div>
|
||||
</BasicTool>
|
||||
|
|
@ -1026,6 +1033,94 @@ ToolRegistry.register({
|
|||
},
|
||||
})
|
||||
|
||||
interface ApplyPatchFile {
|
||||
filePath: string
|
||||
relativePath: string
|
||||
type: "add" | "update" | "delete" | "move"
|
||||
diff: string
|
||||
before: string
|
||||
after: string
|
||||
additions: number
|
||||
deletions: number
|
||||
movePath?: string
|
||||
}
|
||||
|
||||
ToolRegistry.register({
|
||||
name: "apply_patch",
|
||||
render(props) {
|
||||
const diffComponent = useDiffComponent()
|
||||
const files = createMemo(() => (props.metadata.files ?? []) as ApplyPatchFile[])
|
||||
|
||||
const subtitle = createMemo(() => {
|
||||
const count = files().length
|
||||
if (count === 0) return ""
|
||||
return `${count} file${count > 1 ? "s" : ""}`
|
||||
})
|
||||
|
||||
return (
|
||||
<BasicTool
|
||||
{...props}
|
||||
icon="code-lines"
|
||||
trigger={{
|
||||
title: "Patch",
|
||||
subtitle: subtitle(),
|
||||
}}
|
||||
>
|
||||
<Show when={files().length > 0}>
|
||||
<div data-component="apply-patch-files">
|
||||
<For each={files()}>
|
||||
{(file) => (
|
||||
<div data-component="apply-patch-file">
|
||||
<div data-slot="apply-patch-file-header">
|
||||
<Switch>
|
||||
<Match when={file.type === "delete"}>
|
||||
<span data-slot="apply-patch-file-action" data-type="delete">
|
||||
Deleted
|
||||
</span>
|
||||
</Match>
|
||||
<Match when={file.type === "add"}>
|
||||
<span data-slot="apply-patch-file-action" data-type="add">
|
||||
Created
|
||||
</span>
|
||||
</Match>
|
||||
<Match when={file.type === "move"}>
|
||||
<span data-slot="apply-patch-file-action" data-type="move">
|
||||
Moved
|
||||
</span>
|
||||
</Match>
|
||||
<Match when={file.type === "update"}>
|
||||
<span data-slot="apply-patch-file-action" data-type="update">
|
||||
Patched
|
||||
</span>
|
||||
</Match>
|
||||
</Switch>
|
||||
<span data-slot="apply-patch-file-path">{file.relativePath}</span>
|
||||
<Show when={file.type !== "delete"}>
|
||||
<DiffChanges changes={{ additions: file.additions, deletions: file.deletions }} />
|
||||
</Show>
|
||||
<Show when={file.type === "delete"}>
|
||||
<span data-slot="apply-patch-deletion-count">-{file.deletions}</span>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={file.type !== "delete"}>
|
||||
<div data-component="apply-patch-file-diff">
|
||||
<Dynamic
|
||||
component={diffComponent}
|
||||
before={{ name: file.filePath, contents: file.before }}
|
||||
after={{ name: file.filePath, contents: file.after }}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</BasicTool>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
ToolRegistry.register({
|
||||
name: "todowrite",
|
||||
render(props) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import type { ComponentProps } from "solid-js"
|
|||
export interface TooltipProps extends ComponentProps<typeof KobalteTooltip> {
|
||||
value: JSX.Element
|
||||
class?: string
|
||||
contentClass?: string
|
||||
contentStyle?: JSX.CSSProperties
|
||||
inactive?: boolean
|
||||
}
|
||||
|
||||
|
|
@ -30,7 +32,7 @@ export function TooltipKeybind(props: TooltipKeybindProps) {
|
|||
|
||||
export function Tooltip(props: TooltipProps) {
|
||||
const [open, setOpen] = createSignal(false)
|
||||
const [local, others] = splitProps(props, ["children", "class", "inactive"])
|
||||
const [local, others] = splitProps(props, ["children", "class", "contentClass", "contentStyle", "inactive"])
|
||||
|
||||
const c = children(() => local.children)
|
||||
|
||||
|
|
@ -58,7 +60,12 @@ export function Tooltip(props: TooltipProps) {
|
|||
{c()}
|
||||
</KobalteTooltip.Trigger>
|
||||
<KobalteTooltip.Portal>
|
||||
<KobalteTooltip.Content data-component="tooltip" data-placement={props.placement}>
|
||||
<KobalteTooltip.Content
|
||||
data-component="tooltip"
|
||||
data-placement={props.placement}
|
||||
class={local.contentClass}
|
||||
style={local.contentStyle}
|
||||
>
|
||||
{others.value}
|
||||
{/* <KobalteTooltip.Arrow data-slot="tooltip-arrow" /> */}
|
||||
</KobalteTooltip.Content>
|
||||
|
|
|
|||
|
|
@ -24,7 +24,9 @@
|
|||
@import "../components/icon.css" layer(components);
|
||||
@import "../components/icon-button.css" layer(components);
|
||||
@import "../components/image-preview.css" layer(components);
|
||||
@import "../components/keybind.css" layer(components);
|
||||
@import "../components/text-field.css" layer(components);
|
||||
@import "../components/inline-input.css" layer(components);
|
||||
@import "../components/list.css" layer(components);
|
||||
@import "../components/logo.css" layer(components);
|
||||
@import "../components/markdown.css" layer(components);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue