myaiforone 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -0
- package/agents/_template/CLAUDE.md +18 -0
- package/agents/_template/agent.json +7 -0
- package/agents/platform/agentcreator/CLAUDE.md +300 -0
- package/agents/platform/appcreator/CLAUDE.md +158 -0
- package/agents/platform/gym/CLAUDE.md +486 -0
- package/agents/platform/gym/agent.json +40 -0
- package/agents/platform/gym/programs/agent-building/program.json +160 -0
- package/agents/platform/gym/programs/automations-mastery/program.json +129 -0
- package/agents/platform/gym/programs/getting-started/program.json +124 -0
- package/agents/platform/gym/programs/mcp-integrations/program.json +116 -0
- package/agents/platform/gym/programs/multi-model-strategy/program.json +115 -0
- package/agents/platform/gym/programs/prompt-engineering/program.json +136 -0
- package/agents/platform/gym/souls/alex.md +12 -0
- package/agents/platform/gym/souls/jordan.md +12 -0
- package/agents/platform/gym/souls/morgan.md +12 -0
- package/agents/platform/gym/souls/riley.md +12 -0
- package/agents/platform/gym/souls/sam.md +12 -0
- package/agents/platform/hub/CLAUDE.md +372 -0
- package/agents/platform/promptcreator/CLAUDE.md +130 -0
- package/agents/platform/skillcreator/CLAUDE.md +163 -0
- package/bin/cli.js +566 -0
- package/config.example.json +310 -0
- package/dist/agent-registry.d.ts +32 -0
- package/dist/agent-registry.d.ts.map +1 -0
- package/dist/agent-registry.js +144 -0
- package/dist/agent-registry.js.map +1 -0
- package/dist/channels/discord.d.ts +17 -0
- package/dist/channels/discord.d.ts.map +1 -0
- package/dist/channels/discord.js +114 -0
- package/dist/channels/discord.js.map +1 -0
- package/dist/channels/imessage.d.ts +23 -0
- package/dist/channels/imessage.d.ts.map +1 -0
- package/dist/channels/imessage.js +214 -0
- package/dist/channels/imessage.js.map +1 -0
- package/dist/channels/slack.d.ts +19 -0
- package/dist/channels/slack.d.ts.map +1 -0
- package/dist/channels/slack.js +167 -0
- package/dist/channels/slack.js.map +1 -0
- package/dist/channels/telegram.d.ts +19 -0
- package/dist/channels/telegram.d.ts.map +1 -0
- package/dist/channels/telegram.js +274 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/channels/types.d.ts +44 -0
- package/dist/channels/types.d.ts.map +1 -0
- package/dist/channels/types.js +18 -0
- package/dist/channels/types.js.map +1 -0
- package/dist/channels/whatsapp.d.ts +23 -0
- package/dist/channels/whatsapp.d.ts.map +1 -0
- package/dist/channels/whatsapp.js +189 -0
- package/dist/channels/whatsapp.js.map +1 -0
- package/dist/config.d.ts +134 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +127 -0
- package/dist/config.js.map +1 -0
- package/dist/cron.d.ts +8 -0
- package/dist/cron.d.ts.map +1 -0
- package/dist/cron.js +35 -0
- package/dist/cron.js.map +1 -0
- package/dist/decrypt-keys.d.ts +7 -0
- package/dist/decrypt-keys.d.ts.map +1 -0
- package/dist/decrypt-keys.js +53 -0
- package/dist/decrypt-keys.js.map +1 -0
- package/dist/encrypt-keys.d.ts +8 -0
- package/dist/encrypt-keys.d.ts.map +1 -0
- package/dist/encrypt-keys.js +62 -0
- package/dist/encrypt-keys.js.map +1 -0
- package/dist/executor.d.ts +31 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/executor.js +2009 -0
- package/dist/executor.js.map +1 -0
- package/dist/gemini-executor.d.ts +27 -0
- package/dist/gemini-executor.d.ts.map +1 -0
- package/dist/gemini-executor.js +160 -0
- package/dist/gemini-executor.js.map +1 -0
- package/dist/goals.d.ts +24 -0
- package/dist/goals.d.ts.map +1 -0
- package/dist/goals.js +189 -0
- package/dist/goals.js.map +1 -0
- package/dist/gym/activity-digest.d.ts +30 -0
- package/dist/gym/activity-digest.d.ts.map +1 -0
- package/dist/gym/activity-digest.js +506 -0
- package/dist/gym/activity-digest.js.map +1 -0
- package/dist/gym/dimension-scorer.d.ts +76 -0
- package/dist/gym/dimension-scorer.d.ts.map +1 -0
- package/dist/gym/dimension-scorer.js +236 -0
- package/dist/gym/dimension-scorer.js.map +1 -0
- package/dist/gym/gym-router.d.ts +7 -0
- package/dist/gym/gym-router.d.ts.map +1 -0
- package/dist/gym/gym-router.js +718 -0
- package/dist/gym/gym-router.js.map +1 -0
- package/dist/gym/index.d.ts +11 -0
- package/dist/gym/index.d.ts.map +1 -0
- package/dist/gym/index.js +11 -0
- package/dist/gym/index.js.map +1 -0
- package/dist/heartbeat.d.ts +21 -0
- package/dist/heartbeat.d.ts.map +1 -0
- package/dist/heartbeat.js +163 -0
- package/dist/heartbeat.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +254 -0
- package/dist/index.js.map +1 -0
- package/dist/keystore.d.ts +22 -0
- package/dist/keystore.d.ts.map +1 -0
- package/dist/keystore.js +178 -0
- package/dist/keystore.js.map +1 -0
- package/dist/logger.d.ts +9 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +45 -0
- package/dist/logger.js.map +1 -0
- package/dist/memory/daily.d.ts +22 -0
- package/dist/memory/daily.d.ts.map +1 -0
- package/dist/memory/daily.js +82 -0
- package/dist/memory/daily.js.map +1 -0
- package/dist/memory/embeddings.d.ts +15 -0
- package/dist/memory/embeddings.d.ts.map +1 -0
- package/dist/memory/embeddings.js +154 -0
- package/dist/memory/embeddings.js.map +1 -0
- package/dist/memory/index.d.ts +32 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +159 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/search.d.ts +21 -0
- package/dist/memory/search.d.ts.map +1 -0
- package/dist/memory/search.js +77 -0
- package/dist/memory/search.js.map +1 -0
- package/dist/memory/store.d.ts +23 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +144 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/ollama-executor.d.ts +17 -0
- package/dist/ollama-executor.d.ts.map +1 -0
- package/dist/ollama-executor.js +112 -0
- package/dist/ollama-executor.js.map +1 -0
- package/dist/openai-executor.d.ts +38 -0
- package/dist/openai-executor.d.ts.map +1 -0
- package/dist/openai-executor.js +197 -0
- package/dist/openai-executor.js.map +1 -0
- package/dist/router.d.ts +11 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +185 -0
- package/dist/router.js.map +1 -0
- package/dist/test-message.d.ts +2 -0
- package/dist/test-message.d.ts.map +1 -0
- package/dist/test-message.js +60 -0
- package/dist/test-message.js.map +1 -0
- package/dist/utils/imsg-db-reader.d.ts +24 -0
- package/dist/utils/imsg-db-reader.d.ts.map +1 -0
- package/dist/utils/imsg-db-reader.js +92 -0
- package/dist/utils/imsg-db-reader.js.map +1 -0
- package/dist/utils/imsg-rpc.d.ts +25 -0
- package/dist/utils/imsg-rpc.d.ts.map +1 -0
- package/dist/utils/imsg-rpc.js +149 -0
- package/dist/utils/imsg-rpc.js.map +1 -0
- package/dist/utils/message-formatter.d.ts +3 -0
- package/dist/utils/message-formatter.d.ts.map +1 -0
- package/dist/utils/message-formatter.js +69 -0
- package/dist/utils/message-formatter.js.map +1 -0
- package/dist/web-ui.d.ts +12 -0
- package/dist/web-ui.d.ts.map +1 -0
- package/dist/web-ui.js +5784 -0
- package/dist/web-ui.js.map +1 -0
- package/dist/whatsapp-chats.d.ts +2 -0
- package/dist/whatsapp-chats.d.ts.map +1 -0
- package/dist/whatsapp-chats.js +76 -0
- package/dist/whatsapp-chats.js.map +1 -0
- package/dist/whatsapp-login.d.ts +2 -0
- package/dist/whatsapp-login.d.ts.map +1 -0
- package/dist/whatsapp-login.js +90 -0
- package/dist/whatsapp-login.js.map +1 -0
- package/dist/wiki-sync.d.ts +21 -0
- package/dist/wiki-sync.d.ts.map +1 -0
- package/dist/wiki-sync.js +147 -0
- package/dist/wiki-sync.js.map +1 -0
- package/docs/AddNewAgentGuide.md +100 -0
- package/docs/AddNewMcpGuide.md +72 -0
- package/docs/Architecture.md +795 -0
- package/docs/CLAUDE-AI-SETUP.md +166 -0
- package/docs/Setup.md +297 -0
- package/docs/ai-gym-architecture.md +1040 -0
- package/docs/ai-gym-build-plan.md +343 -0
- package/docs/ai-gym-onboarding.md +122 -0
- package/docs/appcreator_plan.md +348 -0
- package/docs/platform-mcp-audit.md +320 -0
- package/docs/server-deployment-plan.md +503 -0
- package/docs/superpowers/plans/2026-03-25-marketplace.md +1281 -0
- package/docs/superpowers/specs/2026-03-25-marketplace-design.md +287 -0
- package/docs/user-guide.md +2016 -0
- package/mcp-catalog.json +628 -0
- package/package.json +63 -0
- package/public/MyAIforOne-logomark-512.svg +16 -0
- package/public/MyAIforOne-logomark-transparent.svg +15 -0
- package/public/activity.html +314 -0
- package/public/admin.html +1674 -0
- package/public/agent-dashboard.html +670 -0
- package/public/api-docs.html +1106 -0
- package/public/automations.html +722 -0
- package/public/canvas.css +223 -0
- package/public/canvas.js +588 -0
- package/public/changelog.html +231 -0
- package/public/gym.html +2766 -0
- package/public/home.html +1930 -0
- package/public/index.html +2809 -0
- package/public/lab.html +1643 -0
- package/public/library.html +1442 -0
- package/public/marketplace.html +1101 -0
- package/public/mcp-docs.html +441 -0
- package/public/mini.html +390 -0
- package/public/monitor.html +584 -0
- package/public/org.html +4304 -0
- package/public/projects.html +734 -0
- package/public/settings.html +645 -0
- package/public/tasks.html +932 -0
- package/public/trainers/alex.svg +12 -0
- package/public/trainers/jordan.svg +12 -0
- package/public/trainers/morgan.svg +12 -0
- package/public/trainers/riley.svg +12 -0
- package/public/trainers/sam.svg +12 -0
- package/public/user-guide.html +218 -0
- package/registry/agents.json +3 -0
- package/registry/apps.json +20 -0
- package/registry/installed-drafts.json +3 -0
- package/registry/mcps.json +1084 -0
- package/registry/prompts/personal/mcp-test-prompt.md +6 -0
- package/registry/prompts/personal/memory-recall.md +6 -0
- package/registry/prompts/platform/brainstorm.md +15 -0
- package/registry/prompts/platform/code-review.md +16 -0
- package/registry/prompts/platform/explain.md +16 -0
- package/registry/prompts.json +58 -0
- package/registry/skills/external/brainstorming.md +5 -0
- package/registry/skills/external/code-review.md +40 -0
- package/registry/skills/external/frontend-patterns.md +642 -0
- package/registry/skills/external/frontend-slides.md +184 -0
- package/registry/skills/external/systematic-debugging.md +5 -0
- package/registry/skills/external/tdd.md +328 -0
- package/registry/skills/external/verification-before-completion.md +5 -0
- package/registry/skills/external/writing-plans.md +5 -0
- package/registry/skills/platform/ai41_app_build.md +930 -0
- package/registry/skills/platform/ai41_app_deploy.md +168 -0
- package/registry/skills/platform/ai41_app_orchestrator.md +239 -0
- package/registry/skills/platform/ai41_app_patterns.md +359 -0
- package/registry/skills/platform/ai41_app_register.md +85 -0
- package/registry/skills/platform/ai41_app_scaffold.md +421 -0
- package/registry/skills/platform/ai41_app_verify.md +107 -0
- package/registry/skills/platform/opProjectCreate.md +239 -0
- package/registry/skills/platform/op_devbrowser.md +136 -0
- package/registry/skills/platform/sop_brandguidelines.md +103 -0
- package/registry/skills/platform/sop_docx.md +117 -0
- package/registry/skills/platform/sop_frontenddesign.md +44 -0
- package/registry/skills/platform/sop_frontenddesign_v2.md +659 -0
- package/registry/skills/platform/sop_mcpbuilder.md +133 -0
- package/registry/skills/platform/sop_pdf.md +172 -0
- package/registry/skills/platform/sop_pptx.md +133 -0
- package/registry/skills/platform/sop_skillcreator.md +104 -0
- package/registry/skills/platform/sop_themefactory.md +128 -0
- package/registry/skills/platform/sop_webapptesting.md +75 -0
- package/registry/skills/platform/sop_webartifactsbuilder.md +97 -0
- package/registry/skills/platform/sop_xlsx.md +134 -0
- package/registry/skills.json +1055 -0
- package/scripts/discover-chats.sh +11 -0
- package/scripts/install-service-windows.ps1 +87 -0
- package/scripts/install-service.sh +52 -0
- package/scripts/seed-registry.ts +195 -0
- package/scripts/test-send.sh +5 -0
- package/scripts/tray-indicator.ps1 +35 -0
- package/scripts/uninstall-service-windows.ps1 +23 -0
- package/scripts/uninstall-service.sh +15 -0
- package/scripts/xbar-myagent.5s.sh +32 -0
- package/server/mcp-server/dist/index.d.ts +11 -0
- package/server/mcp-server/dist/index.js +1332 -0
- package/server/mcp-server/dist/lib/api-client.d.ts +165 -0
- package/server/mcp-server/dist/lib/api-client.js +241 -0
- package/server/mcp-server/index.ts +1545 -0
- package/server/mcp-server/lib/api-client.ts +366 -0
- package/server/mcp-server/tsconfig.json +14 -0
- package/src/agent-registry.ts +180 -0
- package/src/channels/discord.ts +129 -0
- package/src/channels/imessage.ts +261 -0
- package/src/channels/slack.ts +208 -0
- package/src/channels/telegram.ts +307 -0
- package/src/channels/types.ts +62 -0
- package/src/channels/whatsapp.ts +227 -0
- package/src/config.ts +281 -0
- package/src/cron.ts +43 -0
- package/src/decrypt-keys.ts +60 -0
- package/src/encrypt-keys.ts +70 -0
- package/src/executor.ts +2190 -0
- package/src/gemini-executor.ts +212 -0
- package/src/goals.ts +240 -0
- package/src/gym/activity-digest.ts +546 -0
- package/src/gym/dimension-scorer.ts +297 -0
- package/src/gym/gym-router.ts +801 -0
- package/src/gym/index.ts +19 -0
- package/src/heartbeat.ts +220 -0
- package/src/index.ts +275 -0
- package/src/keystore.ts +190 -0
- package/src/logger.ts +51 -0
- package/src/memory/daily.ts +101 -0
- package/src/memory/embeddings.ts +185 -0
- package/src/memory/index.ts +218 -0
- package/src/memory/search.ts +124 -0
- package/src/memory/store.ts +189 -0
- package/src/ollama-executor.ts +126 -0
- package/src/openai-executor.ts +259 -0
- package/src/router.ts +230 -0
- package/src/test-message.ts +72 -0
- package/src/utils/imsg-db-reader.ts +109 -0
- package/src/utils/imsg-rpc.ts +178 -0
- package/src/utils/message-formatter.ts +90 -0
- package/src/web-ui.ts +5778 -0
- package/src/whatsapp-chats.ts +91 -0
- package/src/whatsapp-login.ts +110 -0
- package/src/wiki-sync.ts +199 -0
- package/tsconfig.json +19 -0
package/public/canvas.js
ADDED
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Canvas Module — file preview panel for any page.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* <link rel="stylesheet" href="/canvas.css">
|
|
6
|
+
* <script src="/canvas.js"></script>
|
|
7
|
+
* <script>
|
|
8
|
+
* Canvas.init({ getAgentId: () => selectedAgent });
|
|
9
|
+
* </script>
|
|
10
|
+
*
|
|
11
|
+
* Requires: mammoth.browser.min.js, jszip.min.js, xlsx.full.min.js (loaded before this script)
|
|
12
|
+
* Expects CSS variables from the host page: --bg-surface, --border-dim, --font-mono, etc.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
window.Canvas = (function() {
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
// ─── State ──────────────────────────────────────────────────────
|
|
19
|
+
let canvasOpen = false;
|
|
20
|
+
let canvasFile = null; // {path, name, ext, type, content}
|
|
21
|
+
let _getAgentId = () => '';
|
|
22
|
+
let _escapeHtml = s => (s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
23
|
+
|
|
24
|
+
// ─── Init ───────────────────────────────────────────────────────
|
|
25
|
+
function init(opts) {
|
|
26
|
+
if (opts.getAgentId) _getAgentId = opts.getAgentId;
|
|
27
|
+
if (opts.escapeHtml) _escapeHtml = opts.escapeHtml;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ─── File type detection ────────────────────────────────────────
|
|
31
|
+
function getFileType(ext) {
|
|
32
|
+
if (['html','htm'].includes(ext)) return 'html';
|
|
33
|
+
if (['csv','tsv'].includes(ext)) return 'csv';
|
|
34
|
+
if (['xlsx','xls'].includes(ext)) return 'xlsx';
|
|
35
|
+
if (['md','markdown'].includes(ext)) return 'markdown';
|
|
36
|
+
if (['svg'].includes(ext)) return 'svg';
|
|
37
|
+
if (['png','jpg','jpeg','gif','webp'].includes(ext)) return 'image';
|
|
38
|
+
if (['pdf'].includes(ext)) return 'pdf';
|
|
39
|
+
if (['docx','doc'].includes(ext)) return 'docx';
|
|
40
|
+
if (['pptx','ppt'].includes(ext)) return 'pptx';
|
|
41
|
+
if (['js','ts','jsx','tsx','py','rb','go','rs','java','c','cpp','h','sh','bash','zsh','sql','r','swift','kt','scala','lua','pl','php','css','scss','less'].includes(ext)) return 'code';
|
|
42
|
+
if (['json'].includes(ext)) return 'json';
|
|
43
|
+
if (['xml','yaml','yml','toml','ini','cfg','conf'].includes(ext)) return 'code';
|
|
44
|
+
return 'text';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getFileIcon(ext) {
|
|
48
|
+
const icons = {
|
|
49
|
+
pdf:'📄', csv:'📊', tsv:'📊', xlsx:'📊', xls:'📊',
|
|
50
|
+
json:'📋', md:'📝', markdown:'📝', txt:'📝',
|
|
51
|
+
png:'🖼️', jpg:'🖼️', jpeg:'🖼️', gif:'🖼️', svg:'🖼️', webp:'🖼️',
|
|
52
|
+
html:'🌐', htm:'🌐',
|
|
53
|
+
docx:'📃', doc:'📃',
|
|
54
|
+
pptx:'📽️', ppt:'📽️',
|
|
55
|
+
zip:'📦',
|
|
56
|
+
js:'⚡', ts:'⚡', py:'🐍', rb:'💎', go:'🔷', rs:'🦀',
|
|
57
|
+
java:'☕', c:'⚙️', cpp:'⚙️', h:'⚙️',
|
|
58
|
+
sh:'🐚', bash:'🐚', zsh:'🐚',
|
|
59
|
+
sql:'🗃️', xml:'📰', yaml:'📰', yml:'📰',
|
|
60
|
+
};
|
|
61
|
+
return icons[ext] || '📁';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── Toggle / Close ─────────────────────────────────────────────
|
|
65
|
+
function _clearResizeInlineStyles() {
|
|
66
|
+
const wrap = document.getElementById('chatBodyWrap');
|
|
67
|
+
if (!wrap) return;
|
|
68
|
+
const msgs = wrap.querySelector('.chat-messages');
|
|
69
|
+
const panel = document.getElementById('canvasPanel');
|
|
70
|
+
if (msgs) msgs.style.flex = '';
|
|
71
|
+
if (panel) panel.style.width = '';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function toggle() {
|
|
75
|
+
canvasOpen = !canvasOpen;
|
|
76
|
+
const wrap = document.getElementById('chatBodyWrap');
|
|
77
|
+
const btn = document.querySelector('.canvas-toggle-btn');
|
|
78
|
+
if (wrap) wrap.classList.toggle('canvas-open', canvasOpen);
|
|
79
|
+
if (btn) btn.classList.toggle('active', canvasOpen);
|
|
80
|
+
if (canvasOpen) _initResize();
|
|
81
|
+
else _clearResizeInlineStyles();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function close() {
|
|
85
|
+
canvasOpen = false;
|
|
86
|
+
canvasFile = null;
|
|
87
|
+
const wrap = document.getElementById('chatBodyWrap');
|
|
88
|
+
const btn = document.querySelector('.canvas-toggle-btn');
|
|
89
|
+
if (wrap) wrap.classList.remove('canvas-open');
|
|
90
|
+
if (btn) btn.classList.remove('active');
|
|
91
|
+
_clearResizeInlineStyles();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isOpen() { return canvasOpen; }
|
|
95
|
+
function currentFile() { return canvasFile; }
|
|
96
|
+
|
|
97
|
+
// ─── Resize handle ─────────────────────────────────────────────
|
|
98
|
+
function _initResize() {
|
|
99
|
+
const handle = document.getElementById('canvasResizeHandle');
|
|
100
|
+
if (!handle || handle._resizeInit) return;
|
|
101
|
+
handle._resizeInit = true;
|
|
102
|
+
|
|
103
|
+
handle.addEventListener('mousedown', e => {
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
const wrap = document.getElementById('chatBodyWrap');
|
|
106
|
+
const panel = document.getElementById('canvasPanel');
|
|
107
|
+
if (!wrap || !panel) return;
|
|
108
|
+
|
|
109
|
+
wrap.classList.add('canvas-resizing');
|
|
110
|
+
handle.classList.add('dragging');
|
|
111
|
+
|
|
112
|
+
const onMove = e => {
|
|
113
|
+
const rect = wrap.getBoundingClientRect();
|
|
114
|
+
const pxFromRight = rect.right - e.clientX;
|
|
115
|
+
const totalW = rect.width;
|
|
116
|
+
const pct = Math.max(20, Math.min(80, (pxFromRight / totalW) * 100));
|
|
117
|
+
panel.style.width = pct + '%';
|
|
118
|
+
const msgs = wrap.querySelector('.chat-messages');
|
|
119
|
+
if (msgs) msgs.style.flex = `0 0 ${100 - pct}%`;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const onUp = () => {
|
|
123
|
+
wrap.classList.remove('canvas-resizing');
|
|
124
|
+
handle.classList.remove('dragging');
|
|
125
|
+
document.removeEventListener('mousemove', onMove);
|
|
126
|
+
document.removeEventListener('mouseup', onUp);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
document.addEventListener('mousemove', onMove);
|
|
130
|
+
document.addEventListener('mouseup', onUp);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ─── Open file in canvas ────────────────────────────────────────
|
|
135
|
+
async function openFile(encodedPath) {
|
|
136
|
+
const path = decodeURIComponent(encodedPath);
|
|
137
|
+
const name = path.split('/').pop() || 'file';
|
|
138
|
+
const ext = (name.split('.').pop() || '').toLowerCase();
|
|
139
|
+
const type = getFileType(ext);
|
|
140
|
+
const agentId = _getAgentId();
|
|
141
|
+
|
|
142
|
+
canvasFile = { path, name, ext, type, content: null };
|
|
143
|
+
canvasOpen = true;
|
|
144
|
+
setTimeout(_initResize, 50);
|
|
145
|
+
|
|
146
|
+
const wrap = document.getElementById('chatBodyWrap');
|
|
147
|
+
const toggleBtn = document.querySelector('.canvas-toggle-btn');
|
|
148
|
+
if (wrap) wrap.classList.add('canvas-open');
|
|
149
|
+
if (toggleBtn) toggleBtn.classList.add('active');
|
|
150
|
+
|
|
151
|
+
const panel = document.getElementById('canvasPanel');
|
|
152
|
+
if (panel) {
|
|
153
|
+
panel.innerHTML = `
|
|
154
|
+
<div class="canvas-resize-handle" id="canvasResizeHandle"></div>
|
|
155
|
+
<div class="canvas-header">
|
|
156
|
+
<span class="canvas-file-icon">${getFileIcon(ext)}</span>
|
|
157
|
+
<span class="canvas-filename" title="${_escapeHtml(path)}">${_escapeHtml(name)}</span>
|
|
158
|
+
<button class="canvas-header-btn" onclick="Canvas.download('${encodeURIComponent(path)}')" title="Download">⬇</button>
|
|
159
|
+
<button class="canvas-header-btn" onclick="Canvas.close()" title="Close">✕</button>
|
|
160
|
+
</div>
|
|
161
|
+
<div class="canvas-content" id="canvasContent" style="display:flex;align-items:center;justify-content:center;color:var(--text-muted)">
|
|
162
|
+
Loading...
|
|
163
|
+
</div>
|
|
164
|
+
`;
|
|
165
|
+
// Re-init resize since we just replaced the handle
|
|
166
|
+
const handle = document.getElementById('canvasResizeHandle');
|
|
167
|
+
if (handle) handle._resizeInit = false;
|
|
168
|
+
_initResize();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const dlBase = `/api/agents/${agentId}/download?path=${encodeURIComponent(path)}`;
|
|
173
|
+
|
|
174
|
+
if (type === 'image') {
|
|
175
|
+
const content = document.getElementById('canvasContent');
|
|
176
|
+
if (content) { content.style = ''; content.innerHTML = `<img src="${dlBase}&inline=true" alt="${_escapeHtml(name)}" />`; }
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (type === 'pdf') {
|
|
181
|
+
const content = document.getElementById('canvasContent');
|
|
182
|
+
if (content) { content.style = ''; content.innerHTML = `<iframe src="${dlBase}&inline=true" style="width:100%;height:100%;border:none"></iframe>`; }
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (type === 'docx') {
|
|
187
|
+
const res = await fetch(dlBase);
|
|
188
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
189
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
190
|
+
const result = await mammoth.convertToHtml({ arrayBuffer });
|
|
191
|
+
const content = document.getElementById('canvasContent');
|
|
192
|
+
if (content) { content.style = ''; content.innerHTML = `<div class="canvas-docx">${result.value}</div>`; }
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (type === 'pptx') {
|
|
197
|
+
const res = await fetch(dlBase);
|
|
198
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
199
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
200
|
+
const zip = await JSZip.loadAsync(arrayBuffer);
|
|
201
|
+
|
|
202
|
+
const slideFiles = Object.keys(zip.files)
|
|
203
|
+
.filter(f => /^ppt\/slides\/slide\d+\.xml$/i.test(f))
|
|
204
|
+
.sort((a, b) => parseInt(a.match(/slide(\d+)/)[1]) - parseInt(b.match(/slide(\d+)/)[1]));
|
|
205
|
+
|
|
206
|
+
const mediaFiles = Object.keys(zip.files).filter(f => /^ppt\/media\//i.test(f) && !zip.files[f].dir);
|
|
207
|
+
const mediaMap = {};
|
|
208
|
+
for (const mf of mediaFiles) {
|
|
209
|
+
try { const blob = await zip.files[mf].async('blob'); mediaMap[mf.split('/').pop()] = URL.createObjectURL(blob); } catch {}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const slides = [];
|
|
213
|
+
const parser = new DOMParser();
|
|
214
|
+
for (const sf of slideFiles) {
|
|
215
|
+
const xmlText = await zip.files[sf].async('text');
|
|
216
|
+
const xmlDoc = parser.parseFromString(xmlText, 'text/xml');
|
|
217
|
+
const shapes = xmlDoc.getElementsByTagNameNS('http://schemas.openxmlformats.org/presentationml/2006/main', 'sp');
|
|
218
|
+
const shapeTexts = [];
|
|
219
|
+
if (shapes.length > 0) {
|
|
220
|
+
for (const shape of shapes) {
|
|
221
|
+
const pNodes = shape.getElementsByTagNameNS('http://schemas.openxmlformats.org/drawingml/2006/main', 'p');
|
|
222
|
+
const lines = [];
|
|
223
|
+
for (const p of pNodes) {
|
|
224
|
+
const pTexts = p.getElementsByTagNameNS('http://schemas.openxmlformats.org/drawingml/2006/main', 't');
|
|
225
|
+
const lineText = Array.from(pTexts).map(t => t.textContent).join('');
|
|
226
|
+
if (lineText.trim()) lines.push(lineText);
|
|
227
|
+
}
|
|
228
|
+
if (lines.length > 0) shapeTexts.push(lines);
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
const textNodes = xmlDoc.getElementsByTagNameNS('http://schemas.openxmlformats.org/drawingml/2006/main', 't');
|
|
232
|
+
const allT = Array.from(textNodes).map(t => t.textContent).join(' ');
|
|
233
|
+
if (allT.trim()) shapeTexts.push([allT]);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const slideNum = sf.match(/slide(\d+)/)[1];
|
|
237
|
+
const relPath = `ppt/slides/_rels/slide${slideNum}.xml.rels`;
|
|
238
|
+
const slideImages = [];
|
|
239
|
+
if (zip.files[relPath]) {
|
|
240
|
+
const relXml = await zip.files[relPath].async('text');
|
|
241
|
+
const relDoc = parser.parseFromString(relXml, 'text/xml');
|
|
242
|
+
const rels = relDoc.getElementsByTagName('Relationship');
|
|
243
|
+
for (const rel of rels) {
|
|
244
|
+
const target = rel.getAttribute('Target') || '';
|
|
245
|
+
if (/\.(png|jpg|jpeg|gif|bmp|svg|webp|tiff|emf|wmf)$/i.test(target)) {
|
|
246
|
+
const imgName = target.split('/').pop();
|
|
247
|
+
if (mediaMap[imgName]) slideImages.push(mediaMap[imgName]);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
slides.push({ shapeTexts, images: slideImages });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (slides.length === 0) {
|
|
255
|
+
const content = document.getElementById('canvasContent');
|
|
256
|
+
if (content) { content.style = ''; content.innerHTML = '<div style="padding:20px;color:var(--text-muted)">No slides found.</div>'; }
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
window._pptxSlides = slides;
|
|
261
|
+
window._pptxCurrentSlide = 0;
|
|
262
|
+
const content = document.getElementById('canvasContent');
|
|
263
|
+
if (content) { content.style = ''; _renderPptxSlide(content, 0, slides); }
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (type === 'xlsx') {
|
|
268
|
+
const res = await fetch(dlBase);
|
|
269
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
270
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
271
|
+
const wb = XLSX.read(arrayBuffer, { type: 'array' });
|
|
272
|
+
window._xlsxWorkbook = wb;
|
|
273
|
+
window._xlsxCurrentSheet = 0;
|
|
274
|
+
_renderXlsxSheet(0);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Fetch as text for all other types
|
|
279
|
+
const res = await fetch(dlBase);
|
|
280
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
281
|
+
const text = await res.text();
|
|
282
|
+
canvasFile.content = text;
|
|
283
|
+
|
|
284
|
+
const content = document.getElementById('canvasContent');
|
|
285
|
+
if (!content) return;
|
|
286
|
+
content.style = '';
|
|
287
|
+
|
|
288
|
+
if (type === 'svg') {
|
|
289
|
+
content.innerHTML = `<div class="canvas-svg">${text}</div>`;
|
|
290
|
+
} else if (type === 'json') {
|
|
291
|
+
try {
|
|
292
|
+
const parsed = JSON.parse(text);
|
|
293
|
+
content.innerHTML = `<div class="json-tree"><ul>${_renderJsonTree(parsed, '', 0)}</ul></div>`;
|
|
294
|
+
content.querySelectorAll('.json-toggle').forEach(tog => {
|
|
295
|
+
tog.addEventListener('click', function() {
|
|
296
|
+
const children = this.parentElement.querySelector('.json-children');
|
|
297
|
+
if (children) {
|
|
298
|
+
const collapsed = children.classList.toggle('collapsed');
|
|
299
|
+
this.textContent = collapsed ? '\u25B6' : '\u25BC';
|
|
300
|
+
const summary = this.parentElement.querySelector('.json-summary');
|
|
301
|
+
if (summary) summary.style.display = collapsed ? 'inline' : 'none';
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
} catch { content.innerHTML = `<pre>${_escapeHtml(text)}</pre>`; }
|
|
306
|
+
} else if (type === 'html') {
|
|
307
|
+
const blob = new Blob([text], { type: 'text/html' });
|
|
308
|
+
content.innerHTML = `<iframe src="${URL.createObjectURL(blob)}" sandbox="allow-scripts allow-same-origin" style="width:100%;height:100%;border:none;background:#fff"></iframe>`;
|
|
309
|
+
} else if (type === 'csv') {
|
|
310
|
+
content.innerHTML = _renderCsvTable(text, ext === 'tsv' ? '\t' : ',');
|
|
311
|
+
} else if (type === 'markdown') {
|
|
312
|
+
content.innerHTML = `<div class="canvas-md">${_formatMarkdown(text)}</div>`;
|
|
313
|
+
} else if (type === 'code') {
|
|
314
|
+
content.innerHTML = `<pre><code>${_escapeHtml(text)}</code></pre>`;
|
|
315
|
+
} else {
|
|
316
|
+
content.innerHTML = `<pre>${_escapeHtml(text)}</pre>`;
|
|
317
|
+
}
|
|
318
|
+
} catch (err) {
|
|
319
|
+
const content = document.getElementById('canvasContent');
|
|
320
|
+
if (content) content.innerHTML = `<div style="padding:20px;color:var(--text-muted)">Failed to load file: ${_escapeHtml(err.message)}</div>`;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ─── Download helper ────────────────────────────────────────────
|
|
325
|
+
function download(encodedPath) {
|
|
326
|
+
const path = decodeURIComponent(encodedPath);
|
|
327
|
+
const agentId = _getAgentId();
|
|
328
|
+
const a = document.createElement('a');
|
|
329
|
+
a.href = `/api/agents/${agentId}/download?path=${encodeURIComponent(path)}`;
|
|
330
|
+
a.download = path.split('/').pop() || 'file';
|
|
331
|
+
document.body.appendChild(a);
|
|
332
|
+
a.click();
|
|
333
|
+
document.body.removeChild(a);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ─── PPTX rendering ────────────────────────────────────────────
|
|
337
|
+
function _renderPptxSlide(container, index, slides) {
|
|
338
|
+
const slide = slides[index];
|
|
339
|
+
const total = slides.length;
|
|
340
|
+
let textHtml = '';
|
|
341
|
+
slide.shapeTexts.forEach((lines, si) => {
|
|
342
|
+
if (si === 0 && lines.length > 0) {
|
|
343
|
+
textHtml += `<div class="pptx-slide-title">${_escapeHtml(lines[0])}</div>`;
|
|
344
|
+
textHtml += '<div class="pptx-slide-text">';
|
|
345
|
+
lines.slice(1).forEach(l => { textHtml += `<p>${_escapeHtml(l)}</p>`; });
|
|
346
|
+
textHtml += '</div>';
|
|
347
|
+
} else {
|
|
348
|
+
textHtml += '<div class="pptx-slide-text">';
|
|
349
|
+
lines.forEach(l => { textHtml += `<p>${_escapeHtml(l)}</p>`; });
|
|
350
|
+
textHtml += '</div>';
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
let imgHtml = '';
|
|
354
|
+
if (slide.images.length > 0) {
|
|
355
|
+
imgHtml = '<div class="pptx-slide-images">';
|
|
356
|
+
slide.images.forEach(src => { imgHtml += `<img src="${src}" alt="Slide image" />`; });
|
|
357
|
+
imgHtml += '</div>';
|
|
358
|
+
}
|
|
359
|
+
container.innerHTML = `
|
|
360
|
+
<div class="pptx-viewer">
|
|
361
|
+
<div class="pptx-nav">
|
|
362
|
+
<button class="pptx-nav-btn" onclick="Canvas.navigateSlide(-1)" ${index === 0 ? 'disabled' : ''}>◀ Prev</button>
|
|
363
|
+
<span class="pptx-slide-counter">Slide ${index + 1} of ${total}</span>
|
|
364
|
+
<button class="pptx-nav-btn" onclick="Canvas.navigateSlide(1)" ${index === total - 1 ? 'disabled' : ''}>Next ▶</button>
|
|
365
|
+
</div>
|
|
366
|
+
<div class="pptx-slide">
|
|
367
|
+
${textHtml || '<div style="color:var(--text-muted);font-style:italic">No text content on this slide</div>'}
|
|
368
|
+
${imgHtml}
|
|
369
|
+
</div>
|
|
370
|
+
</div>`;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function navigateSlide(direction) {
|
|
374
|
+
const slides = window._pptxSlides;
|
|
375
|
+
if (!slides) return;
|
|
376
|
+
let idx = window._pptxCurrentSlide + direction;
|
|
377
|
+
if (idx < 0) idx = 0;
|
|
378
|
+
if (idx >= slides.length) idx = slides.length - 1;
|
|
379
|
+
window._pptxCurrentSlide = idx;
|
|
380
|
+
const content = document.getElementById('canvasContent');
|
|
381
|
+
if (content) _renderPptxSlide(content, idx, slides);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// ─── CSV table ──────────────────────────────────────────────────
|
|
385
|
+
function _parseCsvLine(line, delim) {
|
|
386
|
+
const cells = [];
|
|
387
|
+
let current = '';
|
|
388
|
+
let inQuote = false;
|
|
389
|
+
for (let i = 0; i < line.length; i++) {
|
|
390
|
+
const ch = line[i];
|
|
391
|
+
if (inQuote) {
|
|
392
|
+
if (ch === '"' && line[i+1] === '"') { current += '"'; i++; }
|
|
393
|
+
else if (ch === '"') { inQuote = false; }
|
|
394
|
+
else { current += ch; }
|
|
395
|
+
} else {
|
|
396
|
+
if (ch === '"') { inQuote = true; }
|
|
397
|
+
else if (ch === delim) { cells.push(current); current = ''; }
|
|
398
|
+
else { current += ch; }
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
cells.push(current);
|
|
402
|
+
return cells;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function _renderCsvTable(text, delimiter) {
|
|
406
|
+
const lines = text.split('\n').filter(l => l.trim());
|
|
407
|
+
if (lines.length === 0) return '<pre>Empty file</pre>';
|
|
408
|
+
const headers = _parseCsvLine(lines[0], delimiter);
|
|
409
|
+
const rows = lines.slice(1).map(l => _parseCsvLine(l, delimiter));
|
|
410
|
+
const tableId = 'csvTable-' + Math.random().toString(36).slice(2,8);
|
|
411
|
+
let html = `<table class="canvas-csv-table" id="${tableId}"><thead><tr>`;
|
|
412
|
+
headers.forEach((h, i) => {
|
|
413
|
+
html += `<th onclick="Canvas.sortTable('${tableId}',${i})" title="Click to sort">${_escapeHtml(h.trim())} ▴▾</th>`;
|
|
414
|
+
});
|
|
415
|
+
html += '</tr></thead><tbody>';
|
|
416
|
+
rows.forEach(row => {
|
|
417
|
+
html += '<tr>' + row.map(c => `<td>${_escapeHtml(c.trim())}</td>`).join('') + '</tr>';
|
|
418
|
+
});
|
|
419
|
+
html += '</tbody></table>';
|
|
420
|
+
return html;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function sortTable(tableId, colIdx) {
|
|
424
|
+
const table = document.getElementById(tableId);
|
|
425
|
+
if (!table) return;
|
|
426
|
+
const tbody = table.querySelector('tbody');
|
|
427
|
+
const rows = [...tbody.querySelectorAll('tr')];
|
|
428
|
+
const dir = table.dataset.sortCol === String(colIdx) && table.dataset.sortDir === 'asc' ? 'desc' : 'asc';
|
|
429
|
+
table.dataset.sortCol = colIdx;
|
|
430
|
+
table.dataset.sortDir = dir;
|
|
431
|
+
rows.sort((a, b) => {
|
|
432
|
+
const aVal = a.cells[colIdx]?.textContent || '';
|
|
433
|
+
const bVal = b.cells[colIdx]?.textContent || '';
|
|
434
|
+
const aNum = parseFloat(aVal);
|
|
435
|
+
const bNum = parseFloat(bVal);
|
|
436
|
+
if (!isNaN(aNum) && !isNaN(bNum)) return dir === 'asc' ? aNum - bNum : bNum - aNum;
|
|
437
|
+
return dir === 'asc' ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
|
|
438
|
+
});
|
|
439
|
+
rows.forEach(r => tbody.appendChild(r));
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ─── XLSX sheets ────────────────────────────────────────────────
|
|
443
|
+
function _renderXlsxSheet(sheetIndex) {
|
|
444
|
+
const wb = window._xlsxWorkbook;
|
|
445
|
+
if (!wb) return;
|
|
446
|
+
const content = document.getElementById('canvasContent');
|
|
447
|
+
if (!content) return;
|
|
448
|
+
content.style = '';
|
|
449
|
+
window._xlsxCurrentSheet = sheetIndex;
|
|
450
|
+
const sheet = wb.Sheets[wb.SheetNames[sheetIndex]];
|
|
451
|
+
const csvText = XLSX.utils.sheet_to_csv(sheet);
|
|
452
|
+
let html = '';
|
|
453
|
+
if (wb.SheetNames.length > 1) {
|
|
454
|
+
html += '<div class="canvas-xlsx-tabs">';
|
|
455
|
+
wb.SheetNames.forEach((sn, i) => {
|
|
456
|
+
html += `<button class="${i === sheetIndex ? 'active' : ''}" onclick="Canvas.renderSheet(${i})">${_escapeHtml(sn)}</button>`;
|
|
457
|
+
});
|
|
458
|
+
html += '</div>';
|
|
459
|
+
}
|
|
460
|
+
html += _renderCsvTable(csvText, ',');
|
|
461
|
+
content.innerHTML = html;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// ─── JSON tree ──────────────────────────────────────────────────
|
|
465
|
+
function _renderJsonTree(val, key, depth) {
|
|
466
|
+
const maxExpandDepth = 2;
|
|
467
|
+
if (val === null) {
|
|
468
|
+
const kp = key !== '' ? `<span class="json-key">"${_escapeHtml(key)}"</span>: ` : '';
|
|
469
|
+
return `<li>${kp}<span class="json-null">null</span></li>`;
|
|
470
|
+
}
|
|
471
|
+
if (typeof val === 'string') {
|
|
472
|
+
const kp = key !== '' ? `<span class="json-key">"${_escapeHtml(key)}"</span>: ` : '';
|
|
473
|
+
return `<li>${kp}<span class="json-string">"${_escapeHtml(val)}"</span></li>`;
|
|
474
|
+
}
|
|
475
|
+
if (typeof val === 'number') {
|
|
476
|
+
const kp = key !== '' ? `<span class="json-key">"${_escapeHtml(key)}"</span>: ` : '';
|
|
477
|
+
return `<li>${kp}<span class="json-number">${val}</span></li>`;
|
|
478
|
+
}
|
|
479
|
+
if (typeof val === 'boolean') {
|
|
480
|
+
const kp = key !== '' ? `<span class="json-key">"${_escapeHtml(key)}"</span>: ` : '';
|
|
481
|
+
return `<li>${kp}<span class="json-bool">${val}</span></li>`;
|
|
482
|
+
}
|
|
483
|
+
const isArray = Array.isArray(val);
|
|
484
|
+
const entries = isArray ? val.map((v, i) => [i, v]) : Object.entries(val);
|
|
485
|
+
const count = entries.length;
|
|
486
|
+
const collapsed = depth >= maxExpandDepth;
|
|
487
|
+
const openBracket = isArray ? '[' : '{';
|
|
488
|
+
const closeBracket = isArray ? ']' : '}';
|
|
489
|
+
const summary = isArray ? `[...] ${count} items` : `{...} ${count} keys`;
|
|
490
|
+
const arrow = collapsed ? '\u25B6' : '\u25BC';
|
|
491
|
+
const kp = key !== '' ? `<span class="json-key">"${_escapeHtml(key)}"</span>: ` : '';
|
|
492
|
+
let html = `<li>${kp}<span class="json-toggle">${arrow}</span><span class="json-bracket">${openBracket}</span>`;
|
|
493
|
+
html += `<span class="json-summary" style="display:${collapsed ? 'inline' : 'none'}">${summary}</span>`;
|
|
494
|
+
html += `<ul class="json-children${collapsed ? ' collapsed' : ''}">`;
|
|
495
|
+
entries.forEach(([k, v]) => { html += _renderJsonTree(v, isArray ? '' : String(k), depth + 1); });
|
|
496
|
+
html += `</ul><span class="json-bracket">${closeBracket}</span></li>`;
|
|
497
|
+
return html;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// ─── Markdown formatter ─────────────────────────────────────────
|
|
501
|
+
function _formatMarkdown(text) {
|
|
502
|
+
if (!text) return '';
|
|
503
|
+
let html = _escapeHtml(text);
|
|
504
|
+
html = html.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre><code>$2</code></pre>');
|
|
505
|
+
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
506
|
+
html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
|
|
507
|
+
html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
|
|
508
|
+
html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');
|
|
509
|
+
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
|
510
|
+
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
|
511
|
+
html = html.replace(/^- (.+)$/gm, '<li>$1</li>');
|
|
512
|
+
html = html.replace(/(<li>.*<\/li>\n?)+/g, '<ul>$&</ul>');
|
|
513
|
+
html = html.replace(/^\d+\. (.+)$/gm, '<li>$1</li>');
|
|
514
|
+
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" style="color:var(--accent)">$1</a>');
|
|
515
|
+
html = html.replace(/^---+$/gm, '<hr style="border:none;border-top:1px solid var(--border-dim);margin:1em 0">');
|
|
516
|
+
html = html.replace(/\n{2,}/g, '</p><p>');
|
|
517
|
+
html = html.replace(/\n/g, '<br>');
|
|
518
|
+
html = '<p>' + html + '</p>';
|
|
519
|
+
return html;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// ─── File path injection for message formatting ─────────────────
|
|
523
|
+
function injectFileButtons(html, escapeHtmlFn) {
|
|
524
|
+
const fileExts = 'csv|json|txt|md|pdf|xlsx|xls|docx|doc|pptx|ppt|png|jpg|jpeg|gif|svg|html|zip|xml|yaml|yml|tsv';
|
|
525
|
+
const filePathRegex = new RegExp(`((?:~\\/|FileStorage\\/|\\/)[\\w.@/ -]*\\.(?:${fileExts}))`, 'gi');
|
|
526
|
+
return html.replace(filePathRegex, (match) => {
|
|
527
|
+
const decoded = match.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
528
|
+
const fileName = decoded.split('/').pop();
|
|
529
|
+
return `<code>${match}</code> <button class="download-btn" onclick="Canvas.download('${encodeURIComponent(decoded)}')" title="Download ${fileName}">⬇ ${fileName}</button> <button class="canvas-btn" onclick="Canvas.openFile('${encodeURIComponent(decoded)}')" title="Preview in canvas">▨ Preview</button>`;
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// ─── Canvas panel HTML ──────────────────────────────────────────
|
|
534
|
+
function getPanelHtml() {
|
|
535
|
+
if (canvasFile) {
|
|
536
|
+
return `<div class="canvas-panel" id="canvasPanel">
|
|
537
|
+
<div class="canvas-resize-handle" id="canvasResizeHandle"></div>
|
|
538
|
+
<div class="canvas-header">
|
|
539
|
+
<span class="canvas-file-icon">${getFileIcon(canvasFile.ext)}</span>
|
|
540
|
+
<span class="canvas-filename" title="${_escapeHtml(canvasFile.path)}">${_escapeHtml(canvasFile.name)}</span>
|
|
541
|
+
<button class="canvas-header-btn" onclick="Canvas.download('${encodeURIComponent(canvasFile.path)}')" title="Download">⬇</button>
|
|
542
|
+
<button class="canvas-header-btn" onclick="Canvas.close()" title="Close">✕</button>
|
|
543
|
+
</div>
|
|
544
|
+
<div class="canvas-content" id="canvasContent"></div>
|
|
545
|
+
</div>`;
|
|
546
|
+
}
|
|
547
|
+
return `<div class="canvas-panel" id="canvasPanel">
|
|
548
|
+
<div class="canvas-resize-handle" id="canvasResizeHandle"></div>
|
|
549
|
+
<div class="canvas-header">
|
|
550
|
+
<span class="canvas-file-icon">▨</span>
|
|
551
|
+
<span class="canvas-filename" style="color:var(--text-muted)">No file open</span>
|
|
552
|
+
<button class="canvas-header-btn" onclick="Canvas.close()" title="Close">✕</button>
|
|
553
|
+
</div>
|
|
554
|
+
<div class="canvas-content" style="display:flex;align-items:center;justify-content:center;padding:40px;color:var(--text-muted);font-size:13px">
|
|
555
|
+
<div style="text-align:center">
|
|
556
|
+
<div style="font-size:32px;opacity:.3;margin-bottom:8px">▨</div>
|
|
557
|
+
<div>Click "Preview" on a file path to open it here</div>
|
|
558
|
+
</div>
|
|
559
|
+
</div>
|
|
560
|
+
</div>`;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// ─── Public API ─────────────────────────────────────────────────
|
|
564
|
+
return {
|
|
565
|
+
init,
|
|
566
|
+
toggle,
|
|
567
|
+
close,
|
|
568
|
+
isOpen,
|
|
569
|
+
currentFile,
|
|
570
|
+
openFile,
|
|
571
|
+
download,
|
|
572
|
+
navigateSlide,
|
|
573
|
+
sortTable,
|
|
574
|
+
renderSheet: _renderXlsxSheet,
|
|
575
|
+
injectFileButtons,
|
|
576
|
+
getPanelHtml,
|
|
577
|
+
getFileType,
|
|
578
|
+
getFileIcon,
|
|
579
|
+
};
|
|
580
|
+
})();
|
|
581
|
+
|
|
582
|
+
// Backwards compat — global function aliases
|
|
583
|
+
function openInCanvas(p) { Canvas.openFile(p); }
|
|
584
|
+
function closeCanvas() { Canvas.close(); }
|
|
585
|
+
function toggleCanvas() { Canvas.toggle(); }
|
|
586
|
+
function navigatePptxSlide(d) { Canvas.navigateSlide(d); }
|
|
587
|
+
function sortCsvTable(t,c) { Canvas.sortTable(t,c); }
|
|
588
|
+
function renderXlsxSheet(i) { Canvas.renderSheet(i); }
|