mirror of
https://github.com/sst/opencode.git
synced 2025-08-04 13:30:52 +00:00
Share: load server data on page load
This commit is contained in:
parent
879d02f86c
commit
1d782dc19a
3 changed files with 329 additions and 132 deletions
|
@ -69,6 +69,15 @@ export class SyncServer extends DurableObject<Env> {
|
||||||
return secret
|
return secret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async messages() {
|
||||||
|
const data = await this.ctx.storage.list()
|
||||||
|
const messages = []
|
||||||
|
for (const [key, content] of data.entries()) {
|
||||||
|
messages.push({ key, content })
|
||||||
|
}
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
|
||||||
private async getSecret() {
|
private async getSecret() {
|
||||||
return this.ctx.storage.get<string>("secret")
|
return this.ctx.storage.get<string>("secret")
|
||||||
}
|
}
|
||||||
|
@ -163,5 +172,17 @@ export default {
|
||||||
const stub = env.SYNC_SERVER.get(env.SYNC_SERVER.idFromName(id))
|
const stub = env.SYNC_SERVER.get(env.SYNC_SERVER.idFromName(id))
|
||||||
return stub.fetch(request)
|
return stub.fetch(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (request.method === "GET" && method === "share_messages") {
|
||||||
|
const id = url.searchParams.get("id")
|
||||||
|
console.log("share_messages", id)
|
||||||
|
if (!id)
|
||||||
|
return new Response("Error: Share ID is required", { status: 400 })
|
||||||
|
const stub = env.SYNC_SERVER.get(env.SYNC_SERVER.idFromName(id))
|
||||||
|
const messages = await stub.messages()
|
||||||
|
return new Response(JSON.stringify({ messages }), {
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,12 +139,10 @@ function flattenToolArgs(obj: any, prefix: string = ""): Array<[string, any]> {
|
||||||
entries.push([arrayPath, item])
|
entries.push([arrayPath, item])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
entries.push(...flattenToolArgs(value, path))
|
entries.push(...flattenToolArgs(value, path))
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
entries.push([path, value])
|
entries.push([path, value])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -360,7 +358,9 @@ function TerminalPart(props: TerminalPartProps) {
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
<div data-section="body">
|
<div data-section="body">
|
||||||
<div data-section="header"><span>{local.desc}</span></div>
|
<div data-section="header">
|
||||||
|
<span>{local.desc}</span>
|
||||||
|
</div>
|
||||||
<div data-section="content">
|
<div data-section="content">
|
||||||
<CodeBlock
|
<CodeBlock
|
||||||
lang="ansi"
|
lang="ansi"
|
||||||
|
@ -384,25 +384,26 @@ function TerminalPart(props: TerminalPartProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function ToolFooter(props: { time: number }) {
|
function ToolFooter(props: { time: number }) {
|
||||||
return (
|
return props.time > MIN_DURATION ? (
|
||||||
props.time > MIN_DURATION
|
<span data-part-footer title={`${props.time}ms`}>
|
||||||
? <span data-part-footer title={`${props.time}ms`}>
|
{formatDuration(props.time)}
|
||||||
{formatDuration(props.time)}
|
</span>
|
||||||
</span>
|
) : (
|
||||||
: <div data-part-footer="spacer"></div>
|
<div data-part-footer="spacer"></div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Share(props: { api: string }) {
|
export default function Share(props: {
|
||||||
|
api: string
|
||||||
|
data: { key: string; content: SessionMessage | SessionInfo }[]
|
||||||
|
}) {
|
||||||
let params = new URLSearchParams(document.location.search)
|
let params = new URLSearchParams(document.location.search)
|
||||||
const id = params.get("id")
|
const id = params.get("id")
|
||||||
|
|
||||||
const [store, setStore] = createStore<{
|
const [store, setStore] = createStore<{
|
||||||
info?: SessionInfo
|
info?: SessionInfo
|
||||||
messages: Record<string, SessionMessage>
|
messages: Record<string, SessionMessage>
|
||||||
}>({
|
}>({ messages: {} })
|
||||||
messages: {},
|
|
||||||
})
|
|
||||||
const messages = createMemo(() =>
|
const messages = createMemo(() =>
|
||||||
Object.values(store.messages).toSorted((a, b) => a.id?.localeCompare(b.id)),
|
Object.values(store.messages).toSorted((a, b) => a.id?.localeCompare(b.id)),
|
||||||
)
|
)
|
||||||
|
@ -410,6 +411,19 @@ export default function Share(props: { api: string }) {
|
||||||
[Status, string?]
|
[Status, string?]
|
||||||
>(["disconnected", "Disconnected"])
|
>(["disconnected", "Disconnected"])
|
||||||
|
|
||||||
|
const processDatum = (d: any) => {
|
||||||
|
const [root, type, ...splits] = d.key.split("/")
|
||||||
|
if (root !== "session") return
|
||||||
|
if (type === "info") {
|
||||||
|
setStore("info", reconcile(d.content))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (type === "message") {
|
||||||
|
const [, messageID] = splits
|
||||||
|
setStore("messages", messageID, reconcile(d.content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const apiUrl = props.api
|
const apiUrl = props.api
|
||||||
|
|
||||||
|
@ -424,6 +438,10 @@ export default function Share(props: { api: string }) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const datum of props.data) {
|
||||||
|
processDatum(datum)
|
||||||
|
}
|
||||||
|
|
||||||
let reconnectTimer: number | undefined
|
let reconnectTimer: number | undefined
|
||||||
let socket: WebSocket | null = null
|
let socket: WebSocket | null = null
|
||||||
|
|
||||||
|
@ -454,17 +472,7 @@ export default function Share(props: { api: string }) {
|
||||||
socket.onmessage = (event) => {
|
socket.onmessage = (event) => {
|
||||||
console.log("WebSocket message received")
|
console.log("WebSocket message received")
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.data)
|
processDatum(JSON.parse(event.data))
|
||||||
const [root, type, ...splits] = data.key.split("/")
|
|
||||||
if (root !== "session") return
|
|
||||||
if (type === "info") {
|
|
||||||
setStore("info", reconcile(data.content))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (type === "message") {
|
|
||||||
const [, messageID] = splits
|
|
||||||
setStore("messages", messageID, reconcile(data.content))
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error parsing WebSocket message:", error)
|
console.error("Error parsing WebSocket message:", error)
|
||||||
}
|
}
|
||||||
|
@ -540,16 +548,15 @@ export default function Share(props: { api: string }) {
|
||||||
result.tokens.output += assistant.tokens.output
|
result.tokens.output += assistant.tokens.output
|
||||||
result.tokens.reasoning += assistant.tokens.reasoning
|
result.tokens.reasoning += assistant.tokens.reasoning
|
||||||
|
|
||||||
result.models.push([
|
result.models.push([assistant.providerID, assistant.modelID])
|
||||||
assistant.providerID,
|
|
||||||
assistant.modelID,
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
const [showingSystemPrompt, showSystemPrompt] = createSignal(false)
|
const [showingSystemPrompt, showSystemPrompt] = createSignal(false)
|
||||||
|
|
||||||
|
console.log(data())
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main class={`${styles.root} not-content`}>
|
<main class={`${styles.root} not-content`}>
|
||||||
<div class={styles.header}>
|
<div class={styles.header}>
|
||||||
|
@ -563,9 +570,9 @@ export default function Share(props: { api: string }) {
|
||||||
data().created || 0,
|
data().created || 0,
|
||||||
).toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS)}
|
).toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS)}
|
||||||
>
|
>
|
||||||
{DateTime.fromMillis(
|
{DateTime.fromMillis(data().created || 0).toLocaleString(
|
||||||
data().created || 0,
|
DateTime.DATE_MED,
|
||||||
).toLocaleString(DateTime.DATE_MED)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span data-element-label data-placeholder>
|
<span data-element-label data-placeholder>
|
||||||
|
@ -575,7 +582,9 @@ export default function Share(props: { api: string }) {
|
||||||
</div>
|
</div>
|
||||||
<p data-section="status">
|
<p data-section="status">
|
||||||
<span data-status={connectionStatus()[0]}>●</span>
|
<span data-status={connectionStatus()[0]}>●</span>
|
||||||
<span data-element-label>{getStatusText(connectionStatus())}</span>
|
<span data-element-label>
|
||||||
|
{getStatusText(connectionStatus())}
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -645,11 +654,9 @@ export default function Share(props: { api: string }) {
|
||||||
onClick={() => showSystemPrompt((e) => !e)}
|
onClick={() => showSystemPrompt((e) => !e)}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
{
|
{showingSystemPrompt()
|
||||||
showingSystemPrompt()
|
? "Hide system prompt"
|
||||||
? "Hide system prompt"
|
: "Show system prompt"}
|
||||||
: "Show system prompt"
|
|
||||||
}
|
|
||||||
</span>
|
</span>
|
||||||
<span data-button-icon>
|
<span data-button-icon>
|
||||||
<Show
|
<Show
|
||||||
|
@ -825,27 +832,40 @@ export default function Share(props: { api: string }) {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(part) => {
|
{(part) => {
|
||||||
const metadata = createMemo(() =>
|
const metadata = createMemo(
|
||||||
msg.metadata?.tool[part().toolInvocation.toolCallId]
|
() =>
|
||||||
|
msg.metadata?.tool[
|
||||||
|
part().toolInvocation.toolCallId
|
||||||
|
],
|
||||||
)
|
)
|
||||||
const args = part().toolInvocation.args
|
const args = part().toolInvocation.args
|
||||||
const result = part().toolInvocation.state === "result" && part().toolInvocation.result
|
const result =
|
||||||
|
part().toolInvocation.state === "result" &&
|
||||||
|
part().toolInvocation.result
|
||||||
const matches = metadata()?.matches
|
const matches = metadata()?.matches
|
||||||
|
|
||||||
const { pattern, ...rest } = args
|
const { pattern, ...rest } = args
|
||||||
|
|
||||||
const duration = createMemo(() =>
|
const duration = createMemo(() =>
|
||||||
DateTime.fromMillis(metadata()?.time.end || 0).diff(
|
DateTime.fromMillis(metadata()?.time.end || 0)
|
||||||
DateTime.fromMillis(metadata()?.time.start || 0),
|
.diff(
|
||||||
).toMillis(),
|
DateTime.fromMillis(
|
||||||
|
metadata()?.time.start || 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toMillis(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-section="part" data-part-type="tool-grep">
|
<div
|
||||||
|
data-section="part"
|
||||||
|
data-part-type="tool-grep"
|
||||||
|
>
|
||||||
<div data-section="decoration">
|
<div data-section="decoration">
|
||||||
<div title="Grep files">
|
<div title="Grep files">
|
||||||
<IconDocumentMagnifyingGlass
|
<IconDocumentMagnifyingGlass
|
||||||
width={18} height={18}
|
width={18}
|
||||||
|
height={18}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div></div>
|
<div></div>
|
||||||
|
@ -873,13 +893,16 @@ export default function Share(props: { api: string }) {
|
||||||
<Match when={matches > 0}>
|
<Match when={matches > 0}>
|
||||||
<div data-part-tool-result>
|
<div data-part-tool-result>
|
||||||
<ResultsButton
|
<ResultsButton
|
||||||
showCopy={matches === 1
|
showCopy={
|
||||||
? "1 match"
|
matches === 1
|
||||||
: `${matches} matches`
|
? "1 match"
|
||||||
|
: `${matches} matches`
|
||||||
}
|
}
|
||||||
hideCopy="Hide matches"
|
hideCopy="Hide matches"
|
||||||
results={results()}
|
results={results()}
|
||||||
onClick={() => showResults((e) => !e)}
|
onClick={() =>
|
||||||
|
showResults((e) => !e)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Show when={results()}>
|
<Show when={results()}>
|
||||||
<TextPart
|
<TextPart
|
||||||
|
@ -919,25 +942,40 @@ export default function Share(props: { api: string }) {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(part) => {
|
{(part) => {
|
||||||
const metadata = createMemo(() =>
|
const metadata = createMemo(
|
||||||
msg.metadata?.tool[part().toolInvocation.toolCallId]
|
() =>
|
||||||
|
msg.metadata?.tool[
|
||||||
|
part().toolInvocation.toolCallId
|
||||||
|
],
|
||||||
)
|
)
|
||||||
const args = part().toolInvocation.args
|
const args = part().toolInvocation.args
|
||||||
const result = part().toolInvocation.state === "result" && part().toolInvocation.result
|
const result =
|
||||||
|
part().toolInvocation.state === "result" &&
|
||||||
|
part().toolInvocation.result
|
||||||
const count = metadata()?.count
|
const count = metadata()?.count
|
||||||
const pattern = args.pattern
|
const pattern = args.pattern
|
||||||
|
|
||||||
const duration = createMemo(() =>
|
const duration = createMemo(() =>
|
||||||
DateTime.fromMillis(metadata()?.time.end || 0).diff(
|
DateTime.fromMillis(metadata()?.time.end || 0)
|
||||||
DateTime.fromMillis(metadata()?.time.start || 0),
|
.diff(
|
||||||
).toMillis(),
|
DateTime.fromMillis(
|
||||||
|
metadata()?.time.start || 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toMillis(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-section="part" data-part-type="tool-glob">
|
<div
|
||||||
|
data-section="part"
|
||||||
|
data-part-type="tool-glob"
|
||||||
|
>
|
||||||
<div data-section="decoration">
|
<div data-section="decoration">
|
||||||
<div title="Glob files">
|
<div title="Glob files">
|
||||||
<IconMagnifyingGlass width={18} height={18} />
|
<IconMagnifyingGlass
|
||||||
|
width={18}
|
||||||
|
height={18}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -951,12 +989,15 @@ export default function Share(props: { api: string }) {
|
||||||
<Match when={count > 0}>
|
<Match when={count > 0}>
|
||||||
<div data-part-tool-result>
|
<div data-part-tool-result>
|
||||||
<ResultsButton
|
<ResultsButton
|
||||||
showCopy={count === 1
|
showCopy={
|
||||||
? "1 result"
|
count === 1
|
||||||
: `${count} results`
|
? "1 result"
|
||||||
|
: `${count} results`
|
||||||
}
|
}
|
||||||
results={results()}
|
results={results()}
|
||||||
onClick={() => showResults((e) => !e)}
|
onClick={() =>
|
||||||
|
showResults((e) => !e)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Show when={results()}>
|
<Show when={results()}>
|
||||||
<TextPart
|
<TextPart
|
||||||
|
@ -996,23 +1037,36 @@ export default function Share(props: { api: string }) {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(part) => {
|
{(part) => {
|
||||||
const metadata = createMemo(() =>
|
const metadata = createMemo(
|
||||||
msg.metadata?.tool[part().toolInvocation.toolCallId]
|
() =>
|
||||||
|
msg.metadata?.tool[
|
||||||
|
part().toolInvocation.toolCallId
|
||||||
|
],
|
||||||
)
|
)
|
||||||
const args = part().toolInvocation.args
|
const args = part().toolInvocation.args
|
||||||
const path = args.path
|
const path = args.path
|
||||||
|
|
||||||
const duration = createMemo(() =>
|
const duration = createMemo(() =>
|
||||||
DateTime.fromMillis(metadata()?.time.end || 0).diff(
|
DateTime.fromMillis(metadata()?.time.end || 0)
|
||||||
DateTime.fromMillis(metadata()?.time.start || 0),
|
.diff(
|
||||||
).toMillis(),
|
DateTime.fromMillis(
|
||||||
|
metadata()?.time.start || 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toMillis(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-section="part" data-part-type="tool-list">
|
<div
|
||||||
|
data-section="part"
|
||||||
|
data-part-type="tool-list"
|
||||||
|
>
|
||||||
<div data-section="decoration">
|
<div data-section="decoration">
|
||||||
<div title="List files">
|
<div title="List files">
|
||||||
<IconRectangleStack width={18} height={18} />
|
<IconRectangleStack
|
||||||
|
width={18}
|
||||||
|
height={18}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1026,21 +1080,25 @@ export default function Share(props: { api: string }) {
|
||||||
<Match
|
<Match
|
||||||
when={
|
when={
|
||||||
part().toolInvocation.state ===
|
part().toolInvocation.state ===
|
||||||
"result" &&
|
"result" &&
|
||||||
part().toolInvocation.result
|
part().toolInvocation.result
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div data-part-tool-result>
|
<div data-part-tool-result>
|
||||||
<ResultsButton
|
<ResultsButton
|
||||||
results={results()}
|
results={results()}
|
||||||
onClick={() => showResults((e) => !e)}
|
onClick={() =>
|
||||||
|
showResults((e) => !e)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Show when={results()}>
|
<Show when={results()}>
|
||||||
<TextPart
|
<TextPart
|
||||||
expand
|
expand
|
||||||
data-size="sm"
|
data-size="sm"
|
||||||
data-color="dimmed"
|
data-color="dimmed"
|
||||||
text={part().toolInvocation.result}
|
text={
|
||||||
|
part().toolInvocation.result
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1063,21 +1121,35 @@ export default function Share(props: { api: string }) {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(part) => {
|
{(part) => {
|
||||||
const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId])
|
const metadata = createMemo(
|
||||||
|
() =>
|
||||||
|
msg.metadata?.tool[
|
||||||
|
part().toolInvocation.toolCallId
|
||||||
|
],
|
||||||
|
)
|
||||||
const args = part().toolInvocation.args
|
const args = part().toolInvocation.args
|
||||||
const filePath = args.filePath
|
const filePath = args.filePath
|
||||||
const hasError = metadata()?.error
|
const hasError = metadata()?.error
|
||||||
const preview = metadata()?.preview
|
const preview = metadata()?.preview
|
||||||
const result = part().toolInvocation.state === "result" && part().toolInvocation.result
|
const result =
|
||||||
|
part().toolInvocation.state === "result" &&
|
||||||
|
part().toolInvocation.result
|
||||||
|
|
||||||
const duration = createMemo(() =>
|
const duration = createMemo(() =>
|
||||||
DateTime.fromMillis(metadata()?.time.end || 0).diff(
|
DateTime.fromMillis(metadata()?.time.end || 0)
|
||||||
DateTime.fromMillis(metadata()?.time.start || 0),
|
.diff(
|
||||||
).toMillis(),
|
DateTime.fromMillis(
|
||||||
|
metadata()?.time.start || 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toMillis(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-section="part" data-part-type="tool-read">
|
<div
|
||||||
|
data-section="part"
|
||||||
|
data-part-type="tool-read"
|
||||||
|
>
|
||||||
<div data-section="decoration">
|
<div data-section="decoration">
|
||||||
<div title="Read file">
|
<div title="Read file">
|
||||||
<IconDocument width={18} height={18} />
|
<IconDocument width={18} height={18} />
|
||||||
|
@ -1107,7 +1179,9 @@ export default function Share(props: { api: string }) {
|
||||||
showCopy="Show preview"
|
showCopy="Show preview"
|
||||||
hideCopy="Hide preview"
|
hideCopy="Hide preview"
|
||||||
results={results()}
|
results={results()}
|
||||||
onClick={() => showResults((e) => !e)}
|
onClick={() =>
|
||||||
|
showResults((e) => !e)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Show when={results()}>
|
<Show when={results()}>
|
||||||
<div data-part-tool-code>
|
<div data-part-tool-code>
|
||||||
|
@ -1123,7 +1197,9 @@ export default function Share(props: { api: string }) {
|
||||||
<div data-part-tool-result>
|
<div data-part-tool-result>
|
||||||
<ResultsButton
|
<ResultsButton
|
||||||
results={results()}
|
results={results()}
|
||||||
onClick={() => showResults((e) => !e)}
|
onClick={() =>
|
||||||
|
showResults((e) => !e)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Show when={results()}>
|
<Show when={results()}>
|
||||||
<TextPart
|
<TextPart
|
||||||
|
@ -1153,21 +1229,35 @@ export default function Share(props: { api: string }) {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(part) => {
|
{(part) => {
|
||||||
const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId])
|
const metadata = createMemo(
|
||||||
|
() =>
|
||||||
|
msg.metadata?.tool[
|
||||||
|
part().toolInvocation.toolCallId
|
||||||
|
],
|
||||||
|
)
|
||||||
const args = part().toolInvocation.args
|
const args = part().toolInvocation.args
|
||||||
const filePath = args.filePath
|
const filePath = args.filePath
|
||||||
const content = args.content
|
const content = args.content
|
||||||
const hasError = metadata()?.error
|
const hasError = metadata()?.error
|
||||||
const result = part().toolInvocation.state === "result" && part().toolInvocation.result
|
const result =
|
||||||
|
part().toolInvocation.state === "result" &&
|
||||||
|
part().toolInvocation.result
|
||||||
|
|
||||||
const duration = createMemo(() =>
|
const duration = createMemo(() =>
|
||||||
DateTime.fromMillis(metadata()?.time.end || 0).diff(
|
DateTime.fromMillis(metadata()?.time.end || 0)
|
||||||
DateTime.fromMillis(metadata()?.time.start || 0),
|
.diff(
|
||||||
).toMillis(),
|
DateTime.fromMillis(
|
||||||
|
metadata()?.time.start || 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toMillis(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-section="part" data-part-type="tool-write">
|
<div
|
||||||
|
data-section="part"
|
||||||
|
data-part-type="tool-write"
|
||||||
|
>
|
||||||
<div data-section="decoration">
|
<div data-section="decoration">
|
||||||
<div title="Write file">
|
<div title="Write file">
|
||||||
<IconDocumentPlus width={18} height={18} />
|
<IconDocumentPlus width={18} height={18} />
|
||||||
|
@ -1197,7 +1287,9 @@ export default function Share(props: { api: string }) {
|
||||||
showCopy="Show contents"
|
showCopy="Show contents"
|
||||||
hideCopy="Hide contents"
|
hideCopy="Hide contents"
|
||||||
results={results()}
|
results={results()}
|
||||||
onClick={() => showResults((e) => !e)}
|
onClick={() =>
|
||||||
|
showResults((e) => !e)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Show when={results()}>
|
<Show when={results()}>
|
||||||
<div data-part-tool-code>
|
<div data-part-tool-code>
|
||||||
|
@ -1227,14 +1319,23 @@ export default function Share(props: { api: string }) {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(part) => {
|
{(part) => {
|
||||||
const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId])
|
const metadata = createMemo(
|
||||||
|
() =>
|
||||||
|
msg.metadata?.tool[
|
||||||
|
part().toolInvocation.toolCallId
|
||||||
|
],
|
||||||
|
)
|
||||||
const args = part().toolInvocation.args
|
const args = part().toolInvocation.args
|
||||||
const filePath = args.filePath
|
const filePath = args.filePath
|
||||||
|
|
||||||
const duration = createMemo(() =>
|
const duration = createMemo(() =>
|
||||||
DateTime.fromMillis(metadata()?.time.end || 0).diff(
|
DateTime.fromMillis(metadata()?.time.end || 0)
|
||||||
DateTime.fromMillis(metadata()?.time.start || 0),
|
.diff(
|
||||||
).toMillis(),
|
DateTime.fromMillis(
|
||||||
|
metadata()?.time.start || 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toMillis(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1278,17 +1379,29 @@ export default function Share(props: { api: string }) {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(part) => {
|
{(part) => {
|
||||||
const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId])
|
const metadata = createMemo(
|
||||||
|
() =>
|
||||||
|
msg.metadata?.tool[
|
||||||
|
part().toolInvocation.toolCallId
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
const command = part().toolInvocation.args.command
|
const command = part().toolInvocation.args.command
|
||||||
const desc = part().toolInvocation.args.description
|
const desc = part().toolInvocation.args.description
|
||||||
const stdout = metadata()?.stdout
|
const stdout = metadata()?.stdout
|
||||||
const result = stdout || (part().toolInvocation.state === "result" && part().toolInvocation.result)
|
const result =
|
||||||
|
stdout ||
|
||||||
|
(part().toolInvocation.state === "result" &&
|
||||||
|
part().toolInvocation.result)
|
||||||
|
|
||||||
const duration = createMemo(() =>
|
const duration = createMemo(() =>
|
||||||
DateTime.fromMillis(metadata()?.time.end || 0).diff(
|
DateTime.fromMillis(metadata()?.time.end || 0)
|
||||||
DateTime.fromMillis(metadata()?.time.start || 0),
|
.diff(
|
||||||
).toMillis(),
|
DateTime.fromMillis(
|
||||||
|
metadata()?.time.start || 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toMillis(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1307,7 +1420,9 @@ export default function Share(props: { api: string }) {
|
||||||
<TerminalPart
|
<TerminalPart
|
||||||
desc={desc}
|
desc={desc}
|
||||||
data-size="sm"
|
data-size="sm"
|
||||||
text={command + (result ? `\n${result}` : "")}
|
text={
|
||||||
|
command + (result ? `\n${result}` : "")
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ToolFooter time={duration()} />
|
<ToolFooter time={duration()} />
|
||||||
|
@ -1321,17 +1436,27 @@ export default function Share(props: { api: string }) {
|
||||||
when={
|
when={
|
||||||
msg.role === "assistant" &&
|
msg.role === "assistant" &&
|
||||||
part.type === "tool-invocation" &&
|
part.type === "tool-invocation" &&
|
||||||
part.toolInvocation.toolName === "opencode_todoread" &&
|
part.toolInvocation.toolName ===
|
||||||
|
"opencode_todoread" &&
|
||||||
part
|
part
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(part) => {
|
{(part) => {
|
||||||
const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId])
|
const metadata = createMemo(
|
||||||
|
() =>
|
||||||
|
msg.metadata?.tool[
|
||||||
|
part().toolInvocation.toolCallId
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
const duration = createMemo(() =>
|
const duration = createMemo(() =>
|
||||||
DateTime.fromMillis(metadata()?.time.end || 0).diff(
|
DateTime.fromMillis(metadata()?.time.end || 0)
|
||||||
DateTime.fromMillis(metadata()?.time.start || 0),
|
.diff(
|
||||||
).toMillis(),
|
DateTime.fromMillis(
|
||||||
|
metadata()?.time.start || 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toMillis(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1362,24 +1487,39 @@ export default function Share(props: { api: string }) {
|
||||||
when={
|
when={
|
||||||
msg.role === "assistant" &&
|
msg.role === "assistant" &&
|
||||||
part.type === "tool-invocation" &&
|
part.type === "tool-invocation" &&
|
||||||
part.toolInvocation.toolName === "opencode_todowrite" &&
|
part.toolInvocation.toolName ===
|
||||||
|
"opencode_todowrite" &&
|
||||||
part
|
part
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(part) => {
|
{(part) => {
|
||||||
const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId])
|
const metadata = createMemo(
|
||||||
|
() =>
|
||||||
const todos = createMemo(() => sortTodosByStatus(
|
msg.metadata?.tool[
|
||||||
part().toolInvocation.args.todos
|
part().toolInvocation.toolCallId
|
||||||
))
|
],
|
||||||
const starting = todos().every(t => t.status === "pending")
|
)
|
||||||
const finished = todos().every(t => t.status === "completed")
|
|
||||||
|
|
||||||
|
const todos = createMemo(() =>
|
||||||
|
sortTodosByStatus(
|
||||||
|
part().toolInvocation.args.todos,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
const starting = todos().every(
|
||||||
|
(t) => t.status === "pending",
|
||||||
|
)
|
||||||
|
const finished = todos().every(
|
||||||
|
(t) => t.status === "completed",
|
||||||
|
)
|
||||||
|
|
||||||
const duration = createMemo(() =>
|
const duration = createMemo(() =>
|
||||||
DateTime.fromMillis(metadata()?.time.end || 0).diff(
|
DateTime.fromMillis(metadata()?.time.end || 0)
|
||||||
DateTime.fromMillis(metadata()?.time.start || 0),
|
.diff(
|
||||||
).toMillis(),
|
DateTime.fromMillis(
|
||||||
|
metadata()?.time.start || 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toMillis(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1408,12 +1548,12 @@ export default function Share(props: { api: string }) {
|
||||||
<Show when={todos().length > 0}>
|
<Show when={todos().length > 0}>
|
||||||
<ul class={styles.todos}>
|
<ul class={styles.todos}>
|
||||||
<For each={todos()}>
|
<For each={todos()}>
|
||||||
{({ status, content }) =>
|
{({ status, content }) => (
|
||||||
<li data-status={status}>
|
<li data-status={status}>
|
||||||
<span></span>
|
<span></span>
|
||||||
{content}
|
{content}
|
||||||
</li>
|
</li>
|
||||||
}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</ul>
|
</ul>
|
||||||
</Show>
|
</Show>
|
||||||
|
@ -1429,26 +1569,41 @@ export default function Share(props: { api: string }) {
|
||||||
when={
|
when={
|
||||||
msg.role === "assistant" &&
|
msg.role === "assistant" &&
|
||||||
part.type === "tool-invocation" &&
|
part.type === "tool-invocation" &&
|
||||||
part.toolInvocation.toolName === "opencode_webfetch" &&
|
part.toolInvocation.toolName ===
|
||||||
|
"opencode_webfetch" &&
|
||||||
part
|
part
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(part) => {
|
{(part) => {
|
||||||
const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId])
|
const metadata = createMemo(
|
||||||
|
() =>
|
||||||
|
msg.metadata?.tool[
|
||||||
|
part().toolInvocation.toolCallId
|
||||||
|
],
|
||||||
|
)
|
||||||
const args = part().toolInvocation.args
|
const args = part().toolInvocation.args
|
||||||
const url = args.url
|
const url = args.url
|
||||||
const format = args.format
|
const format = args.format
|
||||||
const hasError = metadata()?.error
|
const hasError = metadata()?.error
|
||||||
const result = part().toolInvocation.state === "result" && part().toolInvocation.result
|
const result =
|
||||||
|
part().toolInvocation.state === "result" &&
|
||||||
|
part().toolInvocation.result
|
||||||
|
|
||||||
const duration = createMemo(() =>
|
const duration = createMemo(() =>
|
||||||
DateTime.fromMillis(metadata()?.time.end || 0).diff(
|
DateTime.fromMillis(metadata()?.time.end || 0)
|
||||||
DateTime.fromMillis(metadata()?.time.start || 0),
|
.diff(
|
||||||
).toMillis(),
|
DateTime.fromMillis(
|
||||||
|
metadata()?.time.start || 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toMillis(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-section="part" data-part-type="tool-fetch">
|
<div
|
||||||
|
data-section="part"
|
||||||
|
data-part-type="tool-fetch"
|
||||||
|
>
|
||||||
<div data-section="decoration">
|
<div data-section="decoration">
|
||||||
<div title="Web fetch">
|
<div title="Web fetch">
|
||||||
<IconGlobeAlt width={18} height={18} />
|
<IconGlobeAlt width={18} height={18} />
|
||||||
|
@ -1476,7 +1631,9 @@ export default function Share(props: { api: string }) {
|
||||||
<div data-part-tool-result>
|
<div data-part-tool-result>
|
||||||
<ResultsButton
|
<ResultsButton
|
||||||
results={results()}
|
results={results()}
|
||||||
onClick={() => showResults((e) => !e)}
|
onClick={() =>
|
||||||
|
showResults((e) => !e)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Show when={results()}>
|
<Show when={results()}>
|
||||||
<div data-part-tool-code>
|
<div data-part-tool-code>
|
||||||
|
@ -1505,12 +1662,21 @@ export default function Share(props: { api: string }) {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(part) => {
|
{(part) => {
|
||||||
const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId])
|
const metadata = createMemo(
|
||||||
|
() =>
|
||||||
|
msg.metadata?.tool[
|
||||||
|
part().toolInvocation.toolCallId
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
const duration = createMemo(() =>
|
const duration = createMemo(() =>
|
||||||
DateTime.fromMillis(metadata()?.time.end || 0).diff(
|
DateTime.fromMillis(metadata()?.time.end || 0)
|
||||||
DateTime.fromMillis(metadata()?.time.start || 0),
|
.diff(
|
||||||
).toMillis(),
|
DateTime.fromMillis(
|
||||||
|
metadata()?.time.start || 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toMillis(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1551,21 +1717,25 @@ export default function Share(props: { api: string }) {
|
||||||
<Match
|
<Match
|
||||||
when={
|
when={
|
||||||
part().toolInvocation.state ===
|
part().toolInvocation.state ===
|
||||||
"result" &&
|
"result" &&
|
||||||
part().toolInvocation.result
|
part().toolInvocation.result
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div data-part-tool-result>
|
<div data-part-tool-result>
|
||||||
<ResultsButton
|
<ResultsButton
|
||||||
results={results()}
|
results={results()}
|
||||||
onClick={() => showResults((e) => !e)}
|
onClick={() =>
|
||||||
|
showResults((e) => !e)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Show when={results()}>
|
<Show when={results()}>
|
||||||
<TextPart
|
<TextPart
|
||||||
expand
|
expand
|
||||||
data-size="sm"
|
data-size="sm"
|
||||||
data-color="dimmed"
|
data-color="dimmed"
|
||||||
text={part().toolInvocation.result}
|
text={
|
||||||
|
part().toolInvocation.result
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,6 +4,12 @@ import config from "virtual:starlight/user-config";
|
||||||
import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro';
|
import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro';
|
||||||
import Share from "../../components/Share.tsx";
|
import Share from "../../components/Share.tsx";
|
||||||
|
|
||||||
|
const apiUrl = import.meta.env.VITE_API_URL;
|
||||||
|
|
||||||
|
const id = Astro.url.searchParams.get('id')
|
||||||
|
const res = await fetch(`${apiUrl}/share_messages?id=${id}`);
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<StarlightPage
|
<StarlightPage
|
||||||
|
@ -15,7 +21,7 @@ import Share from "../../components/Share.tsx";
|
||||||
tableOfContents: false,
|
tableOfContents: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Share api={import.meta.env.VITE_API_URL} client:only="solid" />
|
<Share api={apiUrl} data={data.messages} client:only="solid" />
|
||||||
</StarlightPage>
|
</StarlightPage>
|
||||||
|
|
||||||
<style is:global>
|
<style is:global>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue