mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-03 13:00:37 +00:00
transcript: clamp long user messages with a more…/less… toggle
Pastes over 1200 chars collapse in the bubble; copy still gets the full text.
This commit is contained in:
@@ -57,3 +57,37 @@ test("approval: Deny skips the tool and the agent says so", async ({ page }) =>
|
||||
await page.getByRole("button", { name: "Deny" }).last().click();
|
||||
await expect(page.getByText("Understood — skipped the command.")).toBeVisible();
|
||||
});
|
||||
|
||||
test("long user pastes clamp with a more…/less… toggle", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
const box = page.getByPlaceholder(/Ask the coworker/);
|
||||
await expect(box).toBeVisible();
|
||||
|
||||
const tail = "END-OF-PASTE-MARKER";
|
||||
const paste =
|
||||
"reply OK. " + "lorem ipsum dolor sit amet consectetur ".repeat(60) + tail; // ~2.4k chars
|
||||
await box.fill(paste);
|
||||
await page.getByRole("button", { name: "Send" }).click();
|
||||
|
||||
// Clamped: the bubble shows the head but not the tail, plus the toggle.
|
||||
const more = page.getByRole("button", { name: "more…" });
|
||||
await expect(more).toBeVisible();
|
||||
const bubble = page.locator(".bubble-user").last();
|
||||
await expect(bubble).toContainText("reply OK.");
|
||||
await expect(bubble).not.toContainText(tail);
|
||||
|
||||
// Expand → full text + "less…"; collapse → clamped again.
|
||||
await more.click();
|
||||
await expect(bubble).toContainText(tail);
|
||||
const less = page.getByRole("button", { name: "less…" });
|
||||
await expect(less).toBeVisible();
|
||||
await less.click();
|
||||
await expect(bubble).not.toContainText(tail);
|
||||
|
||||
// Short messages never show the control.
|
||||
await expect(page.getByText("Echo:").first()).toBeVisible();
|
||||
await box.fill("short follow-up");
|
||||
await page.getByRole("button", { name: "Send" }).click();
|
||||
await expect(page.getByText("short follow-up", { exact: true }).first()).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "more…" })).toHaveCount(1); // still only the paste's
|
||||
});
|
||||
|
||||
@@ -6,6 +6,28 @@ import { Markdown } from "./Markdown";
|
||||
import { ConnectorMessageCard } from "./ConnectorMessageCard";
|
||||
import { Icon } from "./Icon";
|
||||
|
||||
// Long user pastes swallow the transcript (owner ask 2026-07-30): clamp past a generous
|
||||
// threshold with a more…/less… toggle. Normal typed messages never see the control; the
|
||||
// full text still drives copy (BubbleMeta) and is what the model received.
|
||||
const USER_CLAMP_CHARS = 1200;
|
||||
|
||||
function ClampedUserText({ text }: { text: string }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
if (text.length <= USER_CLAMP_CHARS) return <>{text}</>;
|
||||
return (
|
||||
<>
|
||||
{open ? text : text.slice(0, USER_CLAMP_CHARS).trimEnd() + "…"}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen((o) => !o)}
|
||||
className="block mt-1.5 text-[12.5px] font-medium underline underline-offset-2 opacity-75 hover:opacity-100"
|
||||
>
|
||||
{open ? "less…" : "more…"}
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Hover affordances for a message bubble (FB-005): copy the raw text + the message's time.
|
||||
// Lives in a ZERO-HEIGHT strip under the bubble (absolute, inside the transcript's 20px gap)
|
||||
// so revealing it on group-hover never shifts the layout. `ts` is unix seconds — canonical
|
||||
@@ -398,7 +420,7 @@ export function Transcript({ items, running, streamingText, onRetry }: Props) {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{item.text}
|
||||
<ClampedUserText text={item.text} />
|
||||
</div>
|
||||
<BubbleMeta text={item.text} ts={item.ts} align="right" />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user