ai-control-center 1.15.2
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/LICENSE +21 -0
- package/README.md +584 -0
- package/bin/aicc.js +772 -0
- package/lib/actions/approve.js +71 -0
- package/lib/actions/assign-project.js +132 -0
- package/lib/actions/browser-test.js +64 -0
- package/lib/actions/cleanup.js +174 -0
- package/lib/actions/debug.js +298 -0
- package/lib/actions/deploy.js +1229 -0
- package/lib/actions/fix-bug.js +134 -0
- package/lib/actions/new-feature.js +255 -0
- package/lib/actions/reject.js +307 -0
- package/lib/actions/review.js +706 -0
- package/lib/actions/status.js +47 -0
- package/lib/agents/browser-qa-agent.js +611 -0
- package/lib/agents/payment-agent.js +116 -0
- package/lib/agents/suggestion-agent.js +88 -0
- package/lib/cli.js +303 -0
- package/lib/config.js +243 -0
- package/lib/hub/hub-server.js +440 -0
- package/lib/hub/project-poller.js +75 -0
- package/lib/hub/skill-registry.js +89 -0
- package/lib/hub/state-aggregator.js +204 -0
- package/lib/index.js +471 -0
- package/lib/init/doctor.js +523 -0
- package/lib/init/presets.js +222 -0
- package/lib/init/skill-fetcher.js +77 -0
- package/lib/init/wizard.js +973 -0
- package/lib/integrations/codex-runner.js +128 -0
- package/lib/integrations/github-actions.js +248 -0
- package/lib/integrations/github-reporter.js +229 -0
- package/lib/integrations/screenshot-store.js +102 -0
- package/lib/openclaw/bridge.js +650 -0
- package/lib/openclaw/generate-skill.js +235 -0
- package/lib/openclaw/openclaw.json +64 -0
- package/lib/orchestrator/autonomous-loop.js +429 -0
- package/lib/orchestrator/thread-triggers.js +63 -0
- package/lib/roleplay/agent-messenger.js +75 -0
- package/lib/roleplay/discussion-threads.js +303 -0
- package/lib/roleplay/health-monitor.js +121 -0
- package/lib/roleplay/pm-agent.js +513 -0
- package/lib/roleplay/roleplay-config.js +25 -0
- package/lib/roleplay/room.js +164 -0
- package/lib/shared/action-runner.js +2330 -0
- package/lib/shared/event-bus.js +185 -0
- package/lib/slack/bot.js +378 -0
- package/lib/telegram/bot.js +416 -0
- package/lib/telegram/commands.js +1267 -0
- package/lib/telegram/keyboards.js +113 -0
- package/lib/telegram/notifications.js +247 -0
- package/lib/twitch/bot.js +354 -0
- package/lib/twitch/commands.js +302 -0
- package/lib/twitch/notifications.js +63 -0
- package/lib/utils/achievements.js +191 -0
- package/lib/utils/activity-log.js +182 -0
- package/lib/utils/agent-leaderboard.js +119 -0
- package/lib/utils/audit-logger.js +232 -0
- package/lib/utils/codebase-context.js +288 -0
- package/lib/utils/codebase-indexer.js +381 -0
- package/lib/utils/config-schema.js +230 -0
- package/lib/utils/context-compressor.js +172 -0
- package/lib/utils/correlation.js +63 -0
- package/lib/utils/cost-tracker.js +423 -0
- package/lib/utils/cron-scheduler.js +53 -0
- package/lib/utils/db-adapter.js +293 -0
- package/lib/utils/display.js +272 -0
- package/lib/utils/errors.js +116 -0
- package/lib/utils/format.js +134 -0
- package/lib/utils/intent-engine.js +464 -0
- package/lib/utils/mcp-client.js +238 -0
- package/lib/utils/model-ab-test.js +164 -0
- package/lib/utils/notify.js +122 -0
- package/lib/utils/persona-loader.js +80 -0
- package/lib/utils/pipeline-lock.js +73 -0
- package/lib/utils/pipeline.js +214 -0
- package/lib/utils/plugin-runner.js +234 -0
- package/lib/utils/rate-limiter.js +84 -0
- package/lib/utils/rbac.js +74 -0
- package/lib/utils/runner.js +1809 -0
- package/lib/utils/security.js +191 -0
- package/lib/utils/self-healer.js +144 -0
- package/lib/utils/skill-loader.js +255 -0
- package/lib/utils/spinner.js +132 -0
- package/lib/utils/stage-queue.js +50 -0
- package/lib/utils/state-machine.js +89 -0
- package/lib/utils/status-bar.js +327 -0
- package/lib/utils/token-estimator.js +101 -0
- package/lib/utils/ux-analyzer.js +101 -0
- package/lib/utils/webhook-emitter.js +83 -0
- package/lib/web/public/css/styles.css +417 -0
- package/lib/web/public/dark-mode.js +44 -0
- package/lib/web/public/hub/kanban.html +206 -0
- package/lib/web/public/index.html +45 -0
- package/lib/web/public/js/app.js +71 -0
- package/lib/web/public/js/ask.js +110 -0
- package/lib/web/public/js/dashboard.js +165 -0
- package/lib/web/public/js/deploy.js +72 -0
- package/lib/web/public/js/feature.js +79 -0
- package/lib/web/public/js/health.js +65 -0
- package/lib/web/public/js/logs.js +93 -0
- package/lib/web/public/js/review.js +123 -0
- package/lib/web/public/js/ws-client.js +82 -0
- package/lib/web/public/office/css/office.css +678 -0
- package/lib/web/public/office/index.html +148 -0
- package/lib/web/public/office/js/achievements-ui.js +117 -0
- package/lib/web/public/office/js/character.js +1056 -0
- package/lib/web/public/office/js/chat-bubbles.js +177 -0
- package/lib/web/public/office/js/cost-overlay.js +123 -0
- package/lib/web/public/office/js/day-night.js +68 -0
- package/lib/web/public/office/js/effects.js +632 -0
- package/lib/web/public/office/js/engine.js +146 -0
- package/lib/web/public/office/js/feature-ticket.js +216 -0
- package/lib/web/public/office/js/hub-client.js +60 -0
- package/lib/web/public/office/js/main.js +1757 -0
- package/lib/web/public/office/js/office-layout.js +1524 -0
- package/lib/web/public/office/js/pathfinding.js +144 -0
- package/lib/web/public/office/js/pixel-sprites.js +1454 -0
- package/lib/web/public/office/js/progress-bars.js +117 -0
- package/lib/web/public/office/js/replay.js +191 -0
- package/lib/web/public/office/js/sound-effects.js +91 -0
- package/lib/web/public/office/js/sprite-renderer.js +211 -0
- package/lib/web/public/office/js/stamina-system.js +89 -0
- package/lib/web/public/office/js/ui.js +107 -0
- package/lib/web/public/onboarding/index.html +243 -0
- package/lib/web/public/timeline/index.html +195 -0
- package/lib/web/routes/api.js +499 -0
- package/lib/web/routes/logs.js +20 -0
- package/lib/web/routes/metrics.js +99 -0
- package/lib/web/server.js +183 -0
- package/lib/web/ws/handler.js +65 -0
- package/package.json +67 -0
- package/templates/agent-architect.md +69 -0
- package/templates/agent-gemini-pm.md +49 -0
- package/templates/agent-gemini-reviewer.md +52 -0
- package/templates/copilot-instructions.md +36 -0
- package/templates/pipelines/mobile.json +27 -0
- package/templates/pipelines/nodejs-api.json +27 -0
- package/templates/pipelines/python.json +27 -0
- package/templates/pipelines/react.json +27 -0
- package/templates/pipelines/salesforce.json +27 -0
- package/templates/role-gemini.md +97 -0
- package/templates/skill-architect.md +114 -0
- package/templates/skill-browser-qa.md +50 -0
- package/templates/skill-bug-from-qa.md +58 -0
- package/templates/skill-chatbot.md +93 -0
- package/templates/skill-implement.md +78 -0
- package/templates/skill-openclaw.md +174 -0
- package/templates/skill-payment.md +110 -0
- package/templates/skill-pm-spec.md +77 -0
- package/templates/skill-requirement-capture.md +97 -0
- package/templates/skill-review.md +108 -0
- package/templates/skill-reviewer-qa.md +44 -0
- package/templates/skill-suggestion.md +45 -0
- package/templates/skill-template.md +142 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>AI Operations Center</title>
|
|
7
|
+
<link rel="stylesheet" href="css/office.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="app">
|
|
11
|
+
<!-- Top bar -->
|
|
12
|
+
<header id="topbar">
|
|
13
|
+
<div class="topbar-left">
|
|
14
|
+
<span class="logo">🏢</span>
|
|
15
|
+
<h1>AI OPS CENTER</h1>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="topbar-right">
|
|
18
|
+
<span id="connection-status" class="status-dot offline" title="Disconnected"></span>
|
|
19
|
+
<span id="project-count">0 PROJECTS</span>
|
|
20
|
+
<button id="btn-submit" class="btn btn-primary" title="Submit Feature/Bug">+ NEW TASK</button>
|
|
21
|
+
</div>
|
|
22
|
+
</header>
|
|
23
|
+
|
|
24
|
+
<!-- Main canvas area -->
|
|
25
|
+
<div id="canvas-container">
|
|
26
|
+
<canvas id="office-canvas"></canvas>
|
|
27
|
+
|
|
28
|
+
<!-- Character detail popup (hidden by default) -->
|
|
29
|
+
<div id="char-popup" class="popup hidden">
|
|
30
|
+
<div class="popup-header">
|
|
31
|
+
<span id="popup-title"></span>
|
|
32
|
+
<button class="popup-close" onclick="document.getElementById('char-popup').classList.add('hidden')">X</button>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="popup-body">
|
|
35
|
+
<div class="popup-row"><span class="label">Project:</span> <span class="value" id="popup-project"></span></div>
|
|
36
|
+
<div class="popup-row"><span class="label">Status:</span> <span class="value" id="popup-status"></span></div>
|
|
37
|
+
<div class="popup-row"><span class="label">Model:</span> <span class="value" id="popup-model"></span></div>
|
|
38
|
+
<div class="popup-row"><span class="label">Task:</span> <span class="value" id="popup-task"></span></div>
|
|
39
|
+
<div class="popup-row"><span class="label">Duration:</span> <span class="value" id="popup-duration"></span></div>
|
|
40
|
+
<div class="popup-row popup-extra-row"><span class="value" id="popup-extra" style="color:#80a0c0;font-size:9px"></span></div>
|
|
41
|
+
</div>
|
|
42
|
+
<!-- PM actions -->
|
|
43
|
+
<div class="popup-actions hidden" id="popup-pm-menu">
|
|
44
|
+
<div class="pm-menu-item" data-action="bug">
|
|
45
|
+
<span>🐛 Report a Bug</span>
|
|
46
|
+
<span class="arrow">></span>
|
|
47
|
+
</div>
|
|
48
|
+
<div class="pm-menu-item" data-action="feature">
|
|
49
|
+
<span>⭐ Request a Feature</span>
|
|
50
|
+
<span class="arrow">></span>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="pm-menu-item" data-action="status">
|
|
53
|
+
<span>📊 Check Status</span>
|
|
54
|
+
<span class="arrow">></span>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
<!-- Architect actions -->
|
|
58
|
+
<div class="popup-actions hidden" id="popup-arch-menu">
|
|
59
|
+
<button class="btn btn-sm" id="popup-arch-docs">📐 ARCHITECTURE</button>
|
|
60
|
+
<button class="btn btn-sm" id="popup-arch-logs">📋 LOGS</button>
|
|
61
|
+
</div>
|
|
62
|
+
<!-- Coder actions -->
|
|
63
|
+
<div class="popup-actions hidden" id="popup-coder-menu">
|
|
64
|
+
<button class="btn btn-sm" id="popup-coder-review">🔍 REVIEW</button>
|
|
65
|
+
<button class="btn btn-sm" id="popup-coder-logs">📋 LOGS</button>
|
|
66
|
+
</div>
|
|
67
|
+
<!-- Deployer actions -->
|
|
68
|
+
<div class="popup-actions hidden" id="popup-deployer-menu">
|
|
69
|
+
<button class="btn btn-sm" id="popup-deployer-deploy">🚀 DEPLOY</button>
|
|
70
|
+
<button class="btn btn-sm" id="popup-deployer-logs">📋 LOGS</button>
|
|
71
|
+
</div>
|
|
72
|
+
<!-- Pipeline actions (stage-aware, shown for all roles) -->
|
|
73
|
+
<div class="popup-actions hidden" id="popup-pipeline-menu">
|
|
74
|
+
<div class="pipeline-label">⚡ PIPELINE ACTIONS</div>
|
|
75
|
+
<div class="pipeline-btns">
|
|
76
|
+
<button class="btn btn-sm btn-action" data-pipeline="approve" title="Approve code">✅ Approve</button>
|
|
77
|
+
<button class="btn btn-sm btn-action" data-pipeline="reject" title="Reject code">❌ Reject</button>
|
|
78
|
+
<button class="btn btn-sm btn-action" data-pipeline="review" title="Start review">🔍 Review</button>
|
|
79
|
+
<button class="btn btn-sm btn-action" data-pipeline="fix" title="Fix issues">🔧 Fix</button>
|
|
80
|
+
<button class="btn btn-sm btn-action" data-pipeline="deploy" title="Deploy to org">🚀 Deploy</button>
|
|
81
|
+
<button class="btn btn-sm btn-action" data-pipeline="implement" title="Run implementation">⚙️ Implement</button>
|
|
82
|
+
<button class="btn btn-sm btn-action btn-dim" data-pipeline="cleanup" title="Clear pipeline">🧹 Cleanup</button>
|
|
83
|
+
<button class="btn btn-sm btn-action btn-dim" data-pipeline="reset" title="Full reset + git checkout">🔄 Reset</button>
|
|
84
|
+
<button class="btn btn-sm btn-action btn-danger" data-pipeline="reset" title="Abandon current feature and reset to idle">🛑 Abandon</button>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<!-- Submit form modal (hidden by default) -->
|
|
90
|
+
<div id="submit-modal" class="modal hidden">
|
|
91
|
+
<div class="modal-content">
|
|
92
|
+
<div class="modal-header">
|
|
93
|
+
<h2>SUBMIT WORK ITEM</h2>
|
|
94
|
+
<button class="popup-close" onclick="document.getElementById('submit-modal').classList.add('hidden')">X</button>
|
|
95
|
+
</div>
|
|
96
|
+
<div class="modal-body">
|
|
97
|
+
<label>PROJECT
|
|
98
|
+
<select id="submit-project"></select>
|
|
99
|
+
</label>
|
|
100
|
+
<label>TYPE
|
|
101
|
+
<select id="submit-type">
|
|
102
|
+
<option value="feature">Feature</option>
|
|
103
|
+
<option value="bug">Bug Fix</option>
|
|
104
|
+
</select>
|
|
105
|
+
</label>
|
|
106
|
+
<label>MODE
|
|
107
|
+
<select id="submit-mode">
|
|
108
|
+
<option value="auto">Auto (AI runs full pipeline)</option>
|
|
109
|
+
<option value="manual">Manual (step-by-step)</option>
|
|
110
|
+
</select>
|
|
111
|
+
</label>
|
|
112
|
+
<label>DESCRIPTION
|
|
113
|
+
<textarea id="submit-desc" rows="4" placeholder="Describe the feature or bug..."></textarea>
|
|
114
|
+
</label>
|
|
115
|
+
</div>
|
|
116
|
+
<div class="modal-footer">
|
|
117
|
+
<button class="btn btn-primary" id="submit-go">SUBMIT</button>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<!-- Features / package info panel (Bug 4 & 6) -->
|
|
124
|
+
<div id="features-panel" class="popup hidden" style="min-width:260px;max-width:340px;position:absolute;">
|
|
125
|
+
<div class="popup-header">
|
|
126
|
+
<span id="fp-title">Features</span>
|
|
127
|
+
<button class="popup-close" onclick="document.getElementById('features-panel').classList.add('hidden');document.getElementById('features-panel').style.transform=''">X</button>
|
|
128
|
+
</div>
|
|
129
|
+
<div id="fp-body" class="popup-body" style="max-height:320px;overflow-y:auto;display:block"></div>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<!-- Command bar (slash commands) -->
|
|
133
|
+
<div id="command-bar" class="command-bar" style="display:none;">
|
|
134
|
+
<input type="text" id="command-input" placeholder="Type / for commands..." autocomplete="off" spellcheck="false" />
|
|
135
|
+
<div id="command-dropdown" class="command-dropdown"></div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<!-- Activity feed -->
|
|
139
|
+
<div id="activity-feed">
|
|
140
|
+
<div class="feed-header">ACTIVITY LOG</div>
|
|
141
|
+
<div id="feed-entries"></div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<!-- Load scripts as modules -->
|
|
146
|
+
<script type="module" src="js/main.js"></script>
|
|
147
|
+
</body>
|
|
148
|
+
</html>
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export class AchievementsUI {
|
|
2
|
+
constructor(container) {
|
|
3
|
+
this.container = container;
|
|
4
|
+
this.achievements = [];
|
|
5
|
+
this.unlocked = new Set();
|
|
6
|
+
this.panel = null;
|
|
7
|
+
this.visible = false;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
init(achievementDefs, unlockedIds) {
|
|
11
|
+
this.achievements = Object.values(achievementDefs);
|
|
12
|
+
this.unlocked = new Set(unlockedIds);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
render() {
|
|
16
|
+
if (this.panel) {
|
|
17
|
+
this.panel.remove();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
this.panel = document.createElement('div');
|
|
21
|
+
this.panel.style.cssText = `
|
|
22
|
+
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
|
23
|
+
width: 420px; max-height: 80vh; background: #fff; border-radius: 12px;
|
|
24
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.3); z-index: 10000; overflow-y: auto;
|
|
25
|
+
display: ${this.visible ? 'block' : 'none'}; font-family: system-ui, sans-serif;
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
const unlockedCount = this.unlocked.size;
|
|
29
|
+
const totalCount = this.achievements.length;
|
|
30
|
+
|
|
31
|
+
const header = document.createElement('div');
|
|
32
|
+
header.style.cssText = 'padding: 16px 20px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center;';
|
|
33
|
+
header.innerHTML = `
|
|
34
|
+
<div>
|
|
35
|
+
<h2 style="margin: 0; font-size: 18px;">🏆 Trophy Case</h2>
|
|
36
|
+
<p style="margin: 4px 0 0; font-size: 13px; color: #666;">${unlockedCount}/${totalCount} Achievements Unlocked</p>
|
|
37
|
+
</div>
|
|
38
|
+
<button id="achievements-close" style="background: none; border: none; font-size: 20px; cursor: pointer; color: #999;">✕</button>
|
|
39
|
+
`;
|
|
40
|
+
this.panel.appendChild(header);
|
|
41
|
+
|
|
42
|
+
const grid = document.createElement('div');
|
|
43
|
+
grid.style.cssText = 'display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; padding: 16px 20px;';
|
|
44
|
+
|
|
45
|
+
for (const achievement of this.achievements) {
|
|
46
|
+
const isUnlocked = this.unlocked.has(achievement.id);
|
|
47
|
+
const badge = document.createElement('div');
|
|
48
|
+
badge.style.cssText = `
|
|
49
|
+
text-align: center; padding: 12px 8px; border-radius: 8px; transition: transform 0.2s;
|
|
50
|
+
background: ${isUnlocked ? '#f0f9ff' : '#f5f5f5'};
|
|
51
|
+
opacity: ${isUnlocked ? '1' : '0.5'};
|
|
52
|
+
cursor: default;
|
|
53
|
+
`;
|
|
54
|
+
badge.title = achievement.description;
|
|
55
|
+
badge.innerHTML = `
|
|
56
|
+
<div style="font-size: 28px; margin-bottom: 4px;">${isUnlocked ? achievement.emoji : '❓'}</div>
|
|
57
|
+
<div style="font-size: 11px; font-weight: 600; color: ${isUnlocked ? '#333' : '#999'};">
|
|
58
|
+
${isUnlocked ? achievement.name : '???'}
|
|
59
|
+
</div>
|
|
60
|
+
`;
|
|
61
|
+
badge.addEventListener('mouseenter', () => { badge.style.transform = 'scale(1.05)'; });
|
|
62
|
+
badge.addEventListener('mouseleave', () => { badge.style.transform = 'scale(1)'; });
|
|
63
|
+
grid.appendChild(badge);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.panel.appendChild(grid);
|
|
67
|
+
this.container.appendChild(this.panel);
|
|
68
|
+
|
|
69
|
+
this.panel.querySelector('#achievements-close').addEventListener('click', () => this.toggle());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
showUnlockAnimation(achievement) {
|
|
73
|
+
const toast = document.createElement('div');
|
|
74
|
+
toast.style.cssText = `
|
|
75
|
+
position: fixed; top: 20px; right: 20px; z-index: 10001;
|
|
76
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
77
|
+
color: white; padding: 16px 24px; border-radius: 12px;
|
|
78
|
+
box-shadow: 0 4px 20px rgba(102,126,234,0.4);
|
|
79
|
+
font-family: system-ui, sans-serif; font-size: 14px;
|
|
80
|
+
animation: achievementSlideIn 0.5s ease-out, achievementFadeOut 0.5s ease-in 4.5s;
|
|
81
|
+
opacity: 1;
|
|
82
|
+
`;
|
|
83
|
+
toast.innerHTML = `
|
|
84
|
+
<div style="font-size: 12px; opacity: 0.9; margin-bottom: 4px;">🏆 Achievement Unlocked!</div>
|
|
85
|
+
<div style="font-size: 16px; font-weight: bold;">${achievement.emoji} ${achievement.name}</div>
|
|
86
|
+
<div style="font-size: 12px; opacity: 0.8; margin-top: 2px;">${achievement.description}</div>
|
|
87
|
+
`;
|
|
88
|
+
|
|
89
|
+
if (!document.querySelector('#achievement-animations')) {
|
|
90
|
+
const style = document.createElement('style');
|
|
91
|
+
style.id = 'achievement-animations';
|
|
92
|
+
style.textContent = `
|
|
93
|
+
@keyframes achievementSlideIn {
|
|
94
|
+
from { transform: translateX(120%); opacity: 0; }
|
|
95
|
+
to { transform: translateX(0); opacity: 1; }
|
|
96
|
+
}
|
|
97
|
+
@keyframes achievementFadeOut {
|
|
98
|
+
from { opacity: 1; }
|
|
99
|
+
to { opacity: 0; transform: translateY(-20px); }
|
|
100
|
+
}
|
|
101
|
+
`;
|
|
102
|
+
document.head.appendChild(style);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
document.body.appendChild(toast);
|
|
106
|
+
setTimeout(() => toast.remove(), 5000);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
toggle() {
|
|
110
|
+
this.visible = !this.visible;
|
|
111
|
+
if (this.panel) {
|
|
112
|
+
this.panel.style.display = this.visible ? 'block' : 'none';
|
|
113
|
+
} else if (this.visible) {
|
|
114
|
+
this.render();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|