mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-17 12:56:41 +02:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cdb25656d5 | |||
| 25c876caa2 | |||
| cf83e31f23 | |||
| 3bc238b58b | |||
| b8de69dced | |||
| e7fcb692a4 | |||
| dae38574ab |
@@ -5,6 +5,7 @@
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.2.14",
|
||||
"scripts": {
|
||||
"dev": "bun run packages/opencode/src/index.ts",
|
||||
"typecheck": "bun run --filter='*' typecheck",
|
||||
"stainless": "bun run ./packages/opencode/src/index.ts serve ",
|
||||
"postinstall": "./scripts/hooks"
|
||||
|
||||
@@ -9,6 +9,7 @@ import fs from "fs/promises"
|
||||
import { Installation } from "../../installation"
|
||||
import { Config } from "../../config/config"
|
||||
import { Bus } from "../../bus"
|
||||
import { Log } from "../../util/log"
|
||||
|
||||
export const TuiCommand = cmd({
|
||||
command: "$0 [project]",
|
||||
@@ -57,6 +58,9 @@ export const TuiCommand = cmd({
|
||||
cwd = process.cwd()
|
||||
cmd = [binary]
|
||||
}
|
||||
Log.Default.info("tui", {
|
||||
cmd,
|
||||
})
|
||||
const proc = Bun.spawn({
|
||||
cmd: [...cmd, ...process.argv.slice(2)],
|
||||
cwd,
|
||||
|
||||
@@ -92,11 +92,20 @@ export namespace LSPClient {
|
||||
},
|
||||
}),
|
||||
5_000,
|
||||
).catch(() => {
|
||||
throw new InitializeError({ serverID })
|
||||
).catch((err) => {
|
||||
log.error("initialize error", { error: err })
|
||||
throw new InitializeError(
|
||||
{ serverID },
|
||||
{
|
||||
cause: err,
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
await connection.sendNotification("initialized", {})
|
||||
log.info("initialized")
|
||||
log.info("initialized", {
|
||||
serverID,
|
||||
})
|
||||
|
||||
const files: {
|
||||
[path: string]: number
|
||||
@@ -174,7 +183,6 @@ export namespace LSPClient {
|
||||
log.info("shutting down", { serverID })
|
||||
connection.end()
|
||||
connection.dispose()
|
||||
server.process.kill("SIGTERM")
|
||||
log.info("shutdown", { serverID })
|
||||
},
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ export namespace LSP {
|
||||
const handle = await server.spawn(App.info())
|
||||
if (!handle) break
|
||||
const client = await LSPClient.create(server.id, handle).catch(
|
||||
() => {},
|
||||
(err) => log.error("", { error: err }),
|
||||
)
|
||||
if (!client) break
|
||||
clients.set(server.id, client)
|
||||
|
||||
@@ -4,6 +4,8 @@ import path from "path"
|
||||
import { Global } from "../global"
|
||||
import { Log } from "../util/log"
|
||||
import { BunProc } from "../bun"
|
||||
import { $ } from "bun"
|
||||
import fs from "fs/promises"
|
||||
|
||||
export namespace LSPServer {
|
||||
const log = Log.create({ service: "lsp.server" })
|
||||
@@ -144,4 +146,60 @@ export namespace LSPServer {
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export const ElixirLS: Info = {
|
||||
id: "elixir-ls",
|
||||
extensions: [".ex", ".exs"],
|
||||
async spawn() {
|
||||
let binary = Bun.which("elixir-ls")
|
||||
if (!binary) {
|
||||
const elixirLsPath = path.join(Global.Path.bin, "elixir-ls")
|
||||
binary = path.join(
|
||||
Global.Path.bin,
|
||||
"elixir-ls-master",
|
||||
"release",
|
||||
process.platform === "win32"
|
||||
? "language_server.bar"
|
||||
: "language_server.sh",
|
||||
)
|
||||
|
||||
if (!(await Bun.file(binary).exists())) {
|
||||
const elixir = Bun.which("elixir")
|
||||
if (!elixir) {
|
||||
log.error("elixir is required to run elixir-ls")
|
||||
return
|
||||
}
|
||||
|
||||
log.info("downloading elixir-ls from GitHub releases")
|
||||
|
||||
const response = await fetch(
|
||||
"https://github.com/elixir-lsp/elixir-ls/archive/refs/heads/master.zip",
|
||||
)
|
||||
if (!response.ok) return
|
||||
const zipPath = path.join(Global.Path.bin, "elixir-ls.zip")
|
||||
await Bun.file(zipPath).write(response)
|
||||
|
||||
await $`unzip -o -q ${zipPath}`.cwd(Global.Path.bin).nothrow()
|
||||
|
||||
await fs.rm(zipPath, {
|
||||
force: true,
|
||||
recursive: true,
|
||||
})
|
||||
|
||||
await $`mix deps.get && mix compile && mix elixir_ls.release2 -o release`
|
||||
.quiet()
|
||||
.cwd(path.join(Global.Path.bin, "elixir-ls-master"))
|
||||
.env({ MIX_ENV: "prod", ...process.env })
|
||||
|
||||
log.info(`installed elixir-ls`, {
|
||||
path: elixirLsPath,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
process: spawn(binary),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,14 @@ export namespace Snapshot {
|
||||
const log = Log.create({ service: "snapshot" })
|
||||
|
||||
export async function create(sessionID: string) {
|
||||
log.info("creating snapshot")
|
||||
const app = App.info()
|
||||
const git = gitdir(sessionID)
|
||||
const files = await Ripgrep.files({
|
||||
cwd: app.path.cwd,
|
||||
limit: app.git ? undefined : 1000,
|
||||
})
|
||||
log.info("found files", { count: files.length })
|
||||
// not a git repo and too big to snapshot
|
||||
if (!app.git && files.length === 1000) return
|
||||
await init({
|
||||
@@ -29,19 +31,17 @@ export namespace Snapshot {
|
||||
gitdir: git,
|
||||
fs,
|
||||
})
|
||||
log.info("initialized")
|
||||
const status = await statusMatrix({
|
||||
fs,
|
||||
gitdir: git,
|
||||
dir: app.path.cwd,
|
||||
})
|
||||
await add({
|
||||
fs,
|
||||
gitdir: git,
|
||||
parallel: true,
|
||||
dir: app.path.cwd,
|
||||
filepath: files,
|
||||
log.info("matrix", {
|
||||
count: status.length,
|
||||
})
|
||||
for (const [file, _head, workdir, stage] of status) {
|
||||
const added = []
|
||||
for (const [file, head, workdir, stage] of status) {
|
||||
if (workdir === 0 && stage === 1) {
|
||||
log.info("remove", { file })
|
||||
await remove({
|
||||
@@ -50,8 +50,21 @@ export namespace Snapshot {
|
||||
dir: app.path.cwd,
|
||||
filepath: file,
|
||||
})
|
||||
continue
|
||||
}
|
||||
if (workdir !== head) {
|
||||
added.push(file)
|
||||
}
|
||||
}
|
||||
log.info("removed files")
|
||||
await add({
|
||||
fs,
|
||||
gitdir: git,
|
||||
parallel: true,
|
||||
dir: app.path.cwd,
|
||||
filepath: added,
|
||||
})
|
||||
log.info("added files")
|
||||
const result = await commit({
|
||||
fs,
|
||||
gitdir: git,
|
||||
|
||||
@@ -489,10 +489,10 @@ export function replace(
|
||||
BlockAnchorReplacer,
|
||||
WhitespaceNormalizedReplacer,
|
||||
IndentationFlexibleReplacer,
|
||||
EscapeNormalizedReplacer,
|
||||
TrimmedBoundaryReplacer,
|
||||
ContextAwareReplacer,
|
||||
MultiOccurrenceReplacer,
|
||||
// EscapeNormalizedReplacer,
|
||||
// TrimmedBoundaryReplacer,
|
||||
// ContextAwareReplacer,
|
||||
// MultiOccurrenceReplacer,
|
||||
]) {
|
||||
for (const search of replacer(content, oldString)) {
|
||||
const index = content.indexOf(search)
|
||||
|
||||
@@ -36,6 +36,10 @@ if (isDocs) {
|
||||
}
|
||||
---
|
||||
|
||||
{ slug === "" && (
|
||||
<title>{title} | AI coding agent built for the terminal</title>
|
||||
)}
|
||||
|
||||
<Default {...Astro.props}><slot /></Default>
|
||||
|
||||
{ (isDocs || !slug.startsWith("s")) && (
|
||||
|
||||
@@ -321,6 +321,7 @@ function TextPart(props: TextPartProps) {
|
||||
|
||||
createEffect(() => {
|
||||
local.text
|
||||
local.expand
|
||||
setTimeout(checkOverflow, 0)
|
||||
})
|
||||
|
||||
@@ -372,6 +373,7 @@ function ErrorPart(props: ErrorPartProps) {
|
||||
|
||||
createEffect(() => {
|
||||
local.children
|
||||
local.expand
|
||||
setTimeout(checkOverflow, 0)
|
||||
})
|
||||
|
||||
@@ -425,6 +427,7 @@ function MarkdownPart(props: MarkdownPartProps) {
|
||||
|
||||
createEffect(() => {
|
||||
local.text
|
||||
local.expand
|
||||
setTimeout(checkOverflow, 0)
|
||||
})
|
||||
|
||||
@@ -486,6 +489,14 @@ function TerminalPart(props: TerminalPartProps) {
|
||||
}
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
local.command
|
||||
local.result
|
||||
local.error
|
||||
local.expand
|
||||
setTimeout(checkOverflow, 0)
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
checkOverflow()
|
||||
window.addEventListener("resize", checkOverflow)
|
||||
@@ -597,6 +608,8 @@ export default function Share(props: {
|
||||
}) {
|
||||
let lastScrollY = 0
|
||||
let scrollTimeout: number | undefined
|
||||
let scrollSentinel: HTMLElement | undefined
|
||||
let scrollObserver: IntersectionObserver | undefined
|
||||
|
||||
const id = props.id
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
@@ -604,6 +617,7 @@ export default function Share(props: {
|
||||
|
||||
const [showScrollButton, setShowScrollButton] = createSignal(false)
|
||||
const [isButtonHovered, setIsButtonHovered] = createSignal(false)
|
||||
const [isNearBottom, setIsNearBottom] = createSignal(false)
|
||||
|
||||
const [store, setStore] = createStore<{
|
||||
info?: Session.Info
|
||||
@@ -713,10 +727,9 @@ export default function Share(props: {
|
||||
const currentScrollY = window.scrollY
|
||||
const isScrollingDown = currentScrollY > lastScrollY
|
||||
const scrolled = currentScrollY > 200 // Show after scrolling 200px
|
||||
const isNearBottom = window.innerHeight + currentScrollY >= document.body.scrollHeight - 100
|
||||
|
||||
// Only show when scrolling down, scrolled enough, and not near bottom
|
||||
const shouldShow = isScrollingDown && scrolled && !isNearBottom
|
||||
const shouldShow = isScrollingDown && scrolled && !isNearBottom()
|
||||
|
||||
// Update last scroll position
|
||||
lastScrollY = currentScrollY
|
||||
@@ -732,7 +745,7 @@ export default function Share(props: {
|
||||
if (!isButtonHovered()) {
|
||||
setShowScrollButton(false)
|
||||
}
|
||||
}, 3000)
|
||||
}, 1500)
|
||||
} else if (!isButtonHovered()) {
|
||||
// Only hide if not hovered (to prevent disappearing while user is about to click)
|
||||
setShowScrollButton(false)
|
||||
@@ -744,6 +757,26 @@ export default function Share(props: {
|
||||
|
||||
onMount(() => {
|
||||
lastScrollY = window.scrollY // Initialize scroll position
|
||||
|
||||
// Create sentinel element
|
||||
const sentinel = document.createElement("div")
|
||||
sentinel.style.height = "1px"
|
||||
sentinel.style.position = "absolute"
|
||||
sentinel.style.bottom = "100px"
|
||||
sentinel.style.width = "100%"
|
||||
sentinel.style.pointerEvents = "none"
|
||||
document.body.appendChild(sentinel)
|
||||
|
||||
// Create intersection observer
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
setIsNearBottom(entries[0].isIntersecting)
|
||||
})
|
||||
observer.observe(sentinel)
|
||||
|
||||
// Store references for cleanup
|
||||
scrollSentinel = sentinel
|
||||
scrollObserver = observer
|
||||
|
||||
checkScrollNeed()
|
||||
window.addEventListener("scroll", checkScrollNeed)
|
||||
window.addEventListener("resize", checkScrollNeed)
|
||||
@@ -752,6 +785,15 @@ export default function Share(props: {
|
||||
onCleanup(() => {
|
||||
window.removeEventListener("scroll", checkScrollNeed)
|
||||
window.removeEventListener("resize", checkScrollNeed)
|
||||
|
||||
// Clean up observer and sentinel
|
||||
if (scrollObserver) {
|
||||
scrollObserver.disconnect()
|
||||
}
|
||||
if (scrollSentinel) {
|
||||
document.body.removeChild(scrollSentinel)
|
||||
}
|
||||
|
||||
if (scrollTimeout) {
|
||||
clearTimeout(scrollTimeout)
|
||||
}
|
||||
@@ -855,7 +897,6 @@ export default function Share(props: {
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -865,7 +906,7 @@ export default function Share(props: {
|
||||
fallback={<p>Waiting for messages...</p>}
|
||||
>
|
||||
<div class={styles.parts}>
|
||||
<SuspenseList>
|
||||
<SuspenseList revealOrder="forwards">
|
||||
<For each={data().messages}>
|
||||
{(msg, msgIndex) => (
|
||||
<Suspense>
|
||||
@@ -880,8 +921,11 @@ export default function Share(props: {
|
||||
)
|
||||
return null
|
||||
|
||||
const anchor = createMemo(() => `${msg.id}-${partIndex()}`)
|
||||
const [showResults, setShowResults] = createSignal(false)
|
||||
const anchor = createMemo(
|
||||
() => `${msg.id}-${partIndex()}`,
|
||||
)
|
||||
const [showResults, setShowResults] =
|
||||
createSignal(false)
|
||||
const isLastPart = createMemo(
|
||||
() =>
|
||||
data().messages.length === msgIndex() + 1 &&
|
||||
@@ -903,7 +947,9 @@ export default function Share(props: {
|
||||
const duration = DateTime.fromMillis(
|
||||
metadata?.time.end || 0,
|
||||
)
|
||||
.diff(DateTime.fromMillis(metadata?.time.start || 0))
|
||||
.diff(
|
||||
DateTime.fromMillis(metadata?.time.start || 0),
|
||||
)
|
||||
.toMillis()
|
||||
|
||||
return { metadata, args, result, duration }
|
||||
@@ -921,7 +967,9 @@ export default function Share(props: {
|
||||
{/* User text */}
|
||||
<Match
|
||||
when={
|
||||
msg.role === "user" && part.type === "text" && part
|
||||
msg.role === "user" &&
|
||||
part.type === "text" &&
|
||||
part
|
||||
}
|
||||
>
|
||||
{(part) => (
|
||||
@@ -972,7 +1020,9 @@ export default function Share(props: {
|
||||
expand={isLastPart()}
|
||||
text={stripEnclosingTag(part().text)}
|
||||
/>
|
||||
<Show when={isLastPart() && data().completed}>
|
||||
<Show
|
||||
when={isLastPart() && data().completed}
|
||||
>
|
||||
<span
|
||||
data-part-footer
|
||||
title={DateTime.fromMillis(
|
||||
@@ -1041,7 +1091,8 @@ export default function Share(props: {
|
||||
}
|
||||
>
|
||||
{(_part) => {
|
||||
const matches = () => toolData()?.metadata?.matches
|
||||
const matches = () =>
|
||||
toolData()?.metadata?.matches
|
||||
const splitArgs = () => {
|
||||
const { pattern, ...rest } = toolData()?.args
|
||||
return { pattern, rest }
|
||||
@@ -1066,11 +1117,14 @@ export default function Share(props: {
|
||||
<div data-part-tool-body>
|
||||
<div data-part-title>
|
||||
<span data-element-label>Grep</span>
|
||||
<b>“{splitArgs().pattern}”</b>
|
||||
<b>
|
||||
“{splitArgs().pattern}”
|
||||
</b>
|
||||
</div>
|
||||
<Show
|
||||
when={
|
||||
Object.keys(splitArgs().rest).length > 0
|
||||
Object.keys(splitArgs().rest)
|
||||
.length > 0
|
||||
}
|
||||
>
|
||||
<div data-part-tool-args>
|
||||
@@ -1299,8 +1353,10 @@ export default function Share(props: {
|
||||
data().rootDir,
|
||||
),
|
||||
)
|
||||
const hasError = () => toolData()?.metadata?.error
|
||||
const preview = () => toolData()?.metadata?.preview
|
||||
const hasError = () =>
|
||||
toolData()?.metadata?.error
|
||||
const preview = () =>
|
||||
toolData()?.metadata?.preview
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -1333,7 +1389,9 @@ export default function Share(props: {
|
||||
</div>
|
||||
</Match>
|
||||
{/* Always try to show CodeBlock if preview is available (even if empty string) */}
|
||||
<Match when={typeof preview() === 'string'}>
|
||||
<Match
|
||||
when={typeof preview() === "string"}
|
||||
>
|
||||
<div data-part-tool-result>
|
||||
<ResultsButton
|
||||
showCopy="Show preview"
|
||||
@@ -1346,7 +1404,9 @@ export default function Share(props: {
|
||||
<Show when={showResults()}>
|
||||
<div data-part-tool-code>
|
||||
<CodeBlock
|
||||
lang={getShikiLang(filePath())}
|
||||
lang={getShikiLang(
|
||||
filePath(),
|
||||
)}
|
||||
code={preview()}
|
||||
/>
|
||||
</div>
|
||||
@@ -1354,7 +1414,12 @@ export default function Share(props: {
|
||||
</div>
|
||||
</Match>
|
||||
{/* Fallback to TextPart if preview is not a string (e.g. undefined) AND result exists */}
|
||||
<Match when={typeof preview() !== 'string' && toolData()?.result}>
|
||||
<Match
|
||||
when={
|
||||
typeof preview() !== "string" &&
|
||||
toolData()?.result
|
||||
}
|
||||
>
|
||||
<div data-part-tool-result>
|
||||
<ResultsButton
|
||||
results={showResults()}
|
||||
@@ -1398,7 +1463,8 @@ export default function Share(props: {
|
||||
data().rootDir,
|
||||
),
|
||||
)
|
||||
const hasError = () => toolData()?.metadata?.error
|
||||
const hasError = () =>
|
||||
toolData()?.metadata?.error
|
||||
const content = () => toolData()?.args?.content
|
||||
const diagnostics = createMemo(() =>
|
||||
getDiagnostics(
|
||||
@@ -1415,7 +1481,10 @@ export default function Share(props: {
|
||||
>
|
||||
<div data-section="decoration">
|
||||
<AnchorIcon id={anchor()}>
|
||||
<IconDocumentPlus width={18} height={18} />
|
||||
<IconDocumentPlus
|
||||
width={18}
|
||||
height={18}
|
||||
/>
|
||||
</AnchorIcon>
|
||||
<div></div>
|
||||
</div>
|
||||
@@ -1435,7 +1504,7 @@ export default function Share(props: {
|
||||
<div data-part-tool-result>
|
||||
<ErrorPart>
|
||||
{formatErrorString(
|
||||
toolData()?.result
|
||||
toolData()?.result,
|
||||
)}
|
||||
</ErrorPart>
|
||||
</div>
|
||||
@@ -1453,8 +1522,12 @@ export default function Share(props: {
|
||||
<Show when={showResults()}>
|
||||
<div data-part-tool-code>
|
||||
<CodeBlock
|
||||
lang={getShikiLang(filePath())}
|
||||
code={toolData()?.args?.content}
|
||||
lang={getShikiLang(
|
||||
filePath(),
|
||||
)}
|
||||
code={
|
||||
toolData()?.args?.content
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
@@ -1481,8 +1554,10 @@ export default function Share(props: {
|
||||
>
|
||||
{(_part) => {
|
||||
const diff = () => toolData()?.metadata?.diff
|
||||
const message = () => toolData()?.metadata?.message
|
||||
const hasError = () => toolData()?.metadata?.error
|
||||
const message = () =>
|
||||
toolData()?.metadata?.message
|
||||
const hasError = () =>
|
||||
toolData()?.metadata?.error
|
||||
const filePath = createMemo(() =>
|
||||
stripWorkingDirectory(
|
||||
toolData()?.args.filePath,
|
||||
@@ -1504,7 +1579,10 @@ export default function Share(props: {
|
||||
>
|
||||
<div data-section="decoration">
|
||||
<AnchorIcon id={anchor()}>
|
||||
<IconPencilSquare width={18} height={18} />
|
||||
<IconPencilSquare
|
||||
width={18}
|
||||
height={18}
|
||||
/>
|
||||
</AnchorIcon>
|
||||
<div></div>
|
||||
</div>
|
||||
@@ -1527,7 +1605,9 @@ export default function Share(props: {
|
||||
<Match when={diff()}>
|
||||
<div data-part-tool-edit>
|
||||
<DiffView
|
||||
class={styles["diff-code-block"]}
|
||||
class={
|
||||
styles["diff-code-block"]
|
||||
}
|
||||
diff={diff()}
|
||||
lang={getShikiLang(filePath())}
|
||||
/>
|
||||
@@ -1556,9 +1636,12 @@ export default function Share(props: {
|
||||
}
|
||||
>
|
||||
{(_part) => {
|
||||
const command = () => toolData()?.metadata?.title
|
||||
const desc = () => toolData()?.metadata?.description
|
||||
const result = () => toolData()?.metadata?.stdout
|
||||
const command = () =>
|
||||
toolData()?.metadata?.title
|
||||
const desc = () =>
|
||||
toolData()?.metadata?.description
|
||||
const result = () =>
|
||||
toolData()?.metadata?.stdout
|
||||
const error = () => toolData()?.metadata?.stderr
|
||||
|
||||
return (
|
||||
@@ -1569,7 +1652,10 @@ export default function Share(props: {
|
||||
>
|
||||
<div data-section="decoration">
|
||||
<AnchorIcon id={anchor()}>
|
||||
<IconCommandLine width={18} height={18} />
|
||||
<IconCommandLine
|
||||
width={18}
|
||||
height={18}
|
||||
/>
|
||||
</AnchorIcon>
|
||||
<div></div>
|
||||
</div>
|
||||
@@ -1604,7 +1690,9 @@ export default function Share(props: {
|
||||
>
|
||||
{(_part) => {
|
||||
const todos = createMemo(() =>
|
||||
sortTodosByStatus(toolData()?.args?.todos ?? []),
|
||||
sortTodosByStatus(
|
||||
toolData()?.args?.todos ?? [],
|
||||
),
|
||||
)
|
||||
const starting = () =>
|
||||
todos().every((t) => t.status === "pending")
|
||||
@@ -1670,7 +1758,8 @@ export default function Share(props: {
|
||||
{(_part) => {
|
||||
const url = () => toolData()?.args.url
|
||||
const format = () => toolData()?.args.format
|
||||
const hasError = () => toolData()?.metadata?.error
|
||||
const hasError = () =>
|
||||
toolData()?.metadata?.error
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -1793,7 +1882,8 @@ export default function Share(props: {
|
||||
</Match>
|
||||
<Match
|
||||
when={
|
||||
part().toolInvocation.state === "call"
|
||||
part().toolInvocation.state ===
|
||||
"call"
|
||||
}
|
||||
>
|
||||
<TextPart
|
||||
@@ -1839,7 +1929,10 @@ export default function Share(props: {
|
||||
</Match>
|
||||
|
||||
<Match when={msg.role === "user"}>
|
||||
<IconUserCircle width={18} height={18} />
|
||||
<IconUserCircle
|
||||
width={18}
|
||||
height={18}
|
||||
/>
|
||||
</Match>
|
||||
</Switch>
|
||||
</AnchorIcon>
|
||||
@@ -1848,7 +1941,9 @@ export default function Share(props: {
|
||||
<div data-section="content">
|
||||
<div data-part-tool-body>
|
||||
<div data-part-title>
|
||||
<span data-element-label>{part.type}</span>
|
||||
<span data-element-label>
|
||||
{part.type}
|
||||
</span>
|
||||
</div>
|
||||
<TextPart
|
||||
text={JSON.stringify(part, null, 2)}
|
||||
|
||||
Reference in New Issue
Block a user