mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-24 06:33:01 -04:00
fix(desktop): render prompt images and patch actions
This commit is contained in:
@@ -2,6 +2,36 @@ import { expect, test } from "@playwright/test"
|
||||
import { assistantMessage, setupTimeline, toolPart, userMessage } from "../performance/timeline-stability/fixture"
|
||||
import { createTwoFilesPatch } from "diff"
|
||||
|
||||
test("labels single-file patches by operation", async ({ page }) => {
|
||||
const cases = [
|
||||
{ id: "prt_created_patch", file: "src/new.ts", status: "added" as const, title: "Created" },
|
||||
{ id: "prt_removed_patch", file: "src/old.ts", status: "deleted" as const, title: "Removed" },
|
||||
{ id: "prt_modified_patch", file: "src/current.ts", status: "modified" as const, title: "Patch" },
|
||||
]
|
||||
await setupTimeline(page, {
|
||||
messages: [
|
||||
userMessage(),
|
||||
assistantMessage(
|
||||
cases.map((item) =>
|
||||
toolPart(
|
||||
item.id,
|
||||
"patch",
|
||||
"completed",
|
||||
{ patchText: `Update ${item.file}` },
|
||||
{ metadata: { files: [patchFile(item.file, item.status)] } },
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
})
|
||||
|
||||
for (const item of cases) {
|
||||
await expect(page.locator(`[data-timeline-part-id="${item.id}"] [data-slot="message-part-title-text"]`)).toHaveText(
|
||||
item.title,
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
test("preserves nested patch file state through outer collapse and reopen", async ({ page }) => {
|
||||
const patchID = "prt_nested_patch"
|
||||
const files = [patchFile("src/a.ts", "modified"), patchFile("src/b.ts", "added"), patchFile("src/old.ts", "deleted")]
|
||||
|
||||
@@ -133,7 +133,7 @@ test.describe("session timeline projection", () => {
|
||||
})
|
||||
|
||||
test("renders user image, file attachment, file reference, and agent reference", async ({ page }) => {
|
||||
const text = "Use @explore with @src/a.ts and inspect the attachments"
|
||||
const text = "[Image 1] Use @explore with @src/a.ts and inspect the attachments"
|
||||
const parts: PartSeed<"user">[] = [
|
||||
userText(text, { id: "prt_user_rich" }),
|
||||
{
|
||||
@@ -142,6 +142,7 @@ test.describe("session timeline projection", () => {
|
||||
mime: "image/png",
|
||||
filename: "pixel.png",
|
||||
url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
|
||||
source: { type: "file", path: "pixel.png", text: { value: "[Image 1]", start: 0, end: 9 } },
|
||||
},
|
||||
{
|
||||
id: "prt_user_attachment",
|
||||
@@ -156,18 +157,19 @@ test.describe("session timeline projection", () => {
|
||||
mime: "text/plain",
|
||||
filename: "a.ts",
|
||||
url: "src/a.ts",
|
||||
source: { type: "file", path: "src/a.ts", text: { value: "@src/a.ts", start: 18, end: 27 } },
|
||||
source: { type: "file", path: "src/a.ts", text: { value: "@src/a.ts", start: 28, end: 37 } },
|
||||
},
|
||||
{
|
||||
id: "prt_user_agent",
|
||||
type: "agent",
|
||||
name: "explore",
|
||||
source: { value: "@explore", start: 4, end: 12 },
|
||||
source: { value: "@explore", start: 14, end: 22 },
|
||||
},
|
||||
]
|
||||
await setupTimeline(page, { messages: [userMessage(parts), assistantMessage()] })
|
||||
|
||||
await expect(page.getByAltText("pixel.png")).toBeVisible()
|
||||
await expect(page.getByText("[Image 1]", { exact: true })).toBeVisible()
|
||||
await expect(page.getByText("tsconfig.json")).toBeVisible()
|
||||
await expect(page.getByText("@src/a.ts", { exact: true })).toBeVisible()
|
||||
await expect(page.getByText("@explore", { exact: true })).toBeVisible()
|
||||
|
||||
@@ -206,7 +206,9 @@ export function CurrentUserMessageDisplay(props: {
|
||||
const dialog = useDialog()
|
||||
const i18n = useI18n()
|
||||
const [state, setState] = createStore({ copied: false, reverting: false })
|
||||
const attachments = createMemo(() => (props.message.files ?? []).filter((file) => !file.mention))
|
||||
const attachments = createMemo(() =>
|
||||
(props.message.files ?? []).filter((file) => !file.mention || file.mime.startsWith("image/")),
|
||||
)
|
||||
const inlineFiles = createMemo(() => (props.message.files ?? []).filter((file) => !!file.mention))
|
||||
const agents = createMemo(() => props.message.agents ?? [])
|
||||
const comments = createMemo(() => props.comments ?? [])
|
||||
|
||||
@@ -1397,6 +1397,11 @@ ToolRegistry.register({
|
||||
if (list.length !== 1) return undefined
|
||||
return list[0]
|
||||
})
|
||||
const title = createMemo(() => {
|
||||
if (pending() || !single() || single()!.type === "update") return i18n.t("ui.tool.patch")
|
||||
if (single()?.type === "add") return i18n.t("ui.patch.action.created")
|
||||
return i18n.t("ui.patch.action.removed")
|
||||
})
|
||||
const [expanded, setExpanded] = createSignal<string[]>([])
|
||||
let seeded = false
|
||||
|
||||
@@ -1525,7 +1530,7 @@ ToolRegistry.register({
|
||||
<div data-slot="message-part-title-area">
|
||||
<div data-slot="message-part-title">
|
||||
<span data-slot="message-part-title-text">
|
||||
<TextShimmer text={i18n.t("ui.tool.patch")} active={pending()} />
|
||||
<TextShimmer text={title()} active={pending()} />
|
||||
</span>
|
||||
<Show when={!pending()}>
|
||||
<span data-slot="message-part-title-filename">{getFilename(single()!.path)}</span>
|
||||
|
||||
@@ -213,6 +213,7 @@ const source = {
|
||||
|
||||
"ui.patch.action.deleted": "Deleted",
|
||||
"ui.patch.action.created": "Created",
|
||||
"ui.patch.action.removed": "Removed",
|
||||
"ui.patch.action.moved": "Moved",
|
||||
"ui.patch.action.patched": "Patched",
|
||||
|
||||
|
||||
Reference in New Issue
Block a user