Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions packages/app/src/pages/session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -329,10 +329,9 @@ export default function Page() {
const { params, sessionKey, tabs, view } = useSessionLayout()

createEffect(() => {
if (!untrack(() => prompt.ready())) return
prompt.ready()
if (!prompt.ready()) return
untrack(() => {
if (params.id || !prompt.ready()) return
if (params.id) return
const text = searchParams.prompt
if (!text) return
prompt.set([{ type: "text", content: text, start: 0, end: text.length }], text.length)
Expand Down
5 changes: 0 additions & 5 deletions packages/ui/src/components/line-comment-annotations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -294,11 +294,6 @@ export function createLineCommentState<T>(props: LineCommentStateProps<T>) {
cancelDraft()
}

createEffect(() => {
props.commenting()
setDraft("")
})

return {
draft,
setDraft,
Expand Down
19 changes: 6 additions & 13 deletions packages/ui/src/components/line-comment.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useFilteredList } from "@opencode-ai/ui/hooks"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
import { createEffect, createSignal, For, onMount, Show, splitProps, type JSX } from "solid-js"
import { createSignal, For, onMount, Show, splitProps, type JSX } from "solid-js"
import { Button } from "./button"
import { FileIcon } from "./file-icon"
import { Icon } from "./icon"
Expand Down Expand Up @@ -210,7 +210,6 @@ export const LineCommentEditor = (props: LineCommentEditorProps) => {
const refs = {
textarea: undefined as HTMLTextAreaElement | undefined,
}
const [text, setText] = createSignal(split.value)
const [open, setOpen] = createSignal(false)

function selectMention(item: { path: string } | undefined) {
Expand All @@ -220,10 +219,9 @@ export const LineCommentEditor = (props: LineCommentEditorProps) => {
const query = currentMention()
if (!textarea || !query) return

const value = `${text().slice(0, query.start)}@${item.path} ${text().slice(query.end)}`
const value = `${textarea.value.slice(0, query.start)}@${item.path} ${textarea.value.slice(query.end)}`
const cursor = query.start + item.path.length + 2

setText(value)
split.onInput(value)
closeMention()

Expand Down Expand Up @@ -257,10 +255,6 @@ export const LineCommentEditor = (props: LineCommentEditorProps) => {
fn()
}

createEffect(() => {
setText(split.value)
})

const closeMention = () => {
setOpen(false)
mention.clear()
Expand Down Expand Up @@ -302,7 +296,7 @@ export const LineCommentEditor = (props: LineCommentEditorProps) => {
}

const submit = () => {
const value = text().trim()
const value = split.value.trim()
if (!value) return
split.onSubmit(value)
}
Expand All @@ -322,10 +316,9 @@ export const LineCommentEditor = (props: LineCommentEditorProps) => {
data-slot="line-comment-textarea"
rows={split.rows ?? 3}
placeholder={split.placeholder ?? i18n.t("ui.lineComment.placeholder")}
value={text()}
value={split.value}
on:input={(e) => {
const value = (e.currentTarget as HTMLTextAreaElement).value
setText(value)
split.onInput(value)
syncMention()
}}
Expand Down Expand Up @@ -422,7 +415,7 @@ export const LineCommentEditor = (props: LineCommentEditorProps) => {
type="button"
data-slot="line-comment-action"
data-variant="primary"
disabled={text().trim().length === 0}
disabled={split.value.trim().length === 0}
on:mousedown={hold as any}
on:click={click(submit) as any}
>
Expand All @@ -434,7 +427,7 @@ export const LineCommentEditor = (props: LineCommentEditorProps) => {
<Button size="small" variant="ghost" onClick={split.onCancel}>
{split.cancelLabel ?? i18n.t("ui.common.cancel")}
</Button>
<Button size="small" variant="primary" disabled={text().trim().length === 0} onClick={submit}>
<Button size="small" variant="primary" disabled={split.value.trim().length === 0} onClick={submit}>
{split.submitLabel ?? i18n.t("ui.lineComment.submit")}
</Button>
</Show>
Expand Down
31 changes: 23 additions & 8 deletions packages/ui/src/components/message-part.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,19 @@ function createPacedValue(getValue: () => string, live?: () => boolean) {
return value
}

function PacedMarkdown(props: { text: string; cacheKey: string; streaming: boolean }) {
const value = createPacedValue(
() => props.text,
() => props.streaming,
)

return (
<Show when={value()}>
<Markdown text={value()} cacheKey={props.cacheKey} streaming={props.streaming} />
</Show>
)
}

function relativizeProjectPath(path: string, directory?: string) {
if (!path) return ""
if (!directory) return path
Expand Down Expand Up @@ -1373,8 +1386,7 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
const streaming = createMemo(
() => props.message.role === "assistant" && typeof (props.message as AssistantMessage).time.completed !== "number",
)
const displayText = () => (part().text ?? "").trim()
const throttledText = createPacedValue(displayText, streaming)
const text = () => (part().text ?? "").trim()
const isLastTextPart = createMemo(() => {
const last = (data.store.part?.[props.message.id] ?? [])
.filter((item): item is TextPart => item?.type === "text" && !!item.text?.trim())
Expand All @@ -1390,18 +1402,20 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
const [copied, setCopied] = createSignal(false)

const handleCopy = async () => {
const content = displayText()
const content = text()
if (!content) return
await navigator.clipboard.writeText(content)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}

return (
<Show when={throttledText()}>
<Show when={text()}>
<div data-component="text-part">
<div data-slot="text-part-body">
<Markdown text={throttledText()} cacheKey={part().id} streaming={streaming()} />
<Show when={streaming()} fallback={<Markdown text={text()} cacheKey={part().id} streaming={false} />}>
<PacedMarkdown text={text()} cacheKey={part().id} streaming={streaming()} />
</Show>
</div>
<Show when={showCopy()}>
<div data-slot="text-part-copy-wrapper" data-interrupted={interrupted() ? "" : undefined}>
Expand Down Expand Up @@ -1437,12 +1451,13 @@ PART_MAPPING["reasoning"] = function ReasoningPartDisplay(props) {
() => props.message.role === "assistant" && typeof (props.message as AssistantMessage).time.completed !== "number",
)
const text = () => part().text.trim()
const throttledText = createPacedValue(text, streaming)

return (
<Show when={throttledText()}>
<Show when={text()}>
<div data-component="reasoning-part">
<Markdown text={throttledText()} cacheKey={part().id} streaming={streaming()} />
<Show when={streaming()} fallback={<Markdown text={text()} cacheKey={part().id} streaming={false} />}>
<PacedMarkdown text={text()} cacheKey={part().id} streaming={streaming()} />
</Show>
</div>
</Show>
)
Expand Down
5 changes: 1 addition & 4 deletions packages/ui/src/components/session-turn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -343,25 +343,22 @@ export function SessionTurn(
})
const assistantDerived = createMemo(() => {
let visible = 0
let tail: "text" | "other" | undefined
let reason: string | undefined
const show = showReasoningSummaries()
for (const message of assistantMessages()) {
for (const part of list(data.store.part?.[message.id], emptyParts)) {
if (partState(part, show) === "visible") {
visible++
tail = part.type === "text" ? "text" : "other"
}
if (part.type === "reasoning" && part.text) {
const h = heading(part.text)
if (h) reason = h
}
}
}
return { visible, tail, reason }
return { visible, reason }
})
const assistantVisible = createMemo(() => assistantDerived().visible)
const assistantTailVisible = createMemo(() => assistantDerived().tail)
const reasoningHeading = createMemo(() => assistantDerived().reason)
const showThinking = createMemo(() => {
if (!working() || !!error()) return false
Expand Down
14 changes: 3 additions & 11 deletions packages/web/src/components/Share.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -366,21 +366,13 @@ export default function Share(props: {
<Suspense>
<For each={filteredParts()}>
{(part, partIndex) => {
const last = createMemo(
() =>
data().messages.length === msgIndex() + 1 &&
filteredParts().length === partIndex() + 1,
)
const last = () =>
data().messages.length === msgIndex() + 1 && filteredParts().length === partIndex() + 1

onMount(() => {
const hash = window.location.hash.slice(1)
// Wait till all parts are loaded
if (
hash !== "" &&
!hasScrolledToAnchor &&
filteredParts().length === partIndex() + 1 &&
data().messages.length === msgIndex() + 1
) {
if (hash !== "" && !hasScrolledToAnchor && last()) {
hasScrolledToAnchor = true
scrollToAnchor(hash)
}
Expand Down
11 changes: 7 additions & 4 deletions packages/web/src/components/share/common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,15 @@ export function createOverflow() {
return overflow()
},
ref(el: HTMLElement) {
const sync = () => {
setOverflow(el.scrollHeight > el.clientHeight + 1)
}

const ro = new ResizeObserver(() => {
if (el.scrollHeight > el.clientHeight + 1) {
setOverflow(true)
}
return
sync()
})

sync()
ro.observe(el)

onCleanup(() => {
Expand Down
47 changes: 28 additions & 19 deletions packages/web/src/components/share/content-diff.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { parsePatch } from "diff"
import { createMemo } from "solid-js"
import { createMemo, For } from "solid-js"
import { ContentCode } from "./content-code"
import styles from "./content-diff.module.css"

Expand Down Expand Up @@ -160,28 +160,37 @@ export function ContentDiff(props: Props) {
return (
<div class={styles.root}>
<div data-component="desktop">
{rows().map((r) => (
<div data-component="diff-row" data-type={r.type}>
<div data-slot="before" data-diff-type={r.type === "removed" || r.type === "modified" ? "removed" : ""}>
<ContentCode code={r.left} flush lang={props.lang} />
</div>
<div data-slot="after" data-diff-type={r.type === "added" || r.type === "modified" ? "added" : ""}>
<ContentCode code={r.right} lang={props.lang} flush />
<For each={rows()}>
{(row) => (
<div data-component="diff-row" data-type={row.type}>
<div
data-slot="before"
data-diff-type={row.type === "removed" || row.type === "modified" ? "removed" : ""}
>
<ContentCode code={row.left} flush lang={props.lang} />
</div>
<div data-slot="after" data-diff-type={row.type === "added" || row.type === "modified" ? "added" : ""}>
<ContentCode code={row.right} lang={props.lang} flush />
</div>
</div>
</div>
))}
)}
</For>
</div>

<div data-component="mobile">
{mobileRows().map((block) => (
<div data-component="diff-block" data-type={block.type}>
{block.lines.map((line) => (
<div data-diff-type={block.type === "removed" ? "removed" : block.type === "added" ? "added" : ""}>
<ContentCode code={line} lang={props.lang} flush />
</div>
))}
</div>
))}
<For each={mobileRows()}>
{(block) => (
<div data-component="diff-block" data-type={block.type}>
<For each={block.lines}>
{(line) => (
<div data-diff-type={block.type === "removed" ? "removed" : block.type === "added" ? "added" : ""}>
<ContentCode code={line} lang={props.lang} flush />
</div>
)}
</For>
</div>
)}
</For>
</div>
</div>
)
Expand Down
Loading