feat: add chat page export actions
This commit is contained in:
@@ -2,6 +2,9 @@ import { ChangeEvent, useCallback, useEffect, useLayoutEffect, useRef, useState,
|
||||
import { useNetworkStatus } from '../hooks/useNetworkStatus';
|
||||
import { openAvatarPicker } from '../utils/userAvatar';
|
||||
import { CHAT_SKILL_OPTIONS, filterChatSkills } from '../utils/chatSkills';
|
||||
import { getMessageSaveActions } from '../utils/messageSave';
|
||||
import { getDisplayText } from '../utils/message';
|
||||
import { downloadChatMessageDocx } from '../api/client';
|
||||
import {
|
||||
CHAT_IMAGE_UPLOAD_MAX_COUNT,
|
||||
CHAT_IMAGE_UPLOAD_MAX_INPUT_BYTES,
|
||||
@@ -10,6 +13,7 @@ import {
|
||||
import { AvatarPicker } from './AvatarPicker';
|
||||
import { ChatSkillPicker } from './ChatSkillPicker';
|
||||
import { ChatLoadingSpinner } from './ChatLoadingSpinner';
|
||||
import { ChatSharePreviewModal } from './ChatSharePreviewModal';
|
||||
import { MessageList } from './MessageList';
|
||||
import { PageSaveDialog } from './PageSaveDialog';
|
||||
import { VoiceInputButton } from './VoiceInputButton';
|
||||
@@ -76,6 +80,32 @@ function FileIcon() {
|
||||
);
|
||||
}
|
||||
|
||||
function appendLongImageDownloadParam(url: string) {
|
||||
const next = new URL(url, window.location.href);
|
||||
next.searchParams.set('download', 'long-image');
|
||||
return next.toString();
|
||||
}
|
||||
|
||||
function triggerUrlDownload(url: string) {
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = '';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
}
|
||||
|
||||
function downloadBlob(blob: Blob, filename: string) {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
window.setTimeout(() => URL.revokeObjectURL(url), 60_000);
|
||||
}
|
||||
|
||||
export function ChatPanel({
|
||||
variant,
|
||||
user,
|
||||
@@ -123,6 +153,7 @@ export function ChatPanel({
|
||||
const [input, setInput] = useState('');
|
||||
const [voiceRecording, setVoiceRecording] = useState(false);
|
||||
const [pageSource, setPageSource] = useState<Message | null>(null);
|
||||
const [sharePreviewSource, setSharePreviewSource] = useState<Message | null>(null);
|
||||
const [voiceNotice, setVoiceNotice] = useState<string | null>(null);
|
||||
const [pendingImages, setPendingImages] = useState<PendingChatImage[]>([]);
|
||||
const [uploadingImage, setUploadingImage] = useState(false);
|
||||
@@ -531,6 +562,35 @@ export function ChatPanel({
|
||||
});
|
||||
};
|
||||
|
||||
const openSaveActions = (message: Message) => {
|
||||
const actions = getMessageSaveActions(getDisplayText(message), {
|
||||
userId: user?.id,
|
||||
username: user?.username,
|
||||
});
|
||||
if (actions.kind === 'page') {
|
||||
setSharePreviewSource(message);
|
||||
return;
|
||||
}
|
||||
setPageSource(message);
|
||||
};
|
||||
|
||||
const downloadLongImage = (_message: Message, publicUrl: string) => {
|
||||
triggerUrlDownload(appendLongImageDownloadParam(publicUrl));
|
||||
};
|
||||
|
||||
const downloadDocx = async (message: Message) => {
|
||||
if (!session?.id || !message.id) return;
|
||||
try {
|
||||
const result = await downloadChatMessageDocx({
|
||||
sessionId: session.id,
|
||||
messageId: message.id,
|
||||
});
|
||||
downloadBlob(result.blob, result.filename);
|
||||
} catch (err) {
|
||||
setVoiceNotice(err instanceof Error ? err.message : '文档下载失败,请重试');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<main
|
||||
@@ -549,10 +609,11 @@ export function ChatPanel({
|
||||
messages={messages}
|
||||
streaming={chatState === 'streaming'}
|
||||
onAvatarClick={compact ? undefined : openAvatarPicker}
|
||||
onSaveAsPage={(message) => setPageSource(message)}
|
||||
onSaveAsPage={openSaveActions}
|
||||
onDownloadLongImage={downloadLongImage}
|
||||
onDownloadDocx={(message) => void downloadDocx(message)}
|
||||
publishUserId={user?.id}
|
||||
publishUsername={user?.username}
|
||||
sessionId={session?.id}
|
||||
compact={compact}
|
||||
/>
|
||||
{!compact && (
|
||||
@@ -587,11 +648,24 @@ export function ChatPanel({
|
||||
onClose={() => setPageSource(null)}
|
||||
onSaved={(result) => {
|
||||
setPageSource(null);
|
||||
setSharePreviewSource(null);
|
||||
onPageSaved?.(result);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{sharePreviewSource?.id && session?.id && (
|
||||
<ChatSharePreviewModal
|
||||
sessionId={session.id}
|
||||
messageId={sharePreviewSource.id}
|
||||
onClose={() => setSharePreviewSource(null)}
|
||||
onSave={() => {
|
||||
setPageSource(sharePreviewSource);
|
||||
setSharePreviewSource(null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<footer className={compact ? 'space-chat-panel-footer' : `footer${showHomeWelcome ? ' footer-home' : ''}`}>
|
||||
{connectStatusText && (
|
||||
<div className="chat-connect-status" role="status" aria-live="polite">
|
||||
|
||||
Reference in New Issue
Block a user