diff --git a/server/routes/company.js b/server/routes/company.js
index b9a1f68..ad04b30 100644
--- a/server/routes/company.js
+++ b/server/routes/company.js
@@ -222,6 +222,59 @@ router.post('/templates/:templateId/train', async (req, res) => {
}
});
+// GET /api/company/templates/:templateId/logs — 获取模板相关的 agent-creator / innovator 执行日志
+router.get('/templates/:templateId/logs', (req, res) => {
+ try {
+ const path = require('path');
+ const fs = require('fs');
+ const templateId = req.params.templateId;
+ const AGENTS_DIR = path.resolve(__dirname, '../../data/system/agents');
+ const logs = [];
+
+ // 扫描 agent-creator 和 innovator 的对话,匹配 tpl-gen-{id} 和 tpl-train-{id}
+ const scanAgents = [
+ { agentId: 'agent-creator', prefix: `tpl-gen-${templateId}`, type: 'generate', label: '🤖 Agent Creator' },
+ { agentId: 'innovator', prefix: `tpl-train-${templateId}`, type: 'train', label: '🎓 Innovator' },
+ ];
+
+ for (const scan of scanAgents) {
+ const convDir = path.join(AGENTS_DIR, scan.agentId, 'conversations');
+ if (!fs.existsSync(convDir)) continue;
+
+ const dirs = fs.readdirSync(convDir, { withFileTypes: true })
+ .filter(d => d.isDirectory() && d.name.startsWith(scan.prefix));
+
+ for (const d of dirs) {
+ const metaPath = path.join(convDir, d.name, 'meta.json');
+ const histPath = path.join(convDir, d.name, 'history.json');
+ let meta = {}, history = [];
+ try { meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8')); } catch {}
+ try { history = JSON.parse(fs.readFileSync(histPath, 'utf-8')); } catch {}
+
+ // 提取最后一条 assistant 消息作为摘要
+ const lastAssistant = [...history].reverse().find(m => m.role === 'assistant');
+
+ logs.push({
+ conversationId: d.name,
+ agentId: scan.agentId,
+ type: scan.type,
+ label: scan.label,
+ createdAt: meta.createdAt || null,
+ messageCount: history.length,
+ summary: lastAssistant ? (lastAssistant.content || '').slice(0, 500) : '(执行中...)',
+ status: lastAssistant ? 'done' : 'running',
+ });
+ }
+ }
+
+ // 按时间倒序
+ logs.sort((a, b) => new Date(b.createdAt || 0).getTime() - new Date(a.createdAt || 0).getTime());
+ res.json(logs);
+ } catch (err) {
+ res.status(500).json({ error: err.message });
+ }
+});
+
// GET /api/company/templates/:templateId/files
router.get('/templates/:templateId/files', (req, res) => {
try {
diff --git a/web/src/views/TemplateAgents.vue b/web/src/views/TemplateAgents.vue
index 0cfbe75..3fb7ae2 100644
--- a/web/src/views/TemplateAgents.vue
+++ b/web/src/views/TemplateAgents.vue
@@ -121,6 +121,26 @@
+
+
+
📦
@@ -160,6 +180,7 @@ const newTemplate = ref({ id: '', name: '', type: 'skill', description: '' })
// Action state
const actionStatus = ref(null)
const actionMessage = ref('')
+const templateLogs = ref([])
const fileDirty = computed(() => editFileContent.value !== null && filePreviewContent.value !== null && editFileContent.value !== filePreviewContent.value)
@@ -228,6 +249,7 @@ function selectTemplate(id) {
editFileContent.value = null;
filePreviewSize.value = 0;
actionStatus.value = null;
+ templateLogs.value = [];
router.push(`/templates/${id}`);
}
@@ -283,12 +305,16 @@ async function triggerGenerate() {
description: selectedTemplate.value?.description || '',
})
actionMessage.value = data.message
- // Auto-refresh after a delay
- setTimeout(async () => {
+ // Auto-refresh logs and files
+ const pollId = setInterval(async () => {
+ await loadTemplateLogs(selectedTemplateId.value)
await loadTemplateFiles(selectedTemplateId.value)
+ }, 10000)
+ setTimeout(() => {
+ clearInterval(pollId)
actionStatus.value = 'done'
actionMessage.value = 'Agent Creator 已完成(请检查文件树)'
- }, 30000)
+ }, 60000)
} catch (e) {
actionStatus.value = 'error'
actionMessage.value = e.response?.data?.error || e.message
@@ -301,18 +327,35 @@ async function triggerTrain() {
try {
const { data } = await axios.post(`/api/company/templates/${selectedTemplateId.value}/train`)
actionMessage.value = data.message
+ const pollId = setInterval(async () => {
+ await loadTemplateLogs(selectedTemplateId.value)
+ await loadTemplateFiles(selectedTemplateId.value)
+ }, 15000)
setTimeout(async () => {
+ clearInterval(pollId)
await loadTemplates()
- await loadTemplateFiles(selectedTemplateId.value)
actionStatus.value = 'done'
actionMessage.value = 'Innovator 训练已触发(请稍后查看分数更新)'
- }, 60000)
+ }, 120000)
} catch (e) {
actionStatus.value = 'error'
actionMessage.value = e.response?.data?.error || e.message
}
}
+async function loadTemplateLogs(id) {
+ try {
+ const { data } = await axios.get(`/api/company/templates/${id}/logs`)
+ templateLogs.value = data.map(l => ({ ...l, _expanded: false }))
+ } catch (e) { console.error(e) }
+}
+
+function formatLogTime(ts) {
+ if (!ts) return ''
+ const d = new Date(ts)
+ return d.toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
+}
+
watch(() => route.params.agentId, (newId) => {
if (newId && newId !== selectedTemplateId.value) {
selectedTemplateId.value = newId;
@@ -323,6 +366,7 @@ watch(() => route.params.agentId, (newId) => {
watch(selectedTemplateId, (newId) => {
if (newId) {
loadTemplateFiles(newId);
+ loadTemplateLogs(newId);
}
})
@@ -552,4 +596,40 @@ onMounted(() => {
background: var(--purple-bg, rgba(139,92,246,0.1)); color: var(--purple-text, #a78bfa);
flex-shrink: 0; font-weight: 600;
}
+
+/* ═══ Logs Panel ═══ */
+.logs-section {
+ margin-top: 8px; border: 1px solid var(--border-primary); border-radius: 12px;
+ background: var(--bg-surface); overflow: hidden;
+}
+.logs-section .section-header {
+ display: flex; align-items: center; justify-content: space-between;
+ padding: 10px 16px; border-bottom: 1px solid var(--border-primary);
+}
+.logs-section .section-header h4 {
+ margin: 0; font-size: 14px; font-weight: 700; color: var(--text-primary);
+}
+.logs-list { max-height: 250px; overflow-y: auto; padding: 8px; display: flex; flex-direction: column; gap: 6px; }
+.log-card {
+ border: 1px solid var(--border-primary); border-radius: 8px; overflow: hidden;
+ transition: border-color 0.2s;
+}
+.log-card.log-done { border-left: 3px solid rgba(16,185,129,0.4); }
+.log-card.log-running { border-left: 3px solid rgba(59,130,246,0.4); }
+.log-header {
+ display: flex; align-items: center; gap: 8px; padding: 8px 12px;
+ cursor: pointer; user-select: none; transition: background 0.15s;
+}
+.log-header:hover { background: var(--bg-surface-hover); }
+.log-status-icon { font-size: 12px; }
+.log-label { font-size: 12px; font-weight: 700; color: var(--text-secondary); flex: 1; }
+.log-time { font-size: 10px; color: var(--text-muted); }
+.log-msg-count { font-size: 10px; color: var(--text-faint); }
+.log-toggle { font-size: 9px; color: var(--text-faint); }
+.log-summary {
+ padding: 8px 12px; font-size: 12px; line-height: 1.6; color: var(--text-secondary);
+ background: var(--bg-input); border-top: 1px solid var(--border-primary);
+ white-space: pre-wrap; word-break: break-word; max-height: 200px; overflow-y: auto;
+ font-family: 'JetBrains Mono', monospace;
+}