v0.1.4
parent
82ce7d7373
commit
76a2ab49fe
|
|
@ -452,6 +452,63 @@ function normalizeDashboardAttachmentPath(path: string): string {
|
||||||
return v.replace(/^\/+/, '');
|
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 {
|
function isExternalHttpLink(href: string): boolean {
|
||||||
return /^https?:\/\//i.test(String(href || '').trim());
|
return /^https?:\/\//i.test(String(href || '').trim());
|
||||||
}
|
}
|
||||||
|
|
@ -631,6 +688,7 @@ export function BotDashboardModule({
|
||||||
const [workspaceAutoRefresh, setWorkspaceAutoRefresh] = useState(false);
|
const [workspaceAutoRefresh, setWorkspaceAutoRefresh] = useState(false);
|
||||||
const [workspaceQuery, setWorkspaceQuery] = useState('');
|
const [workspaceQuery, setWorkspaceQuery] = useState('');
|
||||||
const [pendingAttachments, setPendingAttachments] = useState<string[]>([]);
|
const [pendingAttachments, setPendingAttachments] = useState<string[]>([]);
|
||||||
|
const [composerDraftHydrated, setComposerDraftHydrated] = useState(false);
|
||||||
const [quotedReply, setQuotedReply] = useState<QuotedReply | null>(null);
|
const [quotedReply, setQuotedReply] = useState<QuotedReply | null>(null);
|
||||||
const [isUploadingAttachments, setIsUploadingAttachments] = useState(false);
|
const [isUploadingAttachments, setIsUploadingAttachments] = useState(false);
|
||||||
const [attachmentUploadPercent, setAttachmentUploadPercent] = useState<number | null>(null);
|
const [attachmentUploadPercent, setAttachmentUploadPercent] = useState<number | null>(null);
|
||||||
|
|
@ -700,7 +758,6 @@ export function BotDashboardModule({
|
||||||
memory_mb: String(clampMemoryMb(bot.memory_mb ?? 1024)),
|
memory_mb: String(clampMemoryMb(bot.memory_mb ?? 1024)),
|
||||||
storage_gb: String(clampStorageGb(bot.storage_gb ?? 10)),
|
storage_gb: String(clampStorageGb(bot.storage_gb ?? 10)),
|
||||||
});
|
});
|
||||||
setPendingAttachments([]);
|
|
||||||
}, []);
|
}, []);
|
||||||
const buildWorkspaceDownloadHref = (filePath: string, forceDownload: boolean = true) => {
|
const buildWorkspaceDownloadHref = (filePath: string, forceDownload: boolean = true) => {
|
||||||
const query = [`path=${encodeURIComponent(filePath)}`];
|
const query = [`path=${encodeURIComponent(filePath)}`];
|
||||||
|
|
@ -1383,6 +1440,36 @@ export function BotDashboardModule({
|
||||||
if (selectedBotId && !activeBots[selectedBotId] && bots.length > 0) setSelectedBotId(bots[0].id);
|
if (selectedBotId && !activeBots[selectedBotId] && bots.length > 0) setSelectedBotId(bots[0].id);
|
||||||
}, [bots, selectedBotId, activeBots, forcedBotId]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
chatBottomRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
chatBottomRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
||||||
}, [selectedBotId, conversation.length]);
|
}, [selectedBotId, conversation.length]);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue