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
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,7 @@ export function ActionSelector({
tabIndex={0}
p="3"
onClick={(e) => {
if (
e.target instanceof HTMLElement &&
e.target.closest("[contenteditable]")
) {
if (e.target instanceof HTMLInputElement) {
return;
}
containerRef.current?.focus();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Box, Text } from "@radix-ui/themes";
import { useCallback, useEffect } from "react";
import { useCallback, useEffect, useRef } from "react";

interface InlineEditableTextProps {
value: string;
Expand All @@ -9,7 +8,6 @@ interface InlineEditableTextProps {
onNavigateDown: () => void;
onEscape: () => void;
onSubmit: () => void;
inputRef: React.RefObject<HTMLSpanElement | null>;
}

export function InlineEditableText({
Expand All @@ -20,33 +18,15 @@ export function InlineEditableText({
onNavigateDown,
onEscape,
onSubmit,
inputRef,
}: InlineEditableTextProps) {
useEffect(() => {
if (inputRef.current) {
inputRef.current.textContent = value || "";
inputRef.current.focus();
if (value) {
const range = document.createRange();
range.selectNodeContents(inputRef.current);
range.collapse(false);
const sel = window.getSelection();
sel?.removeAllRanges();
sel?.addRange(range);
}
}
}, [inputRef, value]);
const nativeInputRef = useRef<HTMLInputElement>(null);

const handleInput = useCallback(
(e: React.FormEvent<HTMLSpanElement>) => {
const text = e.currentTarget.textContent ?? "";
onChange(text);
},
[onChange],
);
useEffect(() => {
nativeInputRef.current?.focus();
}, []);

const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLSpanElement>) => {
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Escape") {
e.preventDefault();
onEscape();
Expand All @@ -65,54 +45,23 @@ export function InlineEditableText({
);

return (
<Box
<input
ref={nativeInputRef}
type="text"
value={value}
placeholder={placeholder}
onChange={(e) => onChange(e.target.value)}
onKeyDown={handleKeyDown}
onClick={(e) => e.stopPropagation()}
className="text-gray-12 placeholder:text-gray-10"
style={{
display: "inline-grid",
all: "unset",
fontSize: "var(--font-size-1)",
lineHeight: "var(--line-height-1)",
fontWeight: 500,
minWidth: "200px",
display: "inline-block",
}}
>
{!value && (
<Text
size="1"
weight="medium"
className="text-gray-10"
style={{
gridRow: 1,
gridColumn: 1,
pointerEvents: "none",
userSelect: "none",
whiteSpace: "pre-wrap",
wordBreak: "break-word",
}}
>
{placeholder}
</Text>
)}
<Text
asChild
size="1"
weight="medium"
className={value ? "text-gray-12" : ""}
>
{/* biome-ignore lint/a11y/useSemanticElements: contentEditable span needed for inline editing UX */}
<span
ref={inputRef}
role="textbox"
tabIndex={0}
contentEditable
suppressContentEditableWarning
onClick={(e) => e.stopPropagation()}
onInput={handleInput}
onKeyDown={handleKeyDown}
style={{
gridRow: 1,
gridColumn: 1,
outline: "none",
whiteSpace: "pre-wrap",
wordBreak: "break-word",
}}
/>
</Text>
</Box>
/>
);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Box, Checkbox, Flex, Text } from "@radix-ui/themes";
import { compactHomePath } from "@utils/path";
import { useRef } from "react";
import { isOtherOption, isSubmitOption } from "./constants";
import { InlineEditableText } from "./InlineEditableText";
import type { SelectorOption } from "./types";
Expand Down Expand Up @@ -56,8 +55,6 @@ export function OptionRow({
onClick,
onMouseEnter,
}: OptionRowProps) {
const inputRef = useRef<HTMLSpanElement>(null);

if (isSubmitOption(option.id)) {
return (
<Flex
Expand Down Expand Up @@ -102,7 +99,6 @@ export function OptionRow({
onNavigateDown={onNavigateDown}
onEscape={onEscape}
onSubmit={onInlineSubmit}
inputRef={inputRef}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
} from "@agentclientprotocol/sdk";
import {
type QueuedMessage,
sessionStoreSetters,
usePendingPermissionsForTask,
useQueuedMessagesForTask,
} from "@features/sessions/stores/sessionStore";
Expand Down Expand Up @@ -170,10 +171,23 @@ export function ConversationView({
case "user_shell_execute":
return <UserShellExecuteView item={item} />;
case "queued":
return <QueuedMessageView message={item.message} />;
return (
<QueuedMessageView
message={item.message}
onRemove={
taskId
? () =>
sessionStoreSetters.removeQueuedMessage(
taskId,
item.message.id,
)
: undefined
}
/>
);
}
},
[repoPath],
[repoPath, taskId],
);

const getItemKey = useCallback((item: VirtualizedItem) => item.id, []);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,38 @@
import { MarkdownRenderer } from "@features/editor/components/MarkdownRenderer";
import type { QueuedMessage } from "@features/sessions/stores/sessionStore";
import { Clock } from "@phosphor-icons/react";
import { Box, Flex, Text } from "@radix-ui/themes";
import { Clock, X } from "@phosphor-icons/react";
import { Box, Flex, IconButton, Text } from "@radix-ui/themes";

interface QueuedMessageViewProps {
message: QueuedMessage;
onRemove?: () => void;
}

export function QueuedMessageView({ message }: QueuedMessageViewProps) {
export function QueuedMessageView({
message,
onRemove,
}: QueuedMessageViewProps) {
return (
<Box
className="border-l-2 border-dashed bg-gray-2 py-2 pr-2 pl-3 opacity-70"
className="group relative border-l-2 border-dashed bg-gray-2 py-2 pr-2 pl-3 opacity-70"
style={{ borderColor: "var(--gray-8)" }}
>
<Box className="font-medium [&>*:last-child]:mb-0">
<MarkdownRenderer content={message.content} />
</Box>
<Flex justify="between" align="start" gap="2">
<Box className="min-w-0 flex-1 font-medium [&>*:last-child]:mb-0">
<MarkdownRenderer content={message.content} />
</Box>
{onRemove && (
<IconButton
size="1"
variant="ghost"
color="gray"
className="shrink-0 opacity-0 group-hover:opacity-100"
onClick={onRemove}
>
<X size={12} />
</IconButton>
)}
</Flex>
<Flex align="center" gap="1" mt="1">
<Clock size={12} className="text-gray-9" />
<Text size="1" color="gray">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const mockSessionStoreSetters = vi.hoisted(() => ({
updateSession: vi.fn(),
appendEvents: vi.fn(),
enqueueMessage: vi.fn(),
removeQueuedMessage: vi.fn(),
clearMessageQueue: vi.fn(),
dequeueMessagesAsText: vi.fn(() => null),
setPendingPermissions: vi.fn(),
Expand Down
13 changes: 13 additions & 0 deletions apps/twig/src/renderer/features/sessions/stores/sessionStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,19 @@ export const sessionStoreSetters = {
});
},

removeQueuedMessage: (taskId: string, messageId: string) => {
useSessionStore.setState((state) => {
const taskRunId = state.taskIdIndex[taskId];
if (!taskRunId) return;
const session = state.sessions[taskRunId];
if (session) {
session.messageQueue = session.messageQueue.filter(
(msg) => msg.id !== messageId,
);
}
});
},

clearMessageQueue: (taskId: string) => {
useSessionStore.setState((state) => {
const taskRunId = state.taskIdIndex[taskId];
Expand Down
Loading