feat(selfmedia): add 自媒体中心 native panel to sidebar
Browser smoke / changes (push) Canceled after 0s
Browser smoke / browser-smoke (push) Canceled after 0s
Conversation lifecycle (informational) / live-to-final (chromium, reconnect-scene-redraw, tests/browser_reconnect_scene_redraw.py) (push) Canceled after 0s
Conversation lifecycle (informational) / live-to-final (historical-transcript-hydration, tests/browser_historical_transcript_hydration.py) (push) Canceled after 0s
Conversation lifecycle (informational) / live-to-final (normal, normal, tests/browser_conversation_lifecycle.py) (push) Canceled after 0s
Conversation lifecycle (informational) / live-to-final (terminal-error, terminal-error, tests/browser_conversation_lifecycle.py) (push) Canceled after 0s
Tests / changes (push) Canceled after 0s
Tests / lint (push) Canceled after 0s
Tests / test (3.11, 0) (push) Canceled after 0s
Tests / test (3.11, 1) (push) Canceled after 0s
Tests / test (3.11, 2) (push) Canceled after 0s
Tests / test (3.11, 3) (push) Canceled after 0s
Tests / test (3.11, 4) (push) Canceled after 0s
Tests / test (3.12, 0) (push) Canceled after 0s
Tests / test (3.12, 1) (push) Canceled after 0s
Tests / test (3.12, 2) (push) Canceled after 0s
Tests / test (3.12, 3) (push) Canceled after 0s
Tests / test (3.12, 4) (push) Canceled after 0s
Tests / test (3.13, 0) (push) Canceled after 0s
Tests / test (3.13, 1) (push) Canceled after 0s
Tests / test (3.13, 2) (push) Canceled after 0s
Tests / test (3.13, 3) (push) Canceled after 0s
Tests / test (3.13, 4) (push) Canceled after 0s
Browser smoke / changes (push) Canceled after 0s
Browser smoke / browser-smoke (push) Canceled after 0s
Conversation lifecycle (informational) / live-to-final (chromium, reconnect-scene-redraw, tests/browser_reconnect_scene_redraw.py) (push) Canceled after 0s
Conversation lifecycle (informational) / live-to-final (historical-transcript-hydration, tests/browser_historical_transcript_hydration.py) (push) Canceled after 0s
Conversation lifecycle (informational) / live-to-final (normal, normal, tests/browser_conversation_lifecycle.py) (push) Canceled after 0s
Conversation lifecycle (informational) / live-to-final (terminal-error, terminal-error, tests/browser_conversation_lifecycle.py) (push) Canceled after 0s
Tests / changes (push) Canceled after 0s
Tests / lint (push) Canceled after 0s
Tests / test (3.11, 0) (push) Canceled after 0s
Tests / test (3.11, 1) (push) Canceled after 0s
Tests / test (3.11, 2) (push) Canceled after 0s
Tests / test (3.11, 3) (push) Canceled after 0s
Tests / test (3.11, 4) (push) Canceled after 0s
Tests / test (3.12, 0) (push) Canceled after 0s
Tests / test (3.12, 1) (push) Canceled after 0s
Tests / test (3.12, 2) (push) Canceled after 0s
Tests / test (3.12, 3) (push) Canceled after 0s
Tests / test (3.12, 4) (push) Canceled after 0s
Tests / test (3.13, 0) (push) Canceled after 0s
Tests / test (3.13, 1) (push) Canceled after 0s
Tests / test (3.13, 2) (push) Canceled after 0s
Tests / test (3.13, 3) (push) Canceled after 0s
Tests / test (3.13, 4) (push) Canceled after 0s
- New 'selfmedia' panel registered in MAIN_VIEW_PANELS with sidebar rail/nav buttons, main view container and mobile sidebar panel - Native in-origin rendering (no sandboxed iframe) so api() calls carry session cookie + CSRF and actually load cron data - Dashboard: stats cards, self-media cron job detection by keywords, one-click job trigger, recent activity feed, 30s auto-refresh - Quick actions open a brand-new conversation and send the command there (发布公众号/选题/写文章/多平台发布/热门排行)
This commit is contained in:
1 parent
c296673ebf
commit
5e801d74d5
3 files changed
+324
-3
No files matched your search
@@ -160,6 +160,7 @@
|
|||||||
<button class="rail-btn nav-tab has-tooltip" data-panel="profiles" onclick="switchPanel('profiles',{fromRailClick:true})" data-tooltip="Agent profiles" data-i18n-title="tab_profiles" aria-label="Agent profiles"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></button>
|
<button class="rail-btn nav-tab has-tooltip" data-panel="profiles" onclick="switchPanel('profiles',{fromRailClick:true})" data-tooltip="Agent profiles" data-i18n-title="tab_profiles" aria-label="Agent profiles"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></button>
|
||||||
<button class="rail-btn nav-tab has-tooltip" data-panel="todos" onclick="switchPanel('todos',{fromRailClick:true})" data-tooltip="Current task list" data-i18n-title="tab_todos" aria-label="Todos"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5" width="6" height="6" rx="1"/><path d="m3 17 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/></svg></button>
|
<button class="rail-btn nav-tab has-tooltip" data-panel="todos" onclick="switchPanel('todos',{fromRailClick:true})" data-tooltip="Current task list" data-i18n-title="tab_todos" aria-label="Todos"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5" width="6" height="6" rx="1"/><path d="m3 17 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/></svg></button>
|
||||||
<button class="rail-btn nav-tab has-tooltip" data-panel="insights" onclick="switchPanel('insights',{fromRailClick:true})" data-tooltip="Insights" data-i18n-title="tab_insights" aria-label="Insights"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 20V10"/><path d="M12 20V4"/><path d="M6 20v-6"/></svg></button>
|
<button class="rail-btn nav-tab has-tooltip" data-panel="insights" onclick="switchPanel('insights',{fromRailClick:true})" data-tooltip="Insights" data-i18n-title="tab_insights" aria-label="Insights"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 20V10"/><path d="M12 20V4"/><path d="M6 20v-6"/></svg></button>
|
||||||
|
<button class="rail-btn nav-tab has-tooltip" data-panel="selfmedia" onclick="switchPanel('selfmedia',{fromRailClick:true})" data-tooltip="自媒体中心" data-i18n-title="tab_selfmedia" aria-label="自媒体中心"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/><path d="M7 8h2"/><path d="M7 12h2"/></svg></button>
|
||||||
<button class="rail-btn nav-tab dashboard-link has-tooltip" id="dashboardRailBtn" data-dashboard-link style="display:none" onclick="openHermesDashboard(event)" data-tooltip="Hermes Dashboard" data-i18n-title="tab_dashboard" aria-label="Hermes Dashboard"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/></svg><span class="dashboard-external-badge" aria-hidden="true"></span></button>
|
<button class="rail-btn nav-tab dashboard-link has-tooltip" id="dashboardRailBtn" data-dashboard-link style="display:none" onclick="openHermesDashboard(event)" data-tooltip="Hermes Dashboard" data-i18n-title="tab_dashboard" aria-label="Hermes Dashboard"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/></svg><span class="dashboard-external-badge" aria-hidden="true"></span></button>
|
||||||
<button class="rail-btn nav-tab has-tooltip" data-panel="logs" onclick="switchPanel('logs',{fromRailClick:true})" data-tooltip="Logs" data-i18n-title="tab_logs" aria-label="Logs"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M8 13h8"/><path d="M8 17h8"/><path d="M8 9h2"/></svg></button>
|
<button class="rail-btn nav-tab has-tooltip" data-panel="logs" onclick="switchPanel('logs',{fromRailClick:true})" data-tooltip="Logs" data-i18n-title="tab_logs" aria-label="Logs"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M8 13h8"/><path d="M8 17h8"/><path d="M8 9h2"/></svg></button>
|
||||||
<div class="rail-spacer"></div>
|
<div class="rail-spacer"></div>
|
||||||
@@ -179,6 +180,7 @@
|
|||||||
<button class="nav-tab has-tooltip has-tooltip--bottom" data-panel="profiles" data-label="Profiles" onclick="switchPanel('profiles',{fromRailClick:true})" data-tooltip="Agent profiles" data-i18n-title="tab_profiles"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></button>
|
<button class="nav-tab has-tooltip has-tooltip--bottom" data-panel="profiles" data-label="Profiles" onclick="switchPanel('profiles',{fromRailClick:true})" data-tooltip="Agent profiles" data-i18n-title="tab_profiles"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></button>
|
||||||
<button class="nav-tab has-tooltip has-tooltip--bottom" data-panel="todos" data-label="Todos" onclick="switchPanel('todos',{fromRailClick:true})" data-tooltip="Current task list" data-i18n-title="tab_todos"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5" width="6" height="6" rx="1"/><path d="m3 17 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/></svg></button>
|
<button class="nav-tab has-tooltip has-tooltip--bottom" data-panel="todos" data-label="Todos" onclick="switchPanel('todos',{fromRailClick:true})" data-tooltip="Current task list" data-i18n-title="tab_todos"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5" width="6" height="6" rx="1"/><path d="m3 17 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/></svg></button>
|
||||||
<button class="nav-tab has-tooltip has-tooltip--bottom" data-panel="insights" data-label="Insights" onclick="switchPanel('insights',{fromRailClick:true})" data-tooltip="Insights" data-i18n-title="tab_insights"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 20V10"/><path d="M12 20V4"/><path d="M6 20v-6"/></svg></button>
|
<button class="nav-tab has-tooltip has-tooltip--bottom" data-panel="insights" data-label="Insights" onclick="switchPanel('insights',{fromRailClick:true})" data-tooltip="Insights" data-i18n-title="tab_insights"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 20V10"/><path d="M12 20V4"/><path d="M6 20v-6"/></svg></button>
|
||||||
|
<button class="nav-tab has-tooltip has-tooltip--bottom" data-panel="selfmedia" data-label="自媒体中心" onclick="switchPanel('selfmedia',{fromRailClick:true})" data-tooltip="自媒体中心" data-i18n-title="tab_selfmedia"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/><path d="M7 8h2"/><path d="M7 12h2"/></svg></button>
|
||||||
<button class="nav-tab dashboard-link has-tooltip has-tooltip--bottom" id="dashboardMobileBtn" data-dashboard-link data-label="Dashboard" style="display:none" onclick="openHermesDashboard(event)" data-tooltip="Hermes Dashboard" data-i18n-title="tab_dashboard"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/></svg><span class="dashboard-external-badge" aria-hidden="true"></span></button>
|
<button class="nav-tab dashboard-link has-tooltip has-tooltip--bottom" id="dashboardMobileBtn" data-dashboard-link data-label="Dashboard" style="display:none" onclick="openHermesDashboard(event)" data-tooltip="Hermes Dashboard" data-i18n-title="tab_dashboard"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/></svg><span class="dashboard-external-badge" aria-hidden="true"></span></button>
|
||||||
<button class="nav-tab has-tooltip has-tooltip--bottom" data-panel="logs" data-label="Logs" onclick="switchPanel('logs',{fromRailClick:true})" data-tooltip="Logs" data-i18n-title="tab_logs"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M8 13h8"/><path d="M8 17h8"/><path d="M8 9h2"/></svg></button>
|
<button class="nav-tab has-tooltip has-tooltip--bottom" data-panel="logs" data-label="Logs" onclick="switchPanel('logs',{fromRailClick:true})" data-tooltip="Logs" data-i18n-title="tab_logs"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M8 13h8"/><path d="M8 17h8"/><path d="M8 9h2"/></svg></button>
|
||||||
<!-- Settings button mirrored here for mobile (rail is desktop-only via @media >=768px). Keep in sync with rail entry. -->
|
<!-- Settings button mirrored here for mobile (rail is desktop-only via @media >=768px). Keep in sync with rail entry. -->
|
||||||
@@ -340,6 +342,20 @@
|
|||||||
<button type="button" class="logs-copy" id="logsCopyAll" onclick="copyLogsAll()" data-i18n="logs_copy_all">Copy all</button>
|
<button type="button" class="logs-copy" id="logsCopyAll" onclick="copyLogsAll()" data-i18n="logs_copy_all">Copy all</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Self-Media Center sidebar panel -->
|
||||||
|
<div class="panel-view" id="panelSelfmedia">
|
||||||
|
<div class="panel-head">
|
||||||
|
<span>自媒体中心</span>
|
||||||
|
</div>
|
||||||
|
<div class="panel-head-sub">快捷操作</div>
|
||||||
|
<div style="flex:1;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:4px" id="smSidebarActions">
|
||||||
|
<button class="side-menu-item" data-smcmd="使用 wechat-oa-publishing 技能发布微信公众号文章">📱 发布公众号</button>
|
||||||
|
<button class="side-menu-item" data-smcmd="加载 aws-wechat-article-topics 技能生成今日选题">💡 生成今日选题</button>
|
||||||
|
<button class="side-menu-item" data-smcmd="使用 wechat-article-writing 模板写一篇公众号文章">✍️ 写一篇文章</button>
|
||||||
|
<button class="side-menu-item" data-smcmd="使用 multi-platform-publishing 技能进行多平台发布">🌐 多平台发布</button>
|
||||||
|
<button class="side-menu-item" data-smcmd="执行每日热门软件排行任务(daily-software-articles-from-ranking)">📊 热门排行</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<!-- Settings panel (menu list; actual panes render in .main) -->
|
<!-- Settings panel (menu list; actual panes render in .main) -->
|
||||||
<div class="panel-view" id="panelSettings">
|
<div class="panel-view" id="panelSettings">
|
||||||
<div class="panel-head">
|
<div class="panel-head">
|
||||||
@@ -930,6 +946,15 @@
|
|||||||
<div id="pluginPageContainer"></div>
|
<div id="pluginPageContainer"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Self-Media Center main view -->
|
||||||
|
<div id="mainSelfmedia" class="main-view">
|
||||||
|
<div class="main-view-header">
|
||||||
|
<div class="main-view-title">自媒体中心</div>
|
||||||
|
</div>
|
||||||
|
<div class="main-view-body">
|
||||||
|
<div id="selfmediaContainer" style="flex:1;overflow-y:auto;padding:16px"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div id="mainSettings" class="main-view">
|
<div id="mainSettings" class="main-view">
|
||||||
<div class="settings-main">
|
<div class="settings-main">
|
||||||
<div class="settings-pane active" id="settingsPaneConversation">
|
<div class="settings-pane active" id="settingsPaneConversation">
|
||||||
|
|||||||
+294
-1
@@ -45,7 +45,7 @@ const APP_TITLEBAR_KEYS = {
|
|||||||
memory: 'tab_memory', workspaces: 'tab_workspaces',
|
memory: 'tab_memory', workspaces: 'tab_workspaces',
|
||||||
profiles: 'tab_profiles', todos: 'tab_todos', insights: 'tab_insights', logs: 'tab_logs', settings: 'tab_settings',
|
profiles: 'tab_profiles', todos: 'tab_todos', insights: 'tab_insights', logs: 'tab_logs', settings: 'tab_settings',
|
||||||
};
|
};
|
||||||
const MAIN_VIEW_PANELS = ['settings','skills','memory','tasks','kanban','workspaces','profiles','insights','logs','plugin'];
|
const MAIN_VIEW_PANELS = ['settings','skills','memory','tasks','kanban','workspaces','profiles','insights','logs','plugin','selfmedia'];
|
||||||
const MAIN_VIEW_SIDEBAR_PANEL_FALLBACKS = { plugin: 'settings' };
|
const MAIN_VIEW_SIDEBAR_PANEL_FALLBACKS = { plugin: 'settings' };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -460,6 +460,7 @@ async function switchPanel(name, opts = {}) {
|
|||||||
if (nextPanel === 'todos') loadTodos();
|
if (nextPanel === 'todos') loadTodos();
|
||||||
if (nextPanel === 'insights') await loadInsights();
|
if (nextPanel === 'insights') await loadInsights();
|
||||||
if (nextPanel === 'logs') await loadLogs();
|
if (nextPanel === 'logs') await loadLogs();
|
||||||
|
if (nextPanel === 'selfmedia') await loadSelfMediaPanel();
|
||||||
_syncLogsAutoRefresh();
|
_syncLogsAutoRefresh();
|
||||||
if (typeof _syncSystemHealthMonitorVisibility === 'function') _syncSystemHealthMonitorVisibility();
|
if (typeof _syncSystemHealthMonitorVisibility === 'function') _syncSystemHealthMonitorVisibility();
|
||||||
if (nextPanel === 'settings') {
|
if (nextPanel === 'settings') {
|
||||||
@@ -11026,6 +11027,298 @@ async function _loadPluginPage(path, label) {
|
|||||||
_currentPluginPage = { path, label };
|
_currentPluginPage = { path, label };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Self-Media Center panel ──────────────────────────────────────────────────
|
||||||
|
// Native WebUI panel (no iframe): runs in the app origin so api() carries the
|
||||||
|
// session cookie + CSRF token. Quick actions open a fresh conversation and send
|
||||||
|
// the command there instead of hijacking the current chat.
|
||||||
|
|
||||||
|
const SM_KEYWORDS = [
|
||||||
|
'公众号','wechat','weixin','文章','publish','发文',
|
||||||
|
'自媒体','content','选题','写作','writing','article',
|
||||||
|
'头条','toutiao','b站','bilibili','bili','值得买','smzdm',
|
||||||
|
'知乎','zhihu','小红书','xhs','redbook',
|
||||||
|
'post','social','media','推广','营销','运营',
|
||||||
|
'自动','daily','日报','周报','ranking','排行',
|
||||||
|
'视频','video','图文','图片','image','cover','封面',
|
||||||
|
'评论','comment','回复','reply',
|
||||||
|
'seo','关键词','keyword','软件','app',
|
||||||
|
'wechat-oa','weixin-','douyin','multi-platform','cross-post',
|
||||||
|
'aws-wechat','aws-'
|
||||||
|
];
|
||||||
|
|
||||||
|
function _smIsSelfMedia(job){
|
||||||
|
if(!job||!job.name) return false;
|
||||||
|
const n=String(job.name).toLowerCase();
|
||||||
|
return SM_KEYWORDS.some(k=>n.includes(k.toLowerCase()));
|
||||||
|
}
|
||||||
|
|
||||||
|
function _smAgo(t){
|
||||||
|
if(!t) return '';
|
||||||
|
const d=t instanceof Date?t:new Date(t);
|
||||||
|
if(isNaN(d.getTime())) return '';
|
||||||
|
const mins=Math.floor((Date.now()-d.getTime())/60000);
|
||||||
|
if(mins<1) return '刚刚';
|
||||||
|
if(mins<60) return mins+'分钟前';
|
||||||
|
const h=Math.floor(mins/60);
|
||||||
|
if(h<24) return h+'小时前';
|
||||||
|
const days=Math.floor(h/24);
|
||||||
|
if(days<7) return days+'天前';
|
||||||
|
return d.toLocaleDateString('zh-CN',{month:'short',day:'numeric'});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _smEsc(s){
|
||||||
|
const d=document.createElement('div');
|
||||||
|
d.textContent=s==null?'':String(s);
|
||||||
|
return d.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open a brand-new conversation and send the command there.
|
||||||
|
let _smLaunchInFlight=false;
|
||||||
|
async function openSelfMediaCommand(command){
|
||||||
|
if(!command) return;
|
||||||
|
if(_smLaunchInFlight){ if(typeof showToast==='function') showToast('上一个操作还在启动中…',1800); return; }
|
||||||
|
_smLaunchInFlight=true;
|
||||||
|
try{
|
||||||
|
if(typeof newSession==='function') await newSession();
|
||||||
|
if(typeof switchPanel==='function') switchPanel('chat');
|
||||||
|
const box=document.getElementById('msg');
|
||||||
|
if(box){
|
||||||
|
box.value=command;
|
||||||
|
box.dispatchEvent(new Event('input',{bubbles:true}));
|
||||||
|
box.focus();
|
||||||
|
}
|
||||||
|
if(typeof send==='function'){
|
||||||
|
await new Promise(r=>setTimeout(r,400));
|
||||||
|
await send();
|
||||||
|
}
|
||||||
|
if(typeof showToast==='function') showToast('已在新对话中执行:'+command.slice(0,32)+'…',2400);
|
||||||
|
}catch(e){
|
||||||
|
if(typeof showToast==='function') showToast('启动失败:'+(e&&e.message||e),3000);
|
||||||
|
}finally{
|
||||||
|
setTimeout(()=>{_smLaunchInFlight=false;},1200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _smBindSidebarActions(){
|
||||||
|
const wrap=document.getElementById('smSidebarActions');
|
||||||
|
if(!wrap||wrap.dataset.smBound==='1') return;
|
||||||
|
wrap.dataset.smBound='1';
|
||||||
|
wrap.addEventListener('click',ev=>{
|
||||||
|
const btn=ev.target.closest('[data-smcmd]');
|
||||||
|
if(!btn) return;
|
||||||
|
openSelfMediaCommand(btn.dataset.smcmd);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSelfMediaPanel(){
|
||||||
|
const container=document.getElementById('selfmediaContainer');
|
||||||
|
if(!container) return;
|
||||||
|
_smBindSidebarActions();
|
||||||
|
|
||||||
|
container.innerHTML=`
|
||||||
|
<div class="sm-dash">
|
||||||
|
<div class="sm-head">
|
||||||
|
<h1>📡 自媒体中心</h1>
|
||||||
|
<div class="sm-sub">Hermes 自媒体自动化管理面板</div>
|
||||||
|
<div class="sm-conn"><span class="sm-dot checking"></span><span class="sm-conn-text">加载中…</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="sm-stats">
|
||||||
|
<div class="sm-stat"><div class="sm-stat-v" id="smStatTotal">-</div><div class="sm-stat-l">全部任务</div></div>
|
||||||
|
<div class="sm-stat"><div class="sm-stat-v" id="smStatActive">-</div><div class="sm-stat-l">运行中</div></div>
|
||||||
|
<div class="sm-stat"><div class="sm-stat-v" id="smStatSm">-</div><div class="sm-stat-l">自媒体</div></div>
|
||||||
|
<div class="sm-stat"><div class="sm-stat-v" id="smStatToday">-</div><div class="sm-stat-l">今日执行</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="sm-sec">
|
||||||
|
<div class="sm-sec-title">⚡ 快捷操作</div>
|
||||||
|
<div class="sm-actions" id="smActions">
|
||||||
|
<button class="sm-btn sm-btn-primary" data-cmd="使用 wechat-oa-publishing 技能发布微信公众号文章">📱 发布公众号</button>
|
||||||
|
<button class="sm-btn" data-cmd="加载 aws-wechat-article-topics 技能生成今日选题">💡 生成今日选题</button>
|
||||||
|
<button class="sm-btn" data-cmd="使用 wechat-article-writing 模板写一篇公众号文章">✍️ 写一篇文章</button>
|
||||||
|
<button class="sm-btn" data-cmd="使用 multi-platform-publishing 技能进行多平台发布">🌐 多平台发布</button>
|
||||||
|
<button class="sm-btn" data-cmd="执行每日热门软件排行任务(daily-software-articles-from-ranking)">📊 热门排行</button>
|
||||||
|
<button class="sm-btn" data-cmd="__refresh__">🔄 刷新仪表盘</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sm-sec">
|
||||||
|
<div class="sm-sec-head">
|
||||||
|
<div class="sm-sec-title">📋 自媒体任务 <span class="sm-count" id="smCount"></span></div>
|
||||||
|
<label class="sm-toggle"><input type="checkbox" id="smShowAll"> 显示全部</label>
|
||||||
|
</div>
|
||||||
|
<div id="smJobs"><div class="sm-loading"><div class="sm-spin"></div>加载中…</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="sm-sec">
|
||||||
|
<div class="sm-sec-head"><div class="sm-sec-title">🕐 最近执行</div></div>
|
||||||
|
<div id="smRecent"><div class="sm-loading"><div class="sm-spin"></div>加载中…</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
.sm-dash{color:var(--text);font-size:14px;line-height:1.5}
|
||||||
|
.sm-dash .sm-head{margin-bottom:22px}
|
||||||
|
.sm-dash .sm-head h1{font-size:20px;font-weight:600;margin:0}
|
||||||
|
.sm-dash .sm-sub{font-size:12px;color:var(--muted);margin-top:3px}
|
||||||
|
.sm-dash .sm-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px;margin-bottom:22px}
|
||||||
|
.sm-dash .sm-stat{background:var(--surface);border:1px solid var(--border);border-radius:9px;padding:14px}
|
||||||
|
.sm-dash .sm-stat-v{font-size:26px;font-weight:700;color:var(--accent);line-height:1.2}
|
||||||
|
.sm-dash .sm-stat-l{font-size:11px;color:var(--muted);margin-top:4px;letter-spacing:.5px}
|
||||||
|
.sm-dash .sm-sec{margin-bottom:22px}
|
||||||
|
.sm-dash .sm-sec-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px}
|
||||||
|
.sm-dash .sm-sec-title{font-size:14px;font-weight:600}
|
||||||
|
.sm-dash .sm-count{color:var(--muted);font-weight:400;font-size:12px;margin-left:6px}
|
||||||
|
.sm-dash .sm-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
|
||||||
|
.sm-dash .sm-btn{display:inline-flex;align-items:center;gap:6px;padding:8px 14px;border-radius:8px;font-size:12px;font-weight:500;border:1px solid var(--border);background:var(--surface);color:var(--text);cursor:pointer;transition:border-color .15s,color .15s}
|
||||||
|
.sm-dash .sm-btn:hover{border-color:var(--accent);color:var(--accent)}
|
||||||
|
.sm-dash .sm-btn-primary{background:var(--accent);border-color:var(--accent);color:#0D0D1A}
|
||||||
|
.sm-dash .sm-btn:disabled{opacity:.55;cursor:not-allowed}
|
||||||
|
.sm-dash .sm-jobs{display:flex;flex-direction:column;gap:6px}
|
||||||
|
.sm-dash .sm-job{background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:12px 14px;display:flex;align-items:center;justify-content:space-between;gap:10px}
|
||||||
|
.sm-dash .sm-job.is-sm{border-left:3px solid var(--accent)}
|
||||||
|
.sm-dash .sm-job-info{flex:1;min-width:0}
|
||||||
|
.sm-dash .sm-job-name{font-size:13px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||||
|
.sm-dash .sm-job-meta{font-size:10px;color:var(--muted);margin-top:3px;display:flex;gap:10px;flex-wrap:wrap}
|
||||||
|
.sm-dash .sm-badge{display:inline-flex;align-items:center;gap:3px;padding:2px 8px;border-radius:999px;font-size:10px;font-weight:500}
|
||||||
|
.sm-dash .sm-badge.idle{background:rgba(134,192,139,.13);color:var(--success)}
|
||||||
|
.sm-dash .sm-badge.paused{background:rgba(224,177,93,.13);color:var(--warning)}
|
||||||
|
.sm-dash .sm-badge.running{background:rgba(198,172,143,.13);color:var(--accent)}
|
||||||
|
.sm-dash .sm-run{padding:3px 9px;font-size:11px;border-radius:6px;border:1px solid var(--border);background:var(--surface);color:var(--text);cursor:pointer}
|
||||||
|
.sm-dash .sm-run:hover{border-color:var(--accent);color:var(--accent)}
|
||||||
|
.sm-dash .sm-empty{text-align:center;padding:30px 16px;color:var(--muted);font-size:13px}
|
||||||
|
.sm-dash .sm-loading{text-align:center;padding:30px;color:var(--muted);font-size:12px}
|
||||||
|
.sm-dash .sm-spin{display:inline-block;width:20px;height:20px;border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:smspin .8s linear infinite;margin-bottom:10px}
|
||||||
|
@keyframes smspin{to{transform:rotate(360deg)}}
|
||||||
|
.sm-dash .sm-err{background:rgba(248,113,113,.08);border:1px solid rgba(248,113,113,.25);border-radius:8px;padding:10px 14px;color:var(--error);font-size:12px}
|
||||||
|
.sm-dash .sm-acts{display:flex;flex-direction:column;gap:4px}
|
||||||
|
.sm-dash .sm-act{background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:8px 12px;font-size:11px;color:var(--muted);display:flex;align-items:center;gap:8px}
|
||||||
|
.sm-dash .sm-act-time{flex-shrink:0;min-width:46px}
|
||||||
|
.sm-dash .sm-act-name{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||||
|
.sm-dash .sm-conn{display:flex;align-items:center;gap:6px;margin-top:7px;font-size:10px;color:var(--muted)}
|
||||||
|
.sm-dash .sm-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0}
|
||||||
|
.sm-dash .sm-dot.checking{background:var(--warning)}
|
||||||
|
.sm-dash .sm-dot.connected{background:var(--success)}
|
||||||
|
.sm-dash .sm-dot.error{background:var(--error)}
|
||||||
|
.sm-dash .sm-toggle{font-size:11px;color:var(--muted);display:inline-flex;align-items:center;gap:4px;cursor:pointer}
|
||||||
|
.sm-dash .sm-toggle input{accent-color:var(--accent)}
|
||||||
|
</style>`;
|
||||||
|
|
||||||
|
const dash=container.querySelector('.sm-dash');
|
||||||
|
|
||||||
|
async function smLoad(){
|
||||||
|
const dot=dash.querySelector('.sm-dot');
|
||||||
|
const txt=dash.querySelector('.sm-conn-text');
|
||||||
|
if(dot) dot.className='sm-dot checking';
|
||||||
|
if(txt) txt.textContent='加载中…';
|
||||||
|
|
||||||
|
let jobs=[], recent=[];
|
||||||
|
try{
|
||||||
|
const data=await api('/api/crons');
|
||||||
|
jobs=Array.isArray(data.jobs)?data.jobs:[];
|
||||||
|
try{
|
||||||
|
const st=await api('/api/crons/status');
|
||||||
|
const running=(st&&st.running)||{};
|
||||||
|
jobs.forEach(j=>{j.running=!!running[j.id];});
|
||||||
|
}catch(_){}
|
||||||
|
try{
|
||||||
|
const rec=await api('/api/crons/recent');
|
||||||
|
recent=Array.isArray(rec.completions)?rec.completions:[];
|
||||||
|
}catch(_){}
|
||||||
|
}catch(e){
|
||||||
|
if(dot) dot.className='sm-dot error';
|
||||||
|
if(txt) txt.textContent='连接失败';
|
||||||
|
dash.querySelector('#smJobs').innerHTML='<div class="sm-err">⚠️ 加载失败:'+_smEsc(e&&e.message||e)+'</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const smJobs=jobs.filter(_smIsSelfMedia);
|
||||||
|
const dayStart=new Date(); dayStart.setHours(0,0,0,0);
|
||||||
|
const todayRuns=recent.filter(r=>r.completed_at&&new Date(r.completed_at*1000)>=dayStart);
|
||||||
|
|
||||||
|
dash.querySelector('#smStatTotal').textContent=jobs.length;
|
||||||
|
dash.querySelector('#smStatActive').textContent=jobs.filter(j=>j.running).length;
|
||||||
|
dash.querySelector('#smStatSm').textContent=smJobs.length+'/'+jobs.length;
|
||||||
|
dash.querySelector('#smStatToday').textContent=todayRuns.length;
|
||||||
|
dash.querySelector('#smCount').textContent='('+smJobs.length+'/'+jobs.length+')';
|
||||||
|
if(dot) dot.className='sm-dot connected';
|
||||||
|
if(txt) txt.textContent='已连接 · '+smJobs.length+' 个自媒体任务';
|
||||||
|
|
||||||
|
// jobs
|
||||||
|
const showAll=dash.querySelector('#smShowAll').checked;
|
||||||
|
const list=showAll?jobs:smJobs;
|
||||||
|
const jbox=dash.querySelector('#smJobs');
|
||||||
|
if(!list.length){
|
||||||
|
jbox.innerHTML='<div class="sm-empty">'+(showAll?'暂无 cron 任务':'未识别到自媒体任务')+'<br><span style="font-size:11px">任务名含「公众号 / wechat / publish」等关键词会自动归类</span></div>';
|
||||||
|
}else{
|
||||||
|
jbox.innerHTML='<div class="sm-jobs"></div>';
|
||||||
|
const wrap=jbox.querySelector('.sm-jobs');
|
||||||
|
list.forEach(job=>{
|
||||||
|
const isSm=_smIsSelfMedia(job);
|
||||||
|
const badge=job.paused?['paused','已暂停']:job.running?['running','运行中']:['idle','待命中'];
|
||||||
|
const el=document.createElement('div');
|
||||||
|
el.className='sm-job'+(isSm?' is-sm':'');
|
||||||
|
el.innerHTML='<div class="sm-job-info"><div class="sm-job-name">'+(isSm?'📱 ':'')+_smEsc(job.name||'未命名')+
|
||||||
|
'</div><div class="sm-job-meta"><span>🕐 '+_smEsc(job.schedule||(job.no_agent?'脚本':'手动'))+
|
||||||
|
'</span><span>📦 '+(job.no_agent?'脚本':'Agent')+'</span>'+(job.profile?'<span>👤 '+_smEsc(job.profile)+'</span>':'')+
|
||||||
|
'</div></div><div><span class="sm-badge '+badge[0]+'">● '+badge[1]+'</span></div>'+
|
||||||
|
'<button class="sm-run" data-id="'+_smEsc(job.id)+'" title="立即执行">▶</button>';
|
||||||
|
wrap.appendChild(el);
|
||||||
|
});
|
||||||
|
wrap.querySelectorAll('.sm-run').forEach(btn=>{
|
||||||
|
btn.addEventListener('click',async function(){
|
||||||
|
const id=this.dataset.id;
|
||||||
|
this.disabled=true; this.textContent='⋯';
|
||||||
|
try{
|
||||||
|
await api('/api/crons/run',{method:'POST',body:JSON.stringify({job_id:id})});
|
||||||
|
this.textContent='✓';
|
||||||
|
if(typeof showToast==='function') showToast('任务已触发',1600);
|
||||||
|
setTimeout(()=>{this.textContent='▶';this.disabled=false;},2000);
|
||||||
|
setTimeout(smLoad,3000);
|
||||||
|
}catch(e){
|
||||||
|
this.textContent='✗'; this.disabled=false;
|
||||||
|
if(typeof showToast==='function') showToast('触发失败:'+(e&&e.message||e),3000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// recent
|
||||||
|
const rbox=dash.querySelector('#smRecent');
|
||||||
|
if(!recent.length){
|
||||||
|
rbox.innerHTML='<div class="sm-empty" style="padding:16px;font-size:12px">暂无执行记录</div>';
|
||||||
|
}else{
|
||||||
|
rbox.innerHTML='<div class="sm-acts"></div>';
|
||||||
|
const wrap=rbox.querySelector('.sm-acts');
|
||||||
|
recent.slice(0,20).forEach(r=>{
|
||||||
|
const ok=r.status!=='failed'&&r.status!=='error';
|
||||||
|
const el=document.createElement('div');
|
||||||
|
el.className='sm-act';
|
||||||
|
el.innerHTML='<span class="sm-act-time">'+_smEsc(r.completed_at?_smAgo(new Date(r.completed_at*1000)):'进行中')+
|
||||||
|
'</span><span class="sm-act-name">'+_smEsc(r.name||r.job_name||'未知')+
|
||||||
|
'</span><span style="color:'+(ok?'var(--success)':'var(--error)')+'">'+(ok?'✓':'✗')+'</span>';
|
||||||
|
wrap.appendChild(el);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions=dash.querySelector('#smActions');
|
||||||
|
actions.addEventListener('click',ev=>{
|
||||||
|
const btn=ev.target.closest('[data-cmd]');
|
||||||
|
if(!btn) return;
|
||||||
|
const cmd=btn.dataset.cmd;
|
||||||
|
if(cmd==='__refresh__'){ smLoad(); return; }
|
||||||
|
const label=btn.textContent;
|
||||||
|
btn.disabled=true; btn.textContent='⏳ 启动中…';
|
||||||
|
openSelfMediaCommand(cmd).finally(()=>{
|
||||||
|
setTimeout(()=>{btn.disabled=false;btn.textContent=label;},2200);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
dash.querySelector('#smShowAll').addEventListener('change',smLoad);
|
||||||
|
|
||||||
|
await smLoad();
|
||||||
|
if(window._smTimer) clearInterval(window._smTimer);
|
||||||
|
window._smTimer=setInterval(()=>{
|
||||||
|
const el=document.getElementById('selfmediaContainer');
|
||||||
|
if(el&&el.offsetParent!==null) smLoad();
|
||||||
|
},30000);
|
||||||
|
}
|
||||||
|
|
||||||
// ── Providers panel ─────────────────────────────────────────────────────────
|
// ── Providers panel ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const _providerCardEls = new Map(); // providerId → entry used by save/remove/test handlers
|
const _providerCardEls = new Map(); // providerId → entry used by save/remove/test handlers
|
||||||
|
|||||||
+5
-2
@@ -4941,7 +4941,7 @@ main.main > #mainWorkspaces,
|
|||||||
main.main > #mainProfiles,
|
main.main > #mainProfiles,
|
||||||
main.main > #mainInsights,
|
main.main > #mainInsights,
|
||||||
main.main > #mainLogs{display:none;}
|
main.main > #mainLogs{display:none;}
|
||||||
main.main:not(.showing-settings):not(.showing-skills):not(.showing-memory):not(.showing-tasks):not(.showing-kanban):not(.showing-workspaces):not(.showing-profiles):not(.showing-insights):not(.showing-logs):not(.showing-plugin) > #mainChat{display:flex;}
|
main.main:not(.showing-settings):not(.showing-skills):not(.showing-memory):not(.showing-tasks):not(.showing-kanban):not(.showing-workspaces):not(.showing-profiles):not(.showing-insights):not(.showing-logs):not(.showing-plugin):not(.showing-selfmedia) > #mainChat{display:flex;}
|
||||||
main.main.showing-settings > #mainSettings{display:flex;overflow-y:auto;}
|
main.main.showing-settings > #mainSettings{display:flex;overflow-y:auto;}
|
||||||
main.main.showing-skills > #mainSkills{display:flex;}
|
main.main.showing-skills > #mainSkills{display:flex;}
|
||||||
main.main.showing-memory > #mainMemory{display:flex;}
|
main.main.showing-memory > #mainMemory{display:flex;}
|
||||||
@@ -4951,8 +4951,11 @@ main.main.showing-workspaces > #mainWorkspaces{display:flex;}
|
|||||||
main.main.showing-profiles > #mainProfiles{display:flex;}
|
main.main.showing-profiles > #mainProfiles{display:flex;}
|
||||||
main.main.showing-logs > #mainLogs{display:flex;}
|
main.main.showing-logs > #mainLogs{display:flex;}
|
||||||
main.main.showing-plugin > #mainPlugin{display:flex;}
|
main.main.showing-plugin > #mainPlugin{display:flex;}
|
||||||
main.main > #mainPlugin{display:none;}
|
main.main.showing-selfmedia > #mainSelfmedia{display:flex;}
|
||||||
|
main.main > #mainPlugin,
|
||||||
|
main.main > #mainSelfmedia{display:none;}
|
||||||
#mainPlugin .main-view-body{flex:1;display:flex;flex-direction:column;overflow:hidden;}
|
#mainPlugin .main-view-body{flex:1;display:flex;flex-direction:column;overflow:hidden;}
|
||||||
|
#mainSelfmedia .main-view-body{flex:1;display:flex;flex-direction:column;overflow:hidden;}
|
||||||
#pluginPageContainer{flex:1;display:flex;flex-direction:column;min-height:0;}
|
#pluginPageContainer{flex:1;display:flex;flex-direction:column;min-height:0;}
|
||||||
#mainSettings{overflow-y:auto;}
|
#mainSettings{overflow-y:auto;}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user