diff --git a/packages/app/src/components/agent-stream-view.tsx b/packages/app/src/components/agent-stream-view.tsx
index 5c84f7843..e5399b03f 100644
--- a/packages/app/src/components/agent-stream-view.tsx
+++ b/packages/app/src/components/agent-stream-view.tsx
@@ -396,14 +396,23 @@ export function AgentStreamView({
}
const gapBelow = getGapBelow(item, index);
+ const nextItem = flatListData[index - 1];
+ const isEndOfAssistantTurn =
+ item.kind === "assistant_message" &&
+ (nextItem?.kind === "user_message" ||
+ (nextItem === undefined && agent.status !== "running"));
+ const getTurnContent = () => collectAssistantTurnContent(flatListData, index);
return (
{content}
+ {isEndOfAssistantTurn ? (
+
+ ) : null}
);
},
- [getGapBelow, renderStreamItemContent]
+ [getGapBelow, renderStreamItemContent, flatListData, agent.status]
);
const pendingPermissionItems = useMemo(
@@ -483,18 +492,7 @@ export function AgentStreamView({
}, [agentId, pendingPermissionItems.length, streamHead, streamItems]);
const showWorkingIndicator = agent.status === "running";
- const newestItem = flatListData[0] ?? null;
- const latestTurnContent = useMemo(() => {
- if (flatListData.length === 0) {
- return "";
- }
- return collectAssistantTurnContent(flatListData, 0);
- }, [flatListData]);
- const showCopyButton =
- agent.status !== "running" &&
- newestItem?.kind === "assistant_message" &&
- latestTurnContent.trim().length > 0;
- const showBottomBar = showWorkingIndicator || showCopyButton || isVoiceMode;
+ const showBottomBar = showWorkingIndicator || isVoiceMode;
const listHeaderComponent = useMemo(() => {
const hasPermissions = pendingPermissionItems.length > 0;
@@ -504,11 +502,7 @@ export function AgentStreamView({
return null;
}
- const leftContent = showWorkingIndicator ? (
-
- ) : showCopyButton ? (
- latestTurnContent} />
- ) : null;
+ const leftContent = showWorkingIndicator ? : null;
return (
@@ -564,8 +558,6 @@ export function AgentStreamView({
renderStreamItemContent,
tightGap,
showBottomBar,
- showCopyButton,
- latestTurnContent,
isVoiceMode,
]);
@@ -574,14 +566,12 @@ export function AgentStreamView({
pendingPermissionCount: pendingPermissionItems.length,
showWorkingIndicator,
showBottomBar,
- showCopyButton,
isVoiceMode,
}),
[
pendingPermissionItems.length,
showWorkingIndicator,
showBottomBar,
- showCopyButton,
isVoiceMode,
]
);
diff --git a/packages/app/src/components/message.tsx b/packages/app/src/components/message.tsx
index 7c9d57ade..add118136 100644
--- a/packages/app/src/components/message.tsx
+++ b/packages/app/src/components/message.tsx
@@ -99,6 +99,10 @@ const userMessageStylesheet = StyleSheet.create((theme) => ({
justifyContent: "flex-end",
paddingHorizontal: theme.spacing[2],
},
+ content: {
+ alignItems: "flex-end",
+ maxWidth: "100%",
+ },
containerSpacing: {
marginBottom: theme.spacing[1],
},
@@ -123,21 +127,16 @@ const userMessageStylesheet = StyleSheet.create((theme) => ({
lineHeight: 22,
overflowWrap: "anywhere",
},
- bubblePressed: {
- opacity: 0.85,
- },
- copiedTagContainer: {
- marginTop: theme.spacing[1],
- marginRight: theme.spacing[4],
+ copyButton: {
alignSelf: "flex-end",
- backgroundColor: theme.colors.surface2,
- borderRadius: theme.borderRadius.base,
- paddingHorizontal: theme.spacing[2],
- paddingVertical: 4,
+ padding: theme.spacing[1],
+ marginTop: theme.spacing[2],
},
- copiedTagText: {
- color: theme.colors.foreground,
- fontSize: theme.fontSize.xs,
+ copyButtonHidden: {
+ opacity: 0,
+ },
+ copyButtonVisible: {
+ opacity: 1,
},
}));
@@ -150,34 +149,6 @@ export const UserMessage = memo(function UserMessage({
}: UserMessageProps) {
const resolvedDisableOuterSpacing =
useDisableOuterSpacing(disableOuterSpacing);
- const [copied, setCopied] = useState(false);
- const copyTimeoutRef = useRef | null>(null);
-
- const handleLongPress = useCallback(async () => {
- if (!message) {
- return;
- }
-
- await Clipboard.setStringAsync(message);
- setCopied(true);
-
- if (copyTimeoutRef.current) {
- clearTimeout(copyTimeoutRef.current);
- }
-
- copyTimeoutRef.current = setTimeout(() => {
- setCopied(false);
- copyTimeoutRef.current = null;
- }, 1500);
- }, [message]);
-
- useEffect(() => {
- return () => {
- if (copyTimeoutRef.current) {
- clearTimeout(copyTimeoutRef.current);
- }
- };
- }, []);
return (
- [
- userMessageStylesheet.bubble,
- pressed ? userMessageStylesheet.bubblePressed : null,
- ]}
- >
- {message}
+
+ {({ hovered }) => {
+ const showCopyButton = Platform.OS !== "web" || hovered;
+ return (
+ <>
+
+
+ {message}
+
+
+ message}
+ containerStyle={[
+ userMessageStylesheet.copyButton,
+ showCopyButton
+ ? userMessageStylesheet.copyButtonVisible
+ : userMessageStylesheet.copyButtonHidden,
+ ]}
+ accessibilityLabel="Copy message"
+ />
+ >
+ );
+ }}
- {copied && (
-
-
- Copied to clipboard
-
-
- )}
);
});
@@ -225,7 +200,7 @@ interface AssistantMessageProps {
export const assistantMessageStylesheet = StyleSheet.create((theme) => ({
container: {
- paddingHorizontal: theme.spacing[4],
+ paddingHorizontal: theme.spacing[2],
paddingVertical: theme.spacing[3],
},
containerSpacing: {
@@ -261,6 +236,7 @@ const turnCopyButtonStylesheet = StyleSheet.create((theme) => ({
container: {
alignSelf: "flex-start",
padding: theme.spacing[2],
+ paddingTop: 0,
},
iconColor: {
color: theme.colors.foregroundMuted,
@@ -272,10 +248,16 @@ const turnCopyButtonStylesheet = StyleSheet.create((theme) => ({
interface TurnCopyButtonProps {
getContent: () => string;
+ containerStyle?: StyleProp;
+ accessibilityLabel?: string;
+ copiedAccessibilityLabel?: string;
}
export const TurnCopyButton = memo(function TurnCopyButton({
getContent,
+ containerStyle,
+ accessibilityLabel,
+ copiedAccessibilityLabel,
}: TurnCopyButtonProps) {
const [copied, setCopied] = useState(false);
const copyTimeoutRef = useRef | null>(null);
@@ -310,9 +292,13 @@ export const TurnCopyButton = memo(function TurnCopyButton({
return (
{({ hovered }) => {
const iconColor = hovered