mirror of
https://github.com/sst/opencode.git
synced 2025-09-11 07:26:22 +00:00
share page diff
This commit is contained in:
parent
680d52016c
commit
a4e46e6e18
8 changed files with 413 additions and 72 deletions
|
@ -36,9 +36,5 @@
|
||||||
"esbuild",
|
"esbuild",
|
||||||
"protobufjs",
|
"protobufjs",
|
||||||
"sharp"
|
"sharp"
|
||||||
],
|
]
|
||||||
"dependencies": {
|
|
||||||
"@types/luxon": "^3.6.2",
|
|
||||||
"luxon": "^3.6.1"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,15 @@
|
||||||
"@astrojs/solid-js": "^5.1.0",
|
"@astrojs/solid-js": "^5.1.0",
|
||||||
"@astrojs/starlight": "^0.34.3",
|
"@astrojs/starlight": "^0.34.3",
|
||||||
"@fontsource/ibm-plex-mono": "^5.2.5",
|
"@fontsource/ibm-plex-mono": "^5.2.5",
|
||||||
|
"@shikijs/transformers": "^3.4.2",
|
||||||
|
"@types/luxon": "^3.6.2",
|
||||||
"ai": "^5.0.0-alpha.2",
|
"ai": "^5.0.0-alpha.2",
|
||||||
"astro": "^5.7.13",
|
"astro": "^5.7.13",
|
||||||
|
"diff": "^8.0.2",
|
||||||
|
"luxon": "^3.6.1",
|
||||||
"rehype-autolink-headings": "^7.1.0",
|
"rehype-autolink-headings": "^7.1.0",
|
||||||
"sharp": "^0.32.5",
|
"sharp": "^0.32.5",
|
||||||
|
"shiki": "^3.4.2",
|
||||||
"solid-js": "^1.9.7",
|
"solid-js": "^1.9.7",
|
||||||
"toolbeam-docs-theme": "^0.2.4"
|
"toolbeam-docs-theme": "^0.2.4"
|
||||||
}
|
}
|
||||||
|
|
47
app/packages/web/src/components/CodeBlock.tsx
Normal file
47
app/packages/web/src/components/CodeBlock.tsx
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import {
|
||||||
|
type JSX,
|
||||||
|
onCleanup,
|
||||||
|
splitProps,
|
||||||
|
createEffect,
|
||||||
|
createResource,
|
||||||
|
} from "solid-js"
|
||||||
|
import { codeToHtml } from "shiki"
|
||||||
|
import { transformerNotationDiff } from '@shikijs/transformers'
|
||||||
|
|
||||||
|
interface CodeBlockProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
||||||
|
code: string
|
||||||
|
lang?: string
|
||||||
|
}
|
||||||
|
function CodeBlock(props: CodeBlockProps) {
|
||||||
|
const [local, rest] = splitProps(props, ["code", "lang"])
|
||||||
|
let containerRef!: HTMLDivElement
|
||||||
|
|
||||||
|
const [html] = createResource(async () => {
|
||||||
|
return (await codeToHtml(local.code, {
|
||||||
|
lang: local.lang || "text",
|
||||||
|
themes: {
|
||||||
|
light: 'github-light',
|
||||||
|
dark: 'github-dark',
|
||||||
|
},
|
||||||
|
transformers: [
|
||||||
|
transformerNotationDiff(),
|
||||||
|
],
|
||||||
|
})) as string
|
||||||
|
})
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
if (containerRef) containerRef.innerHTML = ""
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (html() && containerRef) {
|
||||||
|
containerRef.innerHTML = html() as string
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={containerRef} {...rest}></div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CodeBlock
|
66
app/packages/web/src/components/DiffView.tsx
Normal file
66
app/packages/web/src/components/DiffView.tsx
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import { type Component, createSignal, onMount } from "solid-js"
|
||||||
|
import { diffLines, type Change } from "diff"
|
||||||
|
import CodeBlock from "./CodeBlock"
|
||||||
|
import styles from "./diffView.module.css"
|
||||||
|
|
||||||
|
type DiffRow = {
|
||||||
|
left: string
|
||||||
|
right: string
|
||||||
|
type: "added" | "removed" | "unchanged"
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DiffViewProps {
|
||||||
|
oldCode: string
|
||||||
|
newCode: string
|
||||||
|
lang?: string
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const DiffView: Component<DiffViewProps> = (props) => {
|
||||||
|
const [rows, setRows] = createSignal<DiffRow[]>([])
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
const chunks = diffLines(props.oldCode, props.newCode)
|
||||||
|
const diffRows: DiffRow[] = []
|
||||||
|
|
||||||
|
chunks.forEach((chunk: Change) => {
|
||||||
|
const lines = chunk.value.split(/\r?\n/)
|
||||||
|
if (lines.at(-1) === "") lines.pop()
|
||||||
|
|
||||||
|
lines.forEach((line) => {
|
||||||
|
diffRows.push({
|
||||||
|
left: chunk.removed ? line : chunk.added ? "" : line,
|
||||||
|
right: chunk.added ? line : chunk.removed ? "" : line,
|
||||||
|
type: chunk.added ? "added"
|
||||||
|
: chunk.removed ? "removed"
|
||||||
|
: "unchanged",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
setRows(diffRows)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class={`${styles.diff} ${props.class ?? ""}`}>
|
||||||
|
{rows().map((r) => (
|
||||||
|
<div data-section="row">
|
||||||
|
<CodeBlock
|
||||||
|
code={r.left}
|
||||||
|
lang={props.lang}
|
||||||
|
data-section="cell"
|
||||||
|
data-diff-type={r.type === "removed" ? "removed" : ""}
|
||||||
|
/>
|
||||||
|
<CodeBlock
|
||||||
|
code={r.right}
|
||||||
|
lang={props.lang}
|
||||||
|
data-section="cell"
|
||||||
|
data-diff-type={r.type === "added" ? "added" : ""}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DiffView
|
|
@ -6,6 +6,7 @@ import {
|
||||||
Switch,
|
Switch,
|
||||||
onMount,
|
onMount,
|
||||||
onCleanup,
|
onCleanup,
|
||||||
|
splitProps,
|
||||||
createMemo,
|
createMemo,
|
||||||
createEffect,
|
createEffect,
|
||||||
createSignal,
|
createSignal,
|
||||||
|
@ -20,8 +21,13 @@ import {
|
||||||
IconCpuChip,
|
IconCpuChip,
|
||||||
IconSparkles,
|
IconSparkles,
|
||||||
IconUserCircle,
|
IconUserCircle,
|
||||||
|
IconChevronDown,
|
||||||
|
IconChevronRight,
|
||||||
|
IconPencilSquare,
|
||||||
IconWrenchScrewdriver,
|
IconWrenchScrewdriver,
|
||||||
} from "./icons"
|
} from "./icons"
|
||||||
|
import CodeBlock from "./CodeBlock"
|
||||||
|
import DiffView from "./DiffView"
|
||||||
import styles from "./share.module.css"
|
import styles from "./share.module.css"
|
||||||
import { type UIMessage } from "ai"
|
import { type UIMessage } from "ai"
|
||||||
import { createStore, reconcile } from "solid-js/store"
|
import { createStore, reconcile } from "solid-js/store"
|
||||||
|
@ -59,6 +65,10 @@ type SessionInfo = {
|
||||||
cost?: number
|
cost?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFileType(path: string) {
|
||||||
|
return path.split('.').pop()
|
||||||
|
}
|
||||||
|
|
||||||
// Converts `{a:{b:{c:1}}` to `[['a.b.c', 1]]`
|
// Converts `{a:{b:{c:1}}` to `[['a.b.c', 1]]`
|
||||||
function flattenToolArgs(obj: any, prefix: string = ""): Array<[string, any]> {
|
function flattenToolArgs(obj: any, prefix: string = ""): Array<[string, any]> {
|
||||||
const entries: Array<[string, any]> = [];
|
const entries: Array<[string, any]> = [];
|
||||||
|
@ -111,18 +121,48 @@ function ProviderIcon(props: { provider: string, size?: number }) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ResultsButtonProps extends JSX.HTMLAttributes<HTMLButtonElement> {
|
||||||
|
results: boolean
|
||||||
|
}
|
||||||
|
function ResultsButton(props: ResultsButtonProps) {
|
||||||
|
const [local, rest] = splitProps(props, ["results"])
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-element-button-text
|
||||||
|
data-element-button-more
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{local.results ? "Hide results" : "Show results"}
|
||||||
|
</span>
|
||||||
|
<span data-button-icon>
|
||||||
|
<Show
|
||||||
|
when={local.results}
|
||||||
|
fallback={
|
||||||
|
<IconChevronRight width={10} height={10} />
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconChevronDown width={10} height={10} />
|
||||||
|
</Show>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
interface TextPartProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
interface TextPartProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
||||||
text: string
|
text: string
|
||||||
expand?: boolean
|
expand?: boolean
|
||||||
highlight?: boolean
|
highlight?: boolean
|
||||||
}
|
}
|
||||||
function TextPart({ text, expand, highlight, ...props }: TextPartProps) {
|
function TextPart(props: TextPartProps) {
|
||||||
|
const [local, rest] = splitProps(props, ["text", "expand", "highlight"])
|
||||||
const [expanded, setExpanded] = createSignal(false)
|
const [expanded, setExpanded] = createSignal(false)
|
||||||
const [overflowed, setOverflowed] = createSignal(false)
|
const [overflowed, setOverflowed] = createSignal(false)
|
||||||
let preEl: HTMLPreElement | undefined
|
let preEl: HTMLPreElement | undefined
|
||||||
|
|
||||||
function checkOverflow() {
|
function checkOverflow() {
|
||||||
if (preEl && !expand) {
|
if (preEl && !local.expand) {
|
||||||
setOverflowed(preEl.scrollHeight > preEl.clientHeight + 1)
|
setOverflowed(preEl.scrollHeight > preEl.clientHeight + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,7 +173,7 @@ function TextPart({ text, expand, highlight, ...props }: TextPartProps) {
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
text
|
local.text
|
||||||
setTimeout(checkOverflow, 0)
|
setTimeout(checkOverflow, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -144,11 +184,11 @@ function TextPart({ text, expand, highlight, ...props }: TextPartProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-element-message-text
|
data-element-message-text
|
||||||
data-highlight={highlight}
|
data-highlight={local.highlight}
|
||||||
data-expanded={expanded() || expand === true}
|
data-expanded={expanded() || local.expand === true}
|
||||||
{...props}
|
{...rest}
|
||||||
>
|
>
|
||||||
<pre ref={el => (preEl = el)}>{text}</pre>
|
<pre ref={el => (preEl = el)}>{local.text}</pre>
|
||||||
{overflowed() &&
|
{overflowed() &&
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -411,6 +451,7 @@ export default function Share(props: { api: string }) {
|
||||||
{(part, partIndex) => {
|
{(part, partIndex) => {
|
||||||
if (part.type === "step-start" && (partIndex() > 0 || !msg.metadata?.assistant)) return null
|
if (part.type === "step-start" && (partIndex() > 0 || !msg.metadata?.assistant)) return null
|
||||||
|
|
||||||
|
const [results, showResults] = createSignal(false)
|
||||||
const isLastPart = createMemo(() =>
|
const isLastPart = createMemo(() =>
|
||||||
(messages().length === msgIndex() + 1)
|
(messages().length === msgIndex() + 1)
|
||||||
&& (msg.parts.length === partIndex() + 1)
|
&& (msg.parts.length === partIndex() + 1)
|
||||||
|
@ -488,6 +529,7 @@ export default function Share(props: { api: string }) {
|
||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
<div data-section="content">
|
<div data-section="content">
|
||||||
|
<div data-part-tool-body>
|
||||||
<span
|
<span
|
||||||
data-size="md"
|
data-size="md"
|
||||||
data-part-title
|
data-part-title
|
||||||
|
@ -499,6 +541,7 @@ export default function Share(props: { api: string }) {
|
||||||
{assistant().modelID}
|
{assistant().modelID}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</Match>
|
</Match>
|
||||||
|
@ -517,6 +560,7 @@ export default function Share(props: { api: string }) {
|
||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
<div data-section="content">
|
<div data-section="content">
|
||||||
|
<div data-part-tool-body>
|
||||||
<span data-element-label data-part-title>
|
<span data-element-label data-part-title>
|
||||||
System
|
System
|
||||||
</span>
|
</span>
|
||||||
|
@ -525,11 +569,50 @@ export default function Share(props: { api: string }) {
|
||||||
text={part().text}
|
text={part().text}
|
||||||
data-color="dimmed"
|
data-color="dimmed"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<PartFooter time={time} />
|
<PartFooter time={time} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</Match>
|
</Match>
|
||||||
|
{ /* Edit tool */}
|
||||||
|
<Match when={
|
||||||
|
msg.role === "assistant"
|
||||||
|
&& part.type === "tool-invocation"
|
||||||
|
&& part.toolInvocation.toolName === "edit"
|
||||||
|
&& part
|
||||||
|
}>
|
||||||
|
{part => {
|
||||||
|
const args = part().toolInvocation.args
|
||||||
|
const filePath = args.filePath
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-section="decoration">
|
||||||
|
<div>
|
||||||
|
<IconPencilSquare width={18} height={18} />
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
<div data-section="content">
|
||||||
|
<div data-part-tool-body>
|
||||||
|
<span data-part-title data-size="md">
|
||||||
|
Edit {filePath}
|
||||||
|
</span>
|
||||||
|
<div data-part-tool-edit>
|
||||||
|
<DiffView
|
||||||
|
class={styles["code-block"]}
|
||||||
|
oldCode={args.oldString}
|
||||||
|
newCode={args.newString}
|
||||||
|
lang={getFileType(filePath)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<PartFooter time={time} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</Match>
|
||||||
{ /* Tool call */}
|
{ /* Tool call */}
|
||||||
<Match when={
|
<Match when={
|
||||||
msg.role === "assistant"
|
msg.role === "assistant"
|
||||||
|
@ -545,6 +628,7 @@ export default function Share(props: { api: string }) {
|
||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
<div data-section="content">
|
<div data-section="content">
|
||||||
|
<div data-part-tool-body>
|
||||||
<span data-part-title data-size="md">
|
<span data-part-title data-size="md">
|
||||||
{part().toolInvocation.toolName}
|
{part().toolInvocation.toolName}
|
||||||
</span>
|
</span>
|
||||||
|
@ -566,12 +650,20 @@ export default function Share(props: { api: string }) {
|
||||||
part().toolInvocation.state === "result"
|
part().toolInvocation.state === "result"
|
||||||
&& part().toolInvocation.result
|
&& part().toolInvocation.result
|
||||||
}>
|
}>
|
||||||
|
<div data-part-tool-result>
|
||||||
|
<ResultsButton
|
||||||
|
results={results()}
|
||||||
|
onClick={() => showResults(e => !e)}
|
||||||
|
/>
|
||||||
|
<Show when={results()}>
|
||||||
<TextPart
|
<TextPart
|
||||||
|
expand
|
||||||
data-size="sm"
|
data-size="sm"
|
||||||
data-color="dimmed"
|
data-color="dimmed"
|
||||||
text={part().toolInvocation.result}
|
text={part().toolInvocation.result}
|
||||||
expand={isLastPart()}
|
|
||||||
/>
|
/>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={
|
<Match when={
|
||||||
part().toolInvocation.state === "call"
|
part().toolInvocation.state === "call"
|
||||||
|
@ -583,6 +675,7 @@ export default function Share(props: { api: string }) {
|
||||||
/>
|
/>
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
</div>
|
||||||
<PartFooter time={time} />
|
<PartFooter time={time} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -609,10 +702,12 @@ export default function Share(props: { api: string }) {
|
||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
<div data-section="content">
|
<div data-section="content">
|
||||||
|
<div data-part-tool-body>
|
||||||
<span data-element-label data-part-title>
|
<span data-element-label data-part-title>
|
||||||
{part.type}
|
{part.type}
|
||||||
</span>
|
</span>
|
||||||
<TextPart text={JSON.stringify(part, null, 2)} />
|
<TextPart text={JSON.stringify(part, null, 2)} />
|
||||||
|
</div>
|
||||||
<PartFooter time={time} />
|
<PartFooter time={time} />
|
||||||
</div>
|
</div>
|
||||||
</Match>
|
</Match>
|
||||||
|
|
70
app/packages/web/src/components/diffview.module.css
Normal file
70
app/packages/web/src/components/diffview.module.css
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
.diff {
|
||||||
|
display: grid;
|
||||||
|
row-gap: 0;
|
||||||
|
border: 1px solid var(--sl-color-divider);
|
||||||
|
background-color: var(--sl-color-bg-surface);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
|
||||||
|
[data-section="row"] {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
|
||||||
|
&:first-child [data-section="cell"] {
|
||||||
|
padding-top: 0.375rem;
|
||||||
|
}
|
||||||
|
&:last-child [data-section="cell"] {
|
||||||
|
padding-bottom: 0.375rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-section="cell"] {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 1.5ch;
|
||||||
|
padding: 0.25rem 0.5rem 0.25rem 1.5ch;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background-color: var(--sl-color-bg-surface) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-right: 1px solid var(--sl-color-divider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-diff-type="removed"] {
|
||||||
|
background-color: var(--sl-color-red-low);
|
||||||
|
|
||||||
|
& > pre {
|
||||||
|
--shiki-dark-bg: var(--sl-color-red-low) !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "-";
|
||||||
|
position: absolute;
|
||||||
|
left: 0.5ch;
|
||||||
|
user-select: none;
|
||||||
|
color: var(--sl-color-red-high);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-diff-type="added"] {
|
||||||
|
background-color: var(--sl-color-green-low);
|
||||||
|
|
||||||
|
& > pre {
|
||||||
|
--shiki-dark-bg: var(--sl-color-green-low) !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "+";
|
||||||
|
position: absolute;
|
||||||
|
left: 0.6ch;
|
||||||
|
user-select: none;
|
||||||
|
color: var(--sl-color-green-high);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,33 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-element-button-text] {
|
||||||
|
cursor: pointer;
|
||||||
|
appearance: none;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
color: var(--sl-color-text-secondary);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--sl-color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-element-button-more] {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.125rem;
|
||||||
|
|
||||||
|
span[data-button-icon] {
|
||||||
|
line-height: 1;
|
||||||
|
opacity: 0.85;
|
||||||
|
svg {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[data-element-label] {
|
[data-element-label] {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
|
@ -154,7 +181,13 @@
|
||||||
padding: 0 0 0.375rem;
|
padding: 0 0 0.375rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.5rem;
|
gap: 1rem;
|
||||||
|
|
||||||
|
[data-part-tool-body] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
span[data-part-title] {
|
span[data-part-title] {
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
|
@ -203,7 +236,17 @@
|
||||||
padding-left: 0.125rem;
|
padding-left: 0.125rem;
|
||||||
color: var(--sl-color-text-dimmed);
|
color: var(--sl-color-text-dimmed);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-part-tool-result] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -274,3 +317,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.code-block {
|
||||||
|
pre {
|
||||||
|
line-height: 1.4;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,3 +2,15 @@
|
||||||
--sl-color-bg-surface: var(--sl-color-bg-nav);
|
--sl-color-bg-surface: var(--sl-color-bg-nav);
|
||||||
--sl-color-divider: var(--sl-color-gray-5);
|
--sl-color-divider: var(--sl-color-gray-5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.shiki,
|
||||||
|
.shiki span {
|
||||||
|
color: var(--shiki-dark) !important;
|
||||||
|
background-color: var(--shiki-dark-bg) !important;
|
||||||
|
/* Optional, if you also want font styles */
|
||||||
|
font-style: var(--shiki-dark-font-style) !important;
|
||||||
|
font-weight: var(--shiki-dark-font-weight) !important;
|
||||||
|
text-decoration: var(--shiki-dark-text-decoration) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue