feat: add chat page export actions
This commit is contained in:
+127
-11
@@ -38,6 +38,65 @@ function CheckIcon() {
|
||||
);
|
||||
}
|
||||
|
||||
function PageIcon() {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M7 3.5h7.5L19 8v12.5H7z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.8"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path d="M14.5 3.5V8H19M10 12h6M10 15h6M10 18h4" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function ImageIcon() {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<rect x="4" y="5" width="16" height="14" rx="2" stroke="currentColor" strokeWidth="1.8" />
|
||||
<path d="M8 15l3-3 3 3 2-2 3 3" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<circle cx="9" cy="9" r="1.2" fill="currentColor" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function DocumentIcon() {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M6.5 4h8L18 7.5V20H6.5z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.8"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path d="M14.5 4v4H18" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path
|
||||
d="M8.7 11l1 5 1.3-4 1.3 4 1-5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.45"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function LinkIcon() {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M10.5 13.5l3-3M9.5 8.5l1.2-1.2a4 4 0 0 1 5.7 5.7l-1.2 1.2M14.5 15.5l-1.2 1.2a4 4 0 0 1-5.7-5.7l1.2-1.2"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.8"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function ToolActivityIcon({ spinning }: { spinning: boolean }) {
|
||||
return (
|
||||
<span className={`tool-badge-spinner${spinning ? ' is-spinning' : ''}`} aria-hidden="true">
|
||||
@@ -139,9 +198,10 @@ function PublicLinkCopyButton({ url }: { url: string }) {
|
||||
type="button"
|
||||
className={`msg-public-share-link${copied ? ' is-copied' : ''}`}
|
||||
onClick={() => void copy()}
|
||||
title={url}
|
||||
aria-label={copied ? '链接已复制' : '复制链接'}
|
||||
title={copied ? '链接已复制' : '复制链接'}
|
||||
>
|
||||
{copied ? '已复制' : '复制链接'}
|
||||
{copied ? <CheckIcon /> : <LinkIcon />}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -180,6 +240,8 @@ function MessageRow({
|
||||
avatarUrl,
|
||||
onAvatarClick,
|
||||
onSaveAsPage,
|
||||
onDownloadLongImage,
|
||||
onDownloadDocx,
|
||||
saveDisabled,
|
||||
publishUserId,
|
||||
publishUsername,
|
||||
@@ -191,6 +253,8 @@ function MessageRow({
|
||||
avatarUrl: string | null;
|
||||
onAvatarClick?: () => void;
|
||||
onSaveAsPage?: (message: Message) => void;
|
||||
onDownloadLongImage?: (message: Message, publicUrl: string) => void;
|
||||
onDownloadDocx?: (message: Message) => void;
|
||||
saveDisabled?: boolean;
|
||||
publishUserId?: string;
|
||||
publishUsername?: string;
|
||||
@@ -221,6 +285,7 @@ function MessageRow({
|
||||
|
||||
const hasAssistantActions =
|
||||
!isUser && Boolean(message.id && onSaveAsPage && copyText);
|
||||
const hasPageDownloadActions = hasAssistantActions && saveActions.kind === 'page' && Boolean(saveActions.previewUrl);
|
||||
const showActionsToggle = compact && Boolean(copyText);
|
||||
|
||||
return (
|
||||
@@ -280,19 +345,43 @@ function MessageRow({
|
||||
<div className="msg-actions">
|
||||
<CopyButton text={copyText} />
|
||||
{hasAssistantActions && (
|
||||
<>
|
||||
<div className="msg-page-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="msg-save-page"
|
||||
disabled={saveDisabled}
|
||||
onClick={() => onSaveAsPage!(message)}
|
||||
aria-label={saveActions.kind === 'page' ? '保存页面' : '保存为文章'}
|
||||
title={saveActions.kind === 'page' ? '保存页面' : '保存为文章'}
|
||||
>
|
||||
{saveActions.kind === 'page' ? '保存页面' : '保存为文章'}
|
||||
<PageIcon />
|
||||
</button>
|
||||
{saveActions.previewUrl && (
|
||||
<PublicLinkCopyButton url={saveActions.previewUrl} />
|
||||
)}
|
||||
</>
|
||||
{hasPageDownloadActions && (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="msg-save-page"
|
||||
onClick={() => onDownloadLongImage?.(message, saveActions.previewUrl!)}
|
||||
aria-label="下载图片"
|
||||
title="下载图片"
|
||||
>
|
||||
<ImageIcon />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="msg-save-page"
|
||||
onClick={() => onDownloadDocx?.(message)}
|
||||
aria-label="保存文档"
|
||||
title="保存文档"
|
||||
>
|
||||
<DocumentIcon />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -300,19 +389,43 @@ function MessageRow({
|
||||
<div className="msg-actions msg-actions-compact">
|
||||
<CopyButton text={copyText} />
|
||||
{hasAssistantActions && (
|
||||
<>
|
||||
<div className="msg-page-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="msg-save-page"
|
||||
disabled={saveDisabled}
|
||||
onClick={() => onSaveAsPage!(message)}
|
||||
aria-label={saveActions.kind === 'page' ? '保存页面' : '保存为文章'}
|
||||
title={saveActions.kind === 'page' ? '保存页面' : '保存为文章'}
|
||||
>
|
||||
{saveActions.kind === 'page' ? '保存页面' : '保存为文章'}
|
||||
<PageIcon />
|
||||
</button>
|
||||
{saveActions.previewUrl && (
|
||||
<PublicLinkCopyButton url={saveActions.previewUrl} />
|
||||
)}
|
||||
</>
|
||||
{hasPageDownloadActions && (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="msg-save-page"
|
||||
onClick={() => onDownloadLongImage?.(message, saveActions.previewUrl!)}
|
||||
aria-label="下载图片"
|
||||
title="下载图片"
|
||||
>
|
||||
<ImageIcon />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="msg-save-page"
|
||||
onClick={() => onDownloadDocx?.(message)}
|
||||
aria-label="保存文档"
|
||||
title="保存文档"
|
||||
>
|
||||
<DocumentIcon />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -335,18 +448,20 @@ export function MessageList({
|
||||
streaming,
|
||||
onAvatarClick,
|
||||
onSaveAsPage,
|
||||
onDownloadLongImage,
|
||||
onDownloadDocx,
|
||||
publishUserId,
|
||||
publishUsername,
|
||||
sessionId,
|
||||
compact = false,
|
||||
}: {
|
||||
messages: Message[];
|
||||
streaming: boolean;
|
||||
onAvatarClick?: () => void;
|
||||
onSaveAsPage?: (message: Message) => void;
|
||||
onDownloadLongImage?: (message: Message, publicUrl: string) => void;
|
||||
onDownloadDocx?: (message: Message) => void;
|
||||
publishUserId?: string;
|
||||
publishUsername?: string;
|
||||
sessionId?: string;
|
||||
compact?: boolean;
|
||||
}) {
|
||||
const { avatarUrl } = useUserAvatar();
|
||||
@@ -381,10 +496,11 @@ export function MessageList({
|
||||
avatarUrl={avatarUrl}
|
||||
onAvatarClick={onAvatarClick}
|
||||
onSaveAsPage={onSaveAsPage}
|
||||
onDownloadLongImage={onDownloadLongImage}
|
||||
onDownloadDocx={onDownloadDocx}
|
||||
saveDisabled={streaming}
|
||||
publishUserId={publishUserId}
|
||||
publishUsername={publishUsername}
|
||||
sessionId={sessionId}
|
||||
compact={compact}
|
||||
activeToolMessage={message === activeToolMessage}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user