Compare commits

...

2 Commits

Author SHA1 Message Date
simosmik
d19b1e949f Release 1.21.0 2026-02-27 15:41:20 +00:00
Xì Gà
b359c51527 feat: add copy icon for user messages (#449)
* feat: add copy icon for user messages

Expose a copy control on user chat bubbles so previous content can be reused quickly.

* fix: Copy control is effectively hidden on touch devices

* fix: copyTextToClipboard doesn't need timer

---------

Co-authored-by: dev <dev@host.local>
Co-authored-by: Haileyesus <118998054+blackmammoth@users.noreply.github.com>
2026-02-27 18:28:10 +03:00
8 changed files with 79 additions and 7 deletions

View File

@@ -3,6 +3,23 @@
All notable changes to CloudCLI UI will be documented in this file.
## [1.21.0](https://github.com/siteboon/claudecodeui/compare/v1.20.1...v1.21.0) (2026-02-27)
### New Features
* add copy icon for user messages ([#449](https://github.com/siteboon/claudecodeui/issues/449)) ([b359c51](https://github.com/siteboon/claudecodeui/commit/b359c515277b4266fde2fb9a29b5356949c07c4f))
* Google's gemini-cli integration ([#422](https://github.com/siteboon/claudecodeui/issues/422)) ([a367edd](https://github.com/siteboon/claudecodeui/commit/a367edd51578608b3281373cb4a95169dbf17f89))
* persist active tab across reloads via localStorage ([#414](https://github.com/siteboon/claudecodeui/issues/414)) ([e3b6892](https://github.com/siteboon/claudecodeui/commit/e3b689214f11d549ffe1b3a347476d58f25c5aca)), closes [#387](https://github.com/siteboon/claudecodeui/issues/387)
### Bug Fixes
* add support for Codex in the shell ([#424](https://github.com/siteboon/claudecodeui/issues/424)) ([23801e9](https://github.com/siteboon/claudecodeui/commit/23801e9cc15d2b8d1bfc6e39aee2fae93226d1ad))
### Maintenance
* upgrade @anthropic-ai/claude-agent-sdk to version 0.2.59 and add model usage logging ([#446](https://github.com/siteboon/claudecodeui/issues/446)) ([917c353](https://github.com/siteboon/claudecodeui/commit/917c353115653ee288bf97be01f62fad24123cbc))
* upgrade better-sqlite to latest version to support node 25 ([#445](https://github.com/siteboon/claudecodeui/issues/445)) ([4ab94fc](https://github.com/siteboon/claudecodeui/commit/4ab94fce4257e1e20370fa83fa4c0f6fadbb8a2b))
## [1.20.1](https://github.com/siteboon/claudecodeui/compare/v1.19.1...v1.20.1) (2026-02-23)
### New Features

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@siteboon/claude-code-ui",
"version": "1.20.1",
"version": "1.21.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@siteboon/claude-code-ui",
"version": "1.20.1",
"version": "1.21.0",
"hasInstallScript": true,
"license": "GPL-3.0",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@siteboon/claude-code-ui",
"version": "1.20.1",
"version": "1.21.0",
"description": "A web-based UI for Claude Code CLI",
"type": "module",
"main": "server/index.js",
@@ -118,4 +118,4 @@
"typescript": "^5.9.3",
"vite": "^7.0.4"
}
}
}

View File

@@ -10,6 +10,7 @@ import type {
import { Markdown } from './Markdown';
import { formatUsageLimitText } from '../../utils/chatFormatting';
import { getClaudePermissionSuggestion } from '../../utils/chatPermissions';
import { copyTextToClipboard } from '../../../../utils/clipboard';
import type { Project } from '../../../../types/app';
import { ToolRenderer, shouldHideToolResult } from '../../tools';
@@ -53,6 +54,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
const [isExpanded, setIsExpanded] = React.useState(false);
const permissionSuggestion = getClaudePermissionSuggestion(message, provider);
const [permissionGrantState, setPermissionGrantState] = React.useState<PermissionGrantState>('idle');
const [messageCopied, setMessageCopied] = React.useState(false);
React.useEffect(() => {
@@ -100,7 +102,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
{message.type === 'user' ? (
/* User message bubble on the right */
<div className="flex items-end space-x-0 sm:space-x-3 w-full sm:w-auto sm:max-w-[85%] md:max-w-md lg:max-w-lg xl:max-w-xl">
<div className="bg-blue-600 text-white rounded-2xl rounded-br-md px-3 sm:px-4 py-2 shadow-sm flex-1 sm:flex-initial">
<div className="bg-blue-600 text-white rounded-2xl rounded-br-md px-3 sm:px-4 py-2 shadow-sm flex-1 sm:flex-initial group">
<div className="text-sm whitespace-pre-wrap break-words">
{message.content}
</div>
@@ -117,8 +119,45 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
))}
</div>
)}
<div className="text-xs text-blue-100 mt-1 text-right">
{formattedTime}
<div className="flex items-center justify-end gap-1 mt-1 text-xs text-blue-100">
<button
type="button"
onClick={() => {
const text = String(message.content || '');
if (!text) return;
copyTextToClipboard(text).then((success) => {
if (!success) return;
setMessageCopied(true);
});
}}
title={messageCopied ? t('copyMessage.copied') : t('copyMessage.copy')}
aria-label={messageCopied ? t('copyMessage.copied') : t('copyMessage.copy')}
>
{messageCopied ? (
<svg className="w-3.5 h-3.5" viewBox="0 0 20 20" fill="currentColor">
<path
fillRule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clipRule="evenodd"
/>
</svg>
) : (
<svg
className="w-3.5 h-3.5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"></path>
</svg>
)}
</button>
<span>{formattedTime}</span>
</div>
</div>
{!isGrouped && (

View File

@@ -4,6 +4,10 @@
"copied": "Copied",
"copyCode": "Copy code"
},
"copyMessage": {
"copy": "Copy message",
"copied": "Message copied"
},
"messageTypes": {
"user": "U",
"error": "Error",

View File

@@ -4,6 +4,10 @@
"copied": "コピーしました",
"copyCode": "コードをコピー"
},
"copyMessage": {
"copy": "メッセージをコピー",
"copied": "メッセージをコピーしました"
},
"messageTypes": {
"user": "U",
"error": "エラー",

View File

@@ -4,6 +4,10 @@
"copied": "복사됨",
"copyCode": "코드 복사"
},
"copyMessage": {
"copy": "메시지 복사",
"copied": "메시지 복사됨"
},
"messageTypes": {
"user": "U",
"error": "오류",

View File

@@ -4,6 +4,10 @@
"copied": "已复制",
"copyCode": "复制代码"
},
"copyMessage": {
"copy": "复制消息",
"copied": "消息已复制"
},
"messageTypes": {
"user": "U",
"error": "错误",