Skip to content
Open
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
18 changes: 13 additions & 5 deletions apps/app/src/app/components/part-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { For, Match, Show, Switch, createEffect, createMemo, createSignal, onCle
import { marked } from "marked";
import type { Part } from "@opencode-ai/sdk/v2/client";
import { File } from "lucide-solid";
import { isTauriRuntime, safeStringify, summarizeStep } from "../utils";
import { isTauriRuntime, isUserSkillIndicatorTextPart, safeStringify, summarizeStep } from "../utils";
import { usePlatform } from "../context/platform";
import { perfNow, recordPerfLog } from "../lib/perf-log";

Expand Down Expand Up @@ -573,6 +573,14 @@ export default function PartView(props: Props) {
const textClass = () => (tone() === "dark" ? "text-gray-12" : "text-gray-12");
const subtleTextClass = () => (tone() === "dark" ? "text-gray-12/70" : "text-gray-11");
const panelBgClass = () => (tone() === "dark" ? "bg-gray-2/10" : "bg-gray-2/30");
const primaryTextClass = createMemo(() => {
if (p().type !== "text" || !isUserSkillIndicatorTextPart(p())) {
return textClass();
}
return tone() === "dark"
? "italic text-[13px] leading-snug text-gray-11"
: "italic text-[13px] leading-snug text-gray-10";
});
const toolOnly = () => true;
const showToolOutput = () => developerMode();
const markdownSource = createMemo(() => {
Expand Down Expand Up @@ -873,7 +881,7 @@ export default function PartView(props: Props) {
ref={(el) => {
textContainerEl = el;
}}
class={`whitespace-pre-wrap break-words text-[14px] leading-relaxed max-h-[22rem] overflow-hidden ${textClass()}`.trim()}
class={`whitespace-pre-wrap break-words text-[14px] leading-relaxed max-h-[22rem] overflow-hidden ${primaryTextClass()}`.trim()}
>
{collapsedPreviewText()}
</div>
Expand All @@ -899,7 +907,7 @@ export default function PartView(props: Props) {
ref={(el) => {
textContainerEl = el;
}}
class={`whitespace-pre-wrap break-words ${textClass()}`.trim()}
class={`whitespace-pre-wrap break-words ${primaryTextClass()}`.trim()}
>
{renderTextWithLinks()}
</div>
Expand All @@ -909,15 +917,15 @@ export default function PartView(props: Props) {
<Show when={renderedMarkdown() === null}>
<div
ref={(el) => { textContainerEl = el; }}
class={`whitespace-pre-wrap break-words ${textClass()}`.trim()}
class={`whitespace-pre-wrap break-words ${primaryTextClass()}`.trim()}
>
{renderTextWithLinks()}
</div>
</Show>
<Show when={typeof renderedMarkdown() === "string" && renderedMarkdown()}>
<div
ref={(el) => { textContainerEl = el; }}
class={`markdown-content max-w-none ${textClass()}
class={`markdown-content max-w-none ${primaryTextClass()}
[&_strong]:font-semibold
[&_em]:italic
[&_h1]:text-2xl [&_h1]:font-bold [&_h1]:my-4
Expand Down
15 changes: 11 additions & 4 deletions apps/app/src/app/components/session/message-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
} from "../../types";
import {
groupMessageParts,
isUserSkillIndicatorTextPart,
isUserVisiblePart,
summarizeStep,
} from "../../utils";
Expand Down Expand Up @@ -380,9 +381,14 @@ export default function MessageList(props: MessageListProps) {
props.expandedStepIds.has(id) ||
relatedIds.some((relatedId) => props.expandedStepIds.has(relatedId));

const renderablePartsForMessage = (message: MessageWithParts) =>
message.parts.filter((part) => {
if (!props.developerMode && !isUserVisiblePart(part)) {
const renderablePartsForMessage = (message: MessageWithParts) => {
const isUser = (message.info as { role?: string }).role === "user";
return message.parts.filter((part) => {
const passesVisibility =
props.developerMode ||
isUserVisiblePart(part) ||
(isUser && isUserSkillIndicatorTextPart(part));
if (!passesVisibility) {
return false;
}

Expand All @@ -405,6 +411,7 @@ export default function MessageList(props: MessageListProps) {

return props.developerMode;
});
};

const messageBlocks = createMemo<MessageBlockItem[]>(() => {
const startedAt = perfNow();
Expand Down Expand Up @@ -463,7 +470,7 @@ export default function MessageList(props: MessageListProps) {
const groupId = String((message.info as any).id ?? "message");
const attachments = attachmentsForParts(renderableParts);
const nonAttachmentParts = renderableParts.filter((part) => !isAttachmentPart(part));
const groups = groupMessageParts(nonAttachmentParts, groupId);
const groups = groupMessageParts(nonAttachmentParts, groupId, isUser);
const isStepsOnly =
groups.length > 0 && groups.every((group) => group.kind === "steps");
const stepGroups = isStepsOnly
Expand Down
5 changes: 4 additions & 1 deletion apps/app/src/app/pages/session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import type {
import { buildOpenworkWorkspaceBaseUrl } from "../lib/openwork-server";
import { join } from "@tauri-apps/api/path";
import {
isUserSkillIndicatorTextPart,
isUserVisiblePart,
isTauriRuntime,
isWindowsPlatform,
Expand Down Expand Up @@ -519,8 +520,10 @@ export default function SessionView(props: SessionViewProps) {
used += next.length;
};

const isUser = (message.info as { role?: string }).role === "user";
for (const part of message.parts) {
if (!isUserVisiblePart(part)) {
const includeSkillIndicator = isUser && isUserSkillIndicatorTextPart(part);
if (!isUserVisiblePart(part) && !includeSkillIndicator) {
continue;
}
if (part.type === "text") {
Expand Down
28 changes: 26 additions & 2 deletions apps/app/src/app/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,17 @@ export function isVisibleTextPart(part: Part) {
return part.type === "text" && isUserVisiblePart(part);
}

/** Ignored companion text on user turns (e.g. slash-command skill label); not the synthetic template. */
export function isUserSkillIndicatorTextPart(part: Part): boolean {
if (part.type !== "text") return false;
const flags = part as { synthetic?: boolean; ignored?: boolean };
return flags.ignored === true && flags.synthetic !== true;
}

export function isUserTranscriptTextPart(part: Part, isUserMessage: boolean): boolean {
return isVisibleTextPart(part) || (isUserMessage && isUserSkillIndicatorTextPart(part));
}

const EXPLORATION_TOOL_NAMES = new Set(["read", "glob", "grep", "search", "list", "list_files"]);

function isExplorationToolPart(part: Part) {
Expand All @@ -477,7 +488,11 @@ function isExplorationToolPart(part: Part) {
return EXPLORATION_TOOL_NAMES.has(tool);
}

export function groupMessageParts(parts: Part[], messageId: string): MessageGroup[] {
export function groupMessageParts(
parts: Part[],
messageId: string,
isUserMessage = false,
): MessageGroup[] {
const groups: MessageGroup[] = [];
const explorationSteps: Part[] = [];
let textBuffer = "";
Expand Down Expand Up @@ -514,10 +529,19 @@ export function groupMessageParts(parts: Part[], messageId: string): MessageGrou

parts.forEach((part) => {
if (part.type === "text") {
if (!isVisibleTextPart(part)) {
if (!isUserTranscriptTextPart(part, isUserMessage)) {
return;
}
flushExplorationSteps();
if (isUserSkillIndicatorTextPart(part)) {
flushText();
groups.push({
kind: "text",
part,
segment: sawExecution ? "result" : "intent",
});
return;
}
textBuffer += (part as { text?: string }).text ?? "";
return;
}
Expand Down
Loading