{selectedFile ? (
!loading ? (
selectedFile.type === 'md' && !isShowSourceCode ? (
@@ -568,7 +608,12 @@ export default function Folder({ data }: { data?: Agent }) {
isShowSourceCode ? (
<>{selectedFile.content}>
) : (
-
+
)
) : selectedFile.type === 'zip' ? (
@@ -660,19 +705,133 @@ function joinPath(...paths: string[]): string {
.replace(/\/+/g, '/');
}
+// Helper function to resolve relative paths (handles ../ and ./)
+function resolveRelativePath(basePath: string, relativePath: string): string {
+ // Normalize paths
+ const normalizedBase = basePath.replace(/\\/g, '/');
+ const normalizedRelative = relativePath.replace(/\\/g, '/');
+
+ // If it's not a relative path, return as-is
+ if (!normalizedRelative.startsWith('./') && !normalizedRelative.startsWith('../')) {
+ // It's a simple relative path like "script.js" or "js/script.js"
+ return joinPath(normalizedBase, normalizedRelative);
+ }
+
+ const baseParts = normalizedBase.split('/').filter(Boolean);
+ const relativeParts = normalizedRelative.split('/').filter(Boolean);
+
+ for (const part of relativeParts) {
+ if (part === '.') {
+ // Current directory, skip
+ continue;
+ } else if (part === '..') {
+ // Parent directory, go up one level
+ baseParts.pop();
+ } else {
+ // Regular path segment
+ baseParts.push(part);
+ }
+ }
+
+ return baseParts.join('/');
+}
+
// Component to render HTML with relative image paths resolved
-function HtmlRenderer({ selectedFile }: { selectedFile: FileInfo }) {
+function HtmlRenderer({
+ selectedFile,
+ projectFiles,
+ scriptsApproved,
+ onScriptsDetected,
+}: {
+ selectedFile: FileInfo;
+ projectFiles: FileInfo[];
+ scriptsApproved: boolean;
+ onScriptsDetected: (hasScripts: boolean) => void;
+}) {
+ const { t } = useTranslation();
const [processedHtml, setProcessedHtml] = useState
('');
+ const [hasScripts, setHasScripts] = useState(false);
+ const [rawHtmlWithScriptsCache, setRawHtmlWithScriptsCache] = useState('');
+ const hasShownWarningRef = useRef(null);
+ const iframeRef = useRef(null);
useEffect(() => {
const processHtml = async () => {
if (!selectedFile.content) {
setProcessedHtml('');
+ setHasScripts(false);
return;
}
let html = selectedFile.content;
+ // Get the directory of the HTML file
+ const htmlDir = getDirPath(selectedFile.path);
+
+ // Parse HTML to find referenced JS and CSS files via relative paths
+ const scriptSrcRegex = / or )
+ const inlineScriptRegex = /`,
+ 'gi'
+ );
+ const inlineScriptTag = ``;
+ processedHtmlContent = processedHtmlContent.replace(scriptRegex, inlineScriptTag);
+ }
+ } catch (error) {
+ console.error(`Failed to load JS file: ${jsFile.path}`, error);
+ }
+ }
+
+ // Store raw HTML with inline scripts for iframe rendering (before sanitization)
+ const rawHtmlWithScripts = processedHtmlContent;
+
+ // Sanitize the processed HTML (for non-iframe rendering)
const sanitized = DOMPurify.sanitize(processedHtmlContent, {
USE_PROFILES: { html: true },
ALLOWED_TAGS: [
@@ -881,6 +1100,12 @@ function HtmlRenderer({ selectedFile }: { selectedFile: FileInfo }) {
'h5',
'h6',
'style',
+ 'canvas',
+ 'html',
+ 'head',
+ 'body',
+ 'title',
+ 'meta',
],
ALLOWED_ATTR: [
'href',
@@ -928,16 +1153,75 @@ function HtmlRenderer({ selectedFile }: { selectedFile: FileInfo }) {
KEEP_CONTENT: false,
});
- setProcessedHtml(sanitized);
+ // Inject JS content after sanitization (only if user is aware of scripts)
+ let finalHtml = sanitized;
+
+ // Cache raw HTML with scripts for iframe rendering (before sanitization stripped them)
+ setRawHtmlWithScriptsCache(rawHtmlWithScripts);
+
+ setProcessedHtml(finalHtml);
};
processHtml();
- }, [selectedFile]);
+ }, [selectedFile, projectFiles, onScriptsDetected, t]);
+
+ // Zoom state and controls
+ const [zoom, setZoom] = useState(100);
+
+ const handleZoomIn = () => setZoom((prev) => Math.min(prev + 10, 200));
+ const handleZoomOut = () => setZoom((prev) => Math.max(prev - 10, 50));
+ const handleZoomReset = () => setZoom(100);
+
+ // Handle scroll wheel zoom (Ctrl+scroll or pinch)
+ const handleWheel = (e: React.WheelEvent) => {
+ if (e.ctrlKey || e.metaKey) {
+ e.preventDefault();
+ const delta = e.deltaY > 0 ? -10 : 10;
+ setZoom((prev) => Math.min(Math.max(prev + delta, 50), 200));
+ }
+ };
return (
-
+
+ {/* Floating notch-style zoom controls */}
+
+
+ {/* Content area with zoom */}
+
+
+ {scriptsApproved && hasScripts ? (
+
+
+
);
}
diff --git a/src/components/TopBar/index.tsx b/src/components/TopBar/index.tsx
index 860dc60c..778a06c6 100644
--- a/src/components/TopBar/index.tsx
+++ b/src/components/TopBar/index.tsx
@@ -48,6 +48,7 @@ function HeaderWin() {
const [isFullscreen, setIsFullscreen] = useState(false);
const { token } = getAuthStore();
const [endDialogOpen, setEndDialogOpen] = useState(false);
+ const [endProjectLoading, setEndProjectLoading] = useState(false);
useEffect(() => {
const p = window.electronAPI.getPlatform();
setPlatform(p);
@@ -150,6 +151,7 @@ function HeaderWin() {
const historyId = projectId ? projectStore.getHistoryId(projectId) : null;
+ setEndProjectLoading(true);
try {
const task = chatStore.tasks[taskId];
@@ -197,6 +199,7 @@ function HeaderWin() {
closeButton: true,
});
} finally {
+ setEndProjectLoading(false);
setEndDialogOpen(false);
}
};
@@ -414,6 +417,7 @@ function HeaderWin() {
open={endDialogOpen}
onOpenChange={setEndDialogOpen}
onConfirm={handleEndProject}
+ loading={endProjectLoading}
/>
);
diff --git a/src/components/WorkFlow/node.tsx b/src/components/WorkFlow/node.tsx
index 30b261ab..43d05aa1 100644
--- a/src/components/WorkFlow/node.tsx
+++ b/src/components/WorkFlow/node.tsx
@@ -104,7 +104,7 @@ export function Node({ id, data }: NodeProps) {
if (!chatStore) {
return
Loading...
;
}
-
+
const { setCenter, getNode, setViewport, setNodes } = useReactFlow();
const workerList = useWorkerList();
const { setWorkerList } = useAuthStore();
@@ -115,21 +115,47 @@ export function Node({ id, data }: NodeProps) {
setIsExpanded(data.isExpanded);
}, [data.isExpanded]);
+ // Auto-expand when a task is running with toolkits
useEffect(() => {
- const runningTask = data.agent?.tasks?.find(
+ const tasks = data.agent?.tasks || [];
+
+ // Find running task with active toolkits
+ const runningTaskWithToolkits = tasks.find(
(task) =>
- task.status === "running" && task.toolkits && task.toolkits.length > 0
+ task.status === "running" &&
+ task.toolkits &&
+ task.toolkits.length > 0
);
- if (runningTask && runningTask.id !== lastAutoExpandedTaskIdRef.current) {
+ // Reset tracking when no tasks are running
+ const hasRunningTasks = tasks.some((task) => task.status === "running");
+ if (!hasRunningTasks && lastAutoExpandedTaskIdRef.current) {
+ lastAutoExpandedTaskIdRef.current = null;
+ }
+
+ // Auto-expand for new running task
+ if (runningTaskWithToolkits && runningTaskWithToolkits.id !== lastAutoExpandedTaskIdRef.current) {
+ // Always select the new task
+ setSelectedTask(runningTaskWithToolkits);
+
+ // Expand if not already expanded
if (!isExpanded) {
setIsExpanded(true);
data.onExpandChange(id, true);
- setSelectedTask(runningTask);
}
- lastAutoExpandedTaskIdRef.current = runningTask.id;
+
+ lastAutoExpandedTaskIdRef.current = runningTaskWithToolkits.id;
}
- }, [data.agent?.tasks, id, data.onExpandChange, isExpanded]);
+ }, [
+ data.agent?.tasks,
+ // Add specific dependencies that actually change
+ data.agent?.tasks?.length,
+ data.agent?.tasks?.find((t) => t.status === "running")?.id,
+ data.agent?.tasks?.find((t) => t.status === "running")?.toolkits?.length,
+ id,
+ data.onExpandChange,
+ isExpanded,
+ ]);
// manually control node size
useEffect(() => {
diff --git a/src/i18n/locales/ar/chat.json b/src/i18n/locales/ar/chat.json
index ba122eee..c11283dd 100644
--- a/src/i18n/locales/ar/chat.json
+++ b/src/i18n/locales/ar/chat.json
@@ -47,5 +47,9 @@
"your-cloud-storage-has-reached-the-limit-of-your-current-plan": ".لقد وصل تخزينك السحابي إلى الحد الأقصى لخطة الاشتراك الحالية الخاصة بك",
"we-re-experiencing-high-traffic-please-try-again-in-a-few-moments": ".نواجه حركة مرور عالية. يرجى المحاولة مرة أخرى بعد وقت قصيرة",
"new-project": "مشروع جديد",
- "no-reply-received-task-continue": "لم يتم استلام رد، تستمر المهمة"
+ "no-reply-received-task-continue": "لم يتم استلام رد، تستمر المهمة",
+ "safe-to-run": "آمن للتشغيل",
+ "scripts-running": "البرامج النصية قيد التشغيل",
+ "scripts-warning": "وجد عارض HTML برامج نصية ذات صلة. تأكد من أن البرامج النصية آمنة للتشغيل.",
+ "scripts-approved": "تمت الموافقة على البرامج النصية. يتم العرض بكامل الوظائف."
}
diff --git a/src/i18n/locales/de/chat.json b/src/i18n/locales/de/chat.json
index 450378fe..e7cb664c 100644
--- a/src/i18n/locales/de/chat.json
+++ b/src/i18n/locales/de/chat.json
@@ -47,6 +47,10 @@
"your-cloud-storage-has-reached-the-limit-of-your-current-plan": "Ihr Cloud-Speicher hat das Limit Ihres aktuellen Plans erreicht.",
"we-re-experiencing-high-traffic-please-try-again-in-a-few-moments": "Wir erleben hohen Datenverkehr. Bitte versuchen Sie es in wenigen Augenblicken erneut.",
"new-project": "Neues Projekt",
- "no-reply-received-task-continue": "Keine Antwort erhalten, Aufgabe wird fortgesetzt"
+ "no-reply-received-task-continue": "Keine Antwort erhalten, Aufgabe wird fortgesetzt",
+ "safe-to-run": "Sicher ausführen",
+ "scripts-running": "Skripte werden ausgeführt",
+ "scripts-warning": "HTML-Renderer hat verwandte Skripte gefunden. Stellen Sie sicher, dass die Skripte sicher sind.",
+ "scripts-approved": "Skripte genehmigt. Rendering mit voller Funktionalität."
}
diff --git a/src/i18n/locales/en-us/chat.json b/src/i18n/locales/en-us/chat.json
index 35a144f4..dc7c6d0d 100644
--- a/src/i18n/locales/en-us/chat.json
+++ b/src/i18n/locales/en-us/chat.json
@@ -47,6 +47,10 @@
"your-cloud-storage-has-reached-the-limit-of-your-current-plan": "Your cloud storage has reached the limit of your current plan.",
"we-re-experiencing-high-traffic-please-try-again-in-a-few-moments": "We're experiencing high traffic. Please try again in a few moments.",
"new-project": "Untitled Project",
- "no-reply-received-task-continue": "No reply received, task continue"
+ "no-reply-received-task-continue": "No reply received, task continue",
+ "safe-to-run": "Safe to Run",
+ "scripts-running": "Scripts running",
+ "scripts-warning": "HTML render found related scripts. Make sure scripts are safe to run.",
+ "scripts-approved": "Scripts approved. Rendering with full functionality."
}
diff --git a/src/i18n/locales/es/chat.json b/src/i18n/locales/es/chat.json
index c6213395..599fb03f 100644
--- a/src/i18n/locales/es/chat.json
+++ b/src/i18n/locales/es/chat.json
@@ -47,6 +47,10 @@
"your-cloud-storage-has-reached-the-limit-of-your-current-plan": "Tu almacenamiento en la nube ha alcanzado el límite de tu plan actual.",
"we-re-experiencing-high-traffic-please-try-again-in-a-few-moments": "Estamos experimentando mucho tráfico. Inténtalo de nuevo en unos momentos.",
"new-project": "Nuevo proyecto",
- "no-reply-received-task-continue": "No se recibió respuesta, la tarea continúa"
+ "no-reply-received-task-continue": "No se recibió respuesta, la tarea continúa",
+ "safe-to-run": "Ejecutar de forma segura",
+ "scripts-running": "Scripts en ejecución",
+ "scripts-warning": "El renderizador HTML encontró scripts relacionados. Asegúrese de que los scripts sean seguros.",
+ "scripts-approved": "Scripts aprobados. Renderizando con funcionalidad completa."
}
diff --git a/src/i18n/locales/fr/chat.json b/src/i18n/locales/fr/chat.json
index 60c7d7f0..156a308a 100644
--- a/src/i18n/locales/fr/chat.json
+++ b/src/i18n/locales/fr/chat.json
@@ -47,5 +47,9 @@
"your-cloud-storage-has-reached-the-limit-of-your-current-plan": "Votre stockage cloud a atteint la limite de votre plan actuel.",
"we-re-experiencing-high-traffic-please-try-again-in-a-few-moments": "Nous connaissons un trafic élevé. Veuillez réessayer dans quelques instants.",
"new-project": "Nouveau projet",
- "no-reply-received-task-continue": "Aucune réponse reçue, la tâche continue"
+ "no-reply-received-task-continue": "Aucune réponse reçue, la tâche continue",
+ "safe-to-run": "Exécuter en toute sécurité",
+ "scripts-running": "Scripts en cours d'exécution",
+ "scripts-warning": "Le rendu HTML a trouvé des scripts associés. Assurez-vous que les scripts sont sûrs.",
+ "scripts-approved": "Scripts approuvés. Rendu avec toutes les fonctionnalités."
}
diff --git a/src/i18n/locales/it/chat.json b/src/i18n/locales/it/chat.json
index 4fa344f3..2ecfa485 100644
--- a/src/i18n/locales/it/chat.json
+++ b/src/i18n/locales/it/chat.json
@@ -47,6 +47,10 @@
"your-cloud-storage-has-reached-the-limit-of-your-current-plan": "Il tuo spazio di archiviazione cloud ha raggiunto il limite del tuo piano attuale.",
"we-re-experiencing-high-traffic-please-try-again-in-a-few-moments": "Stiamo riscontrando un traffico elevato. Riprova tra qualche istante.",
"new-project": "Nuovo Progetto",
- "no-reply-received-task-continue": "Nessuna risposta ricevuta, il compito continua"
+ "no-reply-received-task-continue": "Nessuna risposta ricevuta, il compito continua",
+ "safe-to-run": "Esegui in sicurezza",
+ "scripts-running": "Script in esecuzione",
+ "scripts-warning": "Il renderer HTML ha trovato script correlati. Assicurati che gli script siano sicuri.",
+ "scripts-approved": "Script approvati. Rendering con funzionalità complete."
}
diff --git a/src/i18n/locales/ja/chat.json b/src/i18n/locales/ja/chat.json
index 1423ad6d..3cc5e1e7 100644
--- a/src/i18n/locales/ja/chat.json
+++ b/src/i18n/locales/ja/chat.json
@@ -47,6 +47,10 @@
"your-cloud-storage-has-reached-the-limit-of-your-current-plan": "クラウドストレージが現在のプランの制限に達しました。",
"we-re-experiencing-high-traffic-please-try-again-in-a-few-moments": "トラフィックが多いため、しばらくしてからもう一度お試しください。",
"new-project": "新規プロジェクト",
- "no-reply-received-task-continue": "応答がないため、タスクを続行します"
+ "no-reply-received-task-continue": "応答がないため、タスクを続行します",
+ "safe-to-run": "安全に実行",
+ "scripts-running": "スクリプト実行中",
+ "scripts-warning": "HTMLレンダラーが関連スクリプトを検出しました。スクリプトが安全であることを確認してください。",
+ "scripts-approved": "スクリプトが承認されました。完全な機能でレンダリングしています。"
}
diff --git a/src/i18n/locales/ko/chat.json b/src/i18n/locales/ko/chat.json
index 746b201a..83172117 100644
--- a/src/i18n/locales/ko/chat.json
+++ b/src/i18n/locales/ko/chat.json
@@ -47,6 +47,10 @@
"your-cloud-storage-has-reached-the-limit-of-your-current-plan": "클라우드 스토리지가 현재 플랜의 한도에 도달했습니다.",
"we-re-experiencing-high-traffic-please-try-again-in-a-few-moments": "트래픽이 많습니다. 잠시 후 다시 시도해 주세요.",
"new-project": "새 프로젝트",
- "no-reply-received-task-continue": "응답이 없으므로 작업을 계속합니다."
+ "no-reply-received-task-continue": "응답이 없으므로 작업을 계속합니다.",
+ "safe-to-run": "안전하게 실행",
+ "scripts-running": "스크립트 실행 중",
+ "scripts-warning": "HTML 렌더러가 관련 스크립트를 발견했습니다. 스크립트가 안전한지 확인하세요.",
+ "scripts-approved": "스크립트가 승인되었습니다. 전체 기능으로 렌더링 중입니다."
}
diff --git a/src/i18n/locales/ru/chat.json b/src/i18n/locales/ru/chat.json
index 14a019a2..cedfd6f4 100644
--- a/src/i18n/locales/ru/chat.json
+++ b/src/i18n/locales/ru/chat.json
@@ -47,6 +47,10 @@
"your-cloud-storage-has-reached-the-limit-of-your-current-plan": "Ваше облачное хранилище достигло лимита вашего текущего плана.",
"we-re-experiencing-high-traffic-please-try-again-in-a-few-moments": "Мы испытываем высокую нагрузку. Пожалуйста, попробуйте еще раз через несколько минут.",
"new-project": "Новый проект",
- "no-reply-received-task-continue": "Ответ не получен, задача продолжается"
+ "no-reply-received-task-continue": "Ответ не получен, задача продолжается",
+ "safe-to-run": "Безопасно запустить",
+ "scripts-running": "Скрипты выполняются",
+ "scripts-warning": "HTML-рендерер обнаружил связанные скрипты. Убедитесь, что скрипты безопасны.",
+ "scripts-approved": "Скрипты одобрены. Рендеринг с полной функциональностью."
}
diff --git a/src/i18n/locales/zh-Hans/chat.json b/src/i18n/locales/zh-Hans/chat.json
index 0695c508..058149fd 100644
--- a/src/i18n/locales/zh-Hans/chat.json
+++ b/src/i18n/locales/zh-Hans/chat.json
@@ -47,5 +47,9 @@
"your-cloud-storage-has-reached-the-limit-of-your-current-plan": "您的云存储已达到当前计划的限制。",
"we-re-experiencing-high-traffic-please-try-again-in-a-few-moments": "我们正在经历高流量。请稍后再试。",
"new-project": "新项目",
- "no-reply-received-task-continue": "没有收到回复,任务继续"
+ "no-reply-received-task-continue": "没有收到回复,任务继续",
+ "safe-to-run": "安全运行",
+ "scripts-running": "脚本运行中",
+ "scripts-warning": "HTML渲染器发现相关脚本。请确保脚本可以安全运行。",
+ "scripts-approved": "脚本已批准。正在以完整功能渲染。"
}
diff --git a/src/i18n/locales/zh-Hant/chat.json b/src/i18n/locales/zh-Hant/chat.json
index 05443d9a..d0488fc0 100644
--- a/src/i18n/locales/zh-Hant/chat.json
+++ b/src/i18n/locales/zh-Hant/chat.json
@@ -47,5 +47,9 @@
"your-cloud-storage-has-reached-the-limit-of-your-current-plan": "您的雲存儲已達到當前計劃的限制。",
"we-re-experiencing-high-traffic-please-try-again-in-a-few-moments": "我們正在經歷高流量。請稍後再試。",
"new-project": "新項目",
- "no-reply-received-task-continue": "沒有收到回复,任務繼續"
+ "no-reply-received-task-continue": "沒有收到回复,任務繼續",
+ "safe-to-run": "安全運行",
+ "scripts-running": "腳本運行中",
+ "scripts-warning": "HTML渲染器發現相關腳本。請確保腳本可以安全運行。",
+ "scripts-approved": "腳本已批准。正在以完整功能渲染。"
}