feat: release chat and mindspace UI updates
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { buildChatSavePreviewFrameUrl, quickShareFromChat } from '../api/client';
|
||||
|
||||
type SharePhase =
|
||||
| { kind: 'idle' }
|
||||
| { kind: 'loading' }
|
||||
| { kind: 'done'; publicUrl: string }
|
||||
| { kind: 'error'; message: string };
|
||||
|
||||
export function ChatSharePreviewModal({
|
||||
sessionId,
|
||||
messageId,
|
||||
selectedLinkIndex = 0,
|
||||
onClose,
|
||||
onSave,
|
||||
}: {
|
||||
sessionId: string;
|
||||
messageId: string;
|
||||
selectedLinkIndex?: number;
|
||||
onClose: () => void;
|
||||
onSave?: () => void;
|
||||
}) {
|
||||
const [phase, setPhase] = useState<SharePhase>({ kind: 'idle' });
|
||||
const [copied, setCopied] = useState(false);
|
||||
const copyTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const previewUrl = buildChatSavePreviewFrameUrl({ sessionId, messageId, selectedLinkIndex });
|
||||
|
||||
useEffect(() => {
|
||||
const prev = document.body.style.overflow;
|
||||
document.body.style.overflow = 'hidden';
|
||||
return () => { document.body.style.overflow = prev; };
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
|
||||
window.addEventListener('keydown', onKey);
|
||||
return () => window.removeEventListener('keydown', onKey);
|
||||
}, [onClose]);
|
||||
|
||||
useEffect(() => () => { if (copyTimer.current) clearTimeout(copyTimer.current); }, []);
|
||||
|
||||
const share = async () => {
|
||||
if (phase.kind === 'loading') return;
|
||||
setPhase({ kind: 'loading' });
|
||||
try {
|
||||
const result = await quickShareFromChat({ sessionId, messageId, selectedLinkIndex });
|
||||
setPhase({ kind: 'done', publicUrl: result.publicUrl });
|
||||
} catch (err) {
|
||||
setPhase({ kind: 'error', message: err instanceof Error ? err.message : '公开分享失败,请重试' });
|
||||
}
|
||||
};
|
||||
|
||||
const copy = async (url: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(url);
|
||||
setCopied(true);
|
||||
if (copyTimer.current) clearTimeout(copyTimer.current);
|
||||
copyTimer.current = setTimeout(() => setCopied(false), 2000);
|
||||
} catch { /* ignore */ }
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
onClose();
|
||||
onSave?.();
|
||||
};
|
||||
|
||||
return createPortal(
|
||||
<div className="chat-share-preview-overlay" role="dialog" aria-modal="true" aria-label="页面预览">
|
||||
<div className="chat-share-preview-header">
|
||||
<span className="chat-share-preview-title">页面预览</span>
|
||||
<button type="button" className="chat-share-preview-close" onClick={onClose} aria-label="关闭">✕</button>
|
||||
</div>
|
||||
<div className="chat-share-preview-body">
|
||||
<iframe src={previewUrl} title="页面预览" sandbox="allow-same-origin allow-scripts allow-popups" className="chat-share-preview-frame" />
|
||||
|
||||
<div className="chat-share-side-actions">
|
||||
{phase.kind === 'done' ? (
|
||||
<div className="chat-share-result-card">
|
||||
<p className="chat-share-result-label">✅ 已公开!</p>
|
||||
<button type="button" className="chat-share-result-copy" onClick={() => void copy(phase.publicUrl)}>
|
||||
{copied ? '已复制 ✓' : '复制链接'}
|
||||
</button>
|
||||
<a href={phase.publicUrl} target="_blank" rel="noreferrer" className="chat-share-result-open">
|
||||
打开页面 →
|
||||
</a>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className={`chat-share-side-btn chat-share-side-btn-primary${phase.kind === 'loading' ? ' is-loading' : ''}`}
|
||||
disabled={phase.kind === 'loading'}
|
||||
onClick={() => void share()}
|
||||
title="将页面转为公开链接,私有图片自动转为公开地址"
|
||||
>
|
||||
{phase.kind === 'loading' ? '处理中…' : '🌐 公开分享'}
|
||||
</button>
|
||||
)}
|
||||
{phase.kind === 'error' && <p className="chat-share-side-error">{phase.message}</p>}
|
||||
{onSave && (
|
||||
<button type="button" className="chat-share-side-btn" onClick={handleSave}>
|
||||
📄 保存页面
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user