main
mula.liu 2026-03-11 02:40:59 +08:00
parent 82ce7d7373
commit 76a2ab49fe
1 changed files with 88 additions and 1 deletions

View File

@ -452,6 +452,63 @@ function normalizeDashboardAttachmentPath(path: string): string {
return v.replace(/^\/+/, '');
}
const COMPOSER_DRAFT_STORAGE_PREFIX = 'nanobot-dashboard-composer-draft:v1:';
interface ComposerDraftStorage {
command: string;
attachments: string[];
updated_at_ms: number;
}
function getComposerDraftStorageKey(botId: string): string {
return `${COMPOSER_DRAFT_STORAGE_PREFIX}${String(botId || '').trim()}`;
}
function loadComposerDraft(botId: string): ComposerDraftStorage | null {
const id = String(botId || '').trim();
if (!id || typeof window === 'undefined') return null;
try {
const raw = window.localStorage.getItem(getComposerDraftStorageKey(id));
if (!raw) return null;
const parsed = JSON.parse(raw) as Partial<ComposerDraftStorage> | null;
const command = String(parsed?.command || '');
const attachments = normalizeAttachmentPaths(parsed?.attachments)
.map(normalizeDashboardAttachmentPath)
.filter(Boolean);
return {
command,
attachments,
updated_at_ms: Number(parsed?.updated_at_ms || Date.now()),
};
} catch {
return null;
}
}
function persistComposerDraft(botId: string, commandRaw: string, attachmentsRaw: string[]): void {
const id = String(botId || '').trim();
if (!id || typeof window === 'undefined') return;
const command = String(commandRaw || '');
const attachments = normalizeAttachmentPaths(attachmentsRaw)
.map(normalizeDashboardAttachmentPath)
.filter(Boolean);
const key = getComposerDraftStorageKey(id);
try {
if (!command.trim() && attachments.length === 0) {
window.localStorage.removeItem(key);
return;
}
const payload: ComposerDraftStorage = {
command,
attachments,
updated_at_ms: Date.now(),
};
window.localStorage.setItem(key, JSON.stringify(payload));
} catch {
// ignore localStorage write failures
}
}
function isExternalHttpLink(href: string): boolean {
return /^https?:\/\//i.test(String(href || '').trim());
}
@ -631,6 +688,7 @@ export function BotDashboardModule({
const [workspaceAutoRefresh, setWorkspaceAutoRefresh] = useState(false);
const [workspaceQuery, setWorkspaceQuery] = useState('');
const [pendingAttachments, setPendingAttachments] = useState<string[]>([]);
const [composerDraftHydrated, setComposerDraftHydrated] = useState(false);
const [quotedReply, setQuotedReply] = useState<QuotedReply | null>(null);
const [isUploadingAttachments, setIsUploadingAttachments] = useState(false);
const [attachmentUploadPercent, setAttachmentUploadPercent] = useState<number | null>(null);
@ -700,7 +758,6 @@ export function BotDashboardModule({
memory_mb: String(clampMemoryMb(bot.memory_mb ?? 1024)),
storage_gb: String(clampStorageGb(bot.storage_gb ?? 10)),
});
setPendingAttachments([]);
}, []);
const buildWorkspaceDownloadHref = (filePath: string, forceDownload: boolean = true) => {
const query = [`path=${encodeURIComponent(filePath)}`];
@ -1383,6 +1440,36 @@ export function BotDashboardModule({
if (selectedBotId && !activeBots[selectedBotId] && bots.length > 0) setSelectedBotId(bots[0].id);
}, [bots, selectedBotId, activeBots, forcedBotId]);
useEffect(() => {
setComposerDraftHydrated(false);
if (!selectedBotId) {
setCommand('');
setPendingAttachments([]);
setComposerDraftHydrated(true);
return;
}
const draft = loadComposerDraft(selectedBotId);
setCommand(draft?.command || '');
setPendingAttachments(draft?.attachments || []);
setComposerDraftHydrated(true);
}, [selectedBotId]);
useEffect(() => {
if (!selectedBotId || !composerDraftHydrated) return;
persistComposerDraft(selectedBotId, command, pendingAttachments);
}, [selectedBotId, composerDraftHydrated, command, pendingAttachments]);
useEffect(() => {
const hasDraft = Boolean(String(command || '').trim()) || pendingAttachments.length > 0 || Boolean(quotedReply);
if (!hasDraft && !isUploadingAttachments) return;
const onBeforeUnload = (event: BeforeUnloadEvent) => {
event.preventDefault();
event.returnValue = '';
};
window.addEventListener('beforeunload', onBeforeUnload);
return () => window.removeEventListener('beforeunload', onBeforeUnload);
}, [command, pendingAttachments.length, quotedReply, isUploadingAttachments]);
useEffect(() => {
chatBottomRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' });
}, [selectedBotId, conversation.length]);