feat(chat): add one-click Plaza publish for HTML page messages.
Speed up the quick-plaza path with session snapshot reads, publish timeouts, and a modal fix so parent re-renders no longer abort in-flight requests. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -83,6 +83,22 @@ function DocumentIcon() {
|
||||
);
|
||||
}
|
||||
|
||||
function PlazaIcon() {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true" className="icon-plaza">
|
||||
<rect x="4" y="4" width="7" height="7" rx="1.6" stroke="currentColor" strokeWidth="1.7" />
|
||||
<rect x="13" y="4" width="7" height="7" rx="1.6" stroke="currentColor" strokeWidth="1.7" />
|
||||
<rect x="4" y="13" width="7" height="7" rx="1.6" stroke="currentColor" strokeWidth="1.7" />
|
||||
<path
|
||||
d="M13 16.5h7M16.5 13v7"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.7"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function LinkIcon() {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
@@ -240,6 +256,7 @@ function MessageRow({
|
||||
avatarUrl,
|
||||
onAvatarClick,
|
||||
onSaveAsPage,
|
||||
onShareToPlaza,
|
||||
onDownloadLongImage,
|
||||
onDownloadDocx,
|
||||
saveDisabled,
|
||||
@@ -253,6 +270,7 @@ function MessageRow({
|
||||
avatarUrl: string | null;
|
||||
onAvatarClick?: () => void;
|
||||
onSaveAsPage?: (message: Message) => void;
|
||||
onShareToPlaza?: (message: Message) => void;
|
||||
onDownloadLongImage?: (message: Message, publicUrl: string) => void;
|
||||
onDownloadDocx?: (message: Message) => void;
|
||||
saveDisabled?: boolean;
|
||||
@@ -286,8 +304,59 @@ function MessageRow({
|
||||
const hasAssistantActions =
|
||||
!isUser && Boolean(message.id && onSaveAsPage && copyText);
|
||||
const hasPageDownloadActions = hasAssistantActions && saveActions.kind === 'page' && Boolean(saveActions.previewUrl);
|
||||
const hasPlazaAction = hasPageDownloadActions && Boolean(onShareToPlaza);
|
||||
const showActionsToggle = compact && Boolean(copyText);
|
||||
|
||||
const renderPageActions = () => (
|
||||
<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' ? '保存页面' : '保存为文章'}
|
||||
>
|
||||
<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>
|
||||
{hasPlazaAction && (
|
||||
<button
|
||||
type="button"
|
||||
className="msg-save-page icon-plaza"
|
||||
disabled={saveDisabled}
|
||||
onClick={() => onShareToPlaza!(message)}
|
||||
aria-label="发布到 Plaza"
|
||||
title="发布到 Plaza"
|
||||
>
|
||||
<PlazaIcon />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="msg-save-page"
|
||||
onClick={() => onDownloadDocx?.(message)}
|
||||
aria-label="保存文档"
|
||||
title="保存文档"
|
||||
>
|
||||
<DocumentIcon />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`msg-row ${isUser ? 'msg-row-user' : 'msg-row-assistant'}`}>
|
||||
{!isUser && <TKMindAvatar />}
|
||||
@@ -344,89 +413,13 @@ function MessageRow({
|
||||
{copyText && !compact && (
|
||||
<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' ? '保存页面' : '保存为文章'}
|
||||
>
|
||||
<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>
|
||||
)}
|
||||
{hasAssistantActions && renderPageActions()}
|
||||
</div>
|
||||
)}
|
||||
{copyText && compact && actionsOpen && (
|
||||
<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' ? '保存页面' : '保存为文章'}
|
||||
>
|
||||
<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>
|
||||
)}
|
||||
{hasAssistantActions && renderPageActions()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -448,6 +441,7 @@ export function MessageList({
|
||||
streaming,
|
||||
onAvatarClick,
|
||||
onSaveAsPage,
|
||||
onShareToPlaza,
|
||||
onDownloadLongImage,
|
||||
onDownloadDocx,
|
||||
publishUserId,
|
||||
@@ -458,6 +452,7 @@ export function MessageList({
|
||||
streaming: boolean;
|
||||
onAvatarClick?: () => void;
|
||||
onSaveAsPage?: (message: Message) => void;
|
||||
onShareToPlaza?: (message: Message) => void;
|
||||
onDownloadLongImage?: (message: Message, publicUrl: string) => void;
|
||||
onDownloadDocx?: (message: Message) => void;
|
||||
publishUserId?: string;
|
||||
@@ -496,6 +491,7 @@ export function MessageList({
|
||||
avatarUrl={avatarUrl}
|
||||
onAvatarClick={onAvatarClick}
|
||||
onSaveAsPage={onSaveAsPage}
|
||||
onShareToPlaza={onShareToPlaza}
|
||||
onDownloadLongImage={onDownloadLongImage}
|
||||
onDownloadDocx={onDownloadDocx}
|
||||
saveDisabled={streaming}
|
||||
|
||||
Reference in New Issue
Block a user