Phase 7: Agent Selector — per-agent soul.md + ChromaDB memory filtering

- Agent dropdown UI (chip button + hidden select) in composer header
- Session.agent field persists agent selection across refresh
- soul.md loaded per-agent via ephemeral_system_prompt injection
- ChromaDB memory filtered by agent topic (lotus/, sunflower/, etc.)
- Fixed streaming.py: agent→_ai_agent variable shadowing (lines 1161, 1163)
- New API endpoints: /api/agents/topology, /api/agents/memory/search
- Agent metadata registry with emoji, name, description per Tier-2 agent
This commit is contained in:
Rose
2026-04-20 17:34:58 +02:00
parent 00045314f8
commit c705fad626
14 changed files with 2578 additions and 320 deletions

View File

@@ -439,6 +439,7 @@ $('modelSelect').onchange=async()=>{
await api('/api/session/update',{method:'POST',body:JSON.stringify({session_id:S.session.session_id,workspace:S.session.workspace,model:selectedModel})});
S.session.model=selectedModel;
if(typeof syncModelChip==='function') syncModelChip();
if(typeof syncAgentChip==='function') syncAgentChip();
syncTopbar();
// Warn if selected model belongs to a different provider than what Hermes is configured for
if(typeof _checkProviderMismatch==='function'){

View File

@@ -229,7 +229,7 @@ const LOCALES = {
tab_memory: 'Memory',
tab_workspaces: 'Spaces',
tab_profiles: 'Profiles',
tab_todos: 'Todos',
new_conversation: 'New conversation',
filter_conversations: 'Filter conversations...',
session_time_unknown: 'Unknown',
@@ -249,7 +249,7 @@ const LOCALES = {
search_skills: 'Search skills...',
new_skill: 'New skill',
personal_memory: 'Personal memory',
current_task_list: 'Current task list',
workspace_desc: 'Add and switch workspaces for your sessions.',
new_profile: 'New profile',
transcript: 'Transcript',
@@ -403,7 +403,7 @@ const LOCALES = {
cron_completion_status: (name, status) => `Cron "${name}" ${status}`,
status_failed: 'failed',
status_completed: 'completed',
todos_no_active: 'No active task list in this session.',
clear_conversation_title: 'Clear conversation',
clear_conversation_message: 'Clear all messages? This cannot be undone.',
clear_failed: 'Clear failed: ',
@@ -667,7 +667,7 @@ const LOCALES = {
tab_memory: 'Память',
tab_workspaces: 'Рабочие пространства',
tab_profiles: 'Профили',
tab_todos: 'Список дел',
new_conversation: 'Новая беседа',
filter_conversations: 'Фильтр бесед...',
session_time_unknown: 'Неизвестно',
@@ -714,7 +714,7 @@ const LOCALES = {
search_skills: 'Поиск навыков...',
new_skill: 'Новый навык',
personal_memory: 'Личная память',
current_task_list: 'Текущий список задач',
workspace_desc: 'Добавляйте рабочие пространства и переключайтесь между ними в своих сеансах.',
new_profile: 'Новый профиль',
transcript: 'Транскрипт',
@@ -864,7 +864,7 @@ const LOCALES = {
cron_completion_status: (name, status) => `Cron-задание «${name}» — ${status}`,
status_failed: 'неудачно',
status_completed: 'завершено',
todos_no_active: 'В этой сессии нет активного списка задач.',
clear_conversation_title: 'Очистить беседу',
clear_conversation_message: 'Очистить все сообщения? Это действие нельзя отменить.',
clear_failed: 'Не удалось очистить: ',
@@ -1133,7 +1133,7 @@ const LOCALES = {
tab_memory: 'Memoria',
tab_workspaces: 'Espacios',
tab_profiles: 'Perfiles',
tab_todos: 'Todos',
new_conversation: 'Nueva conversación',
filter_conversations: 'Filtrar conversaciones...',
session_time_unknown: 'Desconocido',
@@ -1153,7 +1153,7 @@ const LOCALES = {
search_skills: 'Buscar skills...',
new_skill: 'Nueva skill',
personal_memory: 'Memoria personal',
current_task_list: 'Lista de tareas actual',
workspace_desc: 'Añade y cambia espacios de trabajo para tus sesiones.',
new_profile: 'Nuevo perfil',
transcript: 'Transcripción',
@@ -1307,7 +1307,7 @@ const LOCALES = {
cron_completion_status: (name, status) => `Cron "${name}" ${status}`,
status_failed: 'failed',
status_completed: 'completed',
todos_no_active: 'No active task list in this session.',
clear_conversation_title: 'Clear conversation',
clear_conversation_message: 'Clear all messages? This cannot be undone.',
clear_failed: 'Clear failed: ',
@@ -1580,7 +1580,7 @@ const LOCALES = {
tab_memory: 'Gedächtnis',
tab_workspaces: 'Spaces',
tab_profiles: 'Profile',
tab_todos: 'Todos',
new_conversation: 'Neuer Chat',
filter_conversations: 'Chats filtern...',
scheduled_jobs: 'Geplante Aufgaben',
@@ -1589,7 +1589,7 @@ const LOCALES = {
search_skills: 'Skills suchen...',
new_skill: 'Neuer Skill',
personal_memory: 'Persönliches Gedächtnis',
current_task_list: 'Aktuelle Aufgabenliste',
workspace_desc: 'Workspaces hinzufügen und wechseln.',
new_profile: 'Neues Profil',
transcript: 'Protokoll',
@@ -1797,7 +1797,7 @@ const LOCALES = {
tab_memory: '记忆',
tab_skills: '技能',
tab_tasks: '任务',
tab_todos: '待办',
tab_workspaces: '工作区',
tab_profiles: '配置',
new_conversation: '新建对话',
@@ -1819,7 +1819,7 @@ const LOCALES = {
new_skill: '新技能',
save_skill: '保存技能',
personal_memory: '个人记忆',
current_task_list: '当前任务列表',
workspace_desc: '为你的会话添加并切换工作区。',
new_profile: '新配置',
transcript: '记录',
@@ -1971,7 +1971,7 @@ const LOCALES = {
cron_completion_status: (name, status) => `定时任务“${name}${status}`,
status_failed: '失败',
status_completed: '完成',
todos_no_active: '此会话暂无活动任务列表。',
clear_conversation_title: '清空对话',
clear_conversation_message: '要清空所有消息吗?此操作无法撤销。',
clear_failed: '清空失败:',
@@ -2246,7 +2246,6 @@ const LOCALES = {
new_skill: '\u65b0\u6280\u80fd',
save_skill: '\u5132\u5b58\u6280\u80fd',
personal_memory: '\u500b\u4eba\u8a18\u61b6',
current_task_list: '\u76ee\u524d\u4efb\u52d9\u6e05\u55ae',
new_profile: '\u65b0\u914d\u7f6e\u6a94',
transcript: '\u8a18\u9304',
download_transcript: '\u4e0b\u8f09\u8a18\u9304',

View File

@@ -22,10 +22,12 @@
<button class="nav-tab" data-panel="tasks" data-label="Tasks" onclick="switchPanel('tasks')" title="Tasks" data-i18n-title="tab_tasks"><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="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg></button>
<button class="nav-tab" data-panel="skills" data-label="Skills" onclick="switchPanel('skills')" title="Skills" data-i18n-title="tab_skills"><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="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg></button>
<button class="nav-tab" data-panel="memory" data-label="Memory" onclick="switchPanel('memory')" title="Memory" data-i18n-title="tab_memory"><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="M12 2a7 7 0 0 1 7 7c0 2.5-1.3 4.7-3.2 6H8.2C6.3 13.7 5 11.5 5 9a7 7 0 0 1 7-7z"/><line x1="9" y1="17" x2="15" y2="17"/><line x1="10" y1="20" x2="14" y2="20"/></svg></button>
<button class="nav-tab" data-panel="workspaces" data-label="Spaces" onclick="switchPanel('workspaces')" title="Spaces" data-i18n-title="tab_workspaces"><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="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg></button>
<button class="nav-tab" data-panel="todos" data-label="Todos" onclick="switchPanel('todos')" title="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" data-panel="missioncontrol" data-label="MC" onclick="switchPanel('missioncontrol')" title="Mission Control" data-i18n-title="tab_mc"><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"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></button>
<button class="nav-tab" data-panel="agents" data-label="Agents" onclick="switchPanel('agents')" title="Rose + Tier-2 Agents" data-i18n-title="tab_agents"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg></button>
<button class="nav-tab" data-panel="projects" data-label="Projects" onclick="switchPanel('projects')" title="Projects & Tasks" data-i18n-title="tab_projects"><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="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg></button>
</div>
<!-- Chat panel -->
<div class="panel-view active" id="panelChat">
@@ -104,84 +106,8 @@
<div id="memEditError" style="font-size:11px;color:var(--accent);margin-top:6px;display:none"></div>
</div>
</div>
<!-- Todo panel -->
<div class="panel-view" id="panelTodos">
<div style="padding:10px 12px 4px;font-size:11px;color:var(--muted);flex-shrink:0" data-i18n="current_task_list">Current task list</div>
<div id="todoPanel" style="flex:1;overflow-y:auto;padding:8px 12px"></div>
</div>
<!-- Mission Control panel -->
<div class="panel-view" id="panelMissioncontrol">
<!-- Header -->
<div style="padding:14px 16px 10px;flex-shrink:0;border-bottom:1px solid var(--border)">
<div style="display:flex;align-items:center;justify-content:space-between">
<div style="display:flex;align-items:center;gap:8px">
<span style="font-size:16px">🎯</span>
<span style="font-size:14px;font-weight:700;color:var(--text)">Mission Control</span>
</div>
<div style="display:flex;align-items:center;gap:6px">
<span id="mcHealthBadge" style="font-size:10px;font-weight:600;padding:2px 8px;border-radius:12px;background:rgba(0,0,0,.06)"></span>
<button onclick="refreshMC()" title="Refresh" style="background:none;border:none;cursor:pointer;color:var(--muted);font-size:12px;padding:2px"></button>
</div>
</div>
</div>
<!-- Stats Cards -->
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;padding:10px 16px;flex-shrink:0">
<div style="background:var(--surface);border-radius:10px;padding:10px 12px;border:1px solid var(--border)">
<div style="font-size:10px;color:var(--muted);margin-bottom:2px">Tasks</div>
<div style="font-size:18px;font-weight:700;color:var(--blue)" id="mcTasksCount"></div>
<div style="font-size:9px;color:var(--muted)" id="mcTasksLabel">loading...</div>
</div>
<div style="background:var(--surface);border-radius:10px;padding:10px 12px;border:1px solid var(--border)">
<div style="font-size:10px;color:var(--muted);margin-bottom:2px">Priorities</div>
<div style="font-size:18px;font-weight:700;color:var(--gold)" id="mcPrioritiesCount"></div>
<div style="font-size:9px;color:var(--muted)" id="mcPrioritiesLabel">loading...</div>
</div>
</div>
<!-- Progress Bar -->
<div style="padding:0 16px 10px;flex-shrink:0">
<div style="display:flex;justify-content:space-between;font-size:9px;color:var(--muted);margin-bottom:4px">
<span>Progress</span><span id="mcProgressPct">0%</span>
</div>
<div style="background:var(--border);border-radius:6px;height:8px;overflow:hidden">
<div id="mcProgressBar" style="height:100%;width:0%;background:linear-gradient(90deg,var(--blue),var(--accent));border-radius:6px;transition:width .4s ease"></div>
</div>
</div>
<!-- Add Task -->
<div style="padding:0 16px 10px;flex-shrink:0">
<div style="font-size:10px;font-weight:600;color:var(--muted);margin-bottom:6px;text-transform:uppercase;letter-spacing:.05em">New Task</div>
<input id="mcNewTaskTitle" placeholder="What needs to be done?" style="width:100%;background:var(--input-bg);border:1px solid var(--border);border-radius:8px;padding:8px 10px;font-size:12px;color:var(--text);outline:none;margin-bottom:6px" onkeydown="if(event.key==='Enter')createMCTask()">
<div style="display:grid;grid-template-columns:1fr 1fr 80px;gap:6px">
<select id="mcNewTaskPriority" style="background:var(--input-bg);border:1px solid var(--border);border-radius:8px;padding:6px 8px;font-size:11px;color:var(--text);outline:none">
<option value="1">🔴 Critical</option>
<option value="2" selected>🟠 High</option>
<option value="3">🟡 Medium</option>
<option value="4">🟢 Low</option>
</select>
<select id="mcNewTaskStatus" style="background:var(--input-bg);border:1px solid var(--border);border-radius:8px;padding:6px 8px;font-size:11px;color:var(--text);outline:none">
<option value="backlog" selected>○ Backlog</option>
<option value="progress">◐ In Progress</option>
</select>
<button onclick="createMCTask()" style="background:var(--accent);border:none;border-radius:8px;padding:6px 10px;font-size:11px;font-weight:600;color:#fff;cursor:pointer">Add</button>
</div>
</div>
<!-- Priority Filter -->
<div style="padding:0 16px 6px;flex-shrink:0">
<div style="display:flex;gap:4px;flex-wrap:wrap" id="mcPriorityFilters"></div>
</div>
<!-- Tasks List -->
<div style="flex:1;overflow-y:auto;padding:0 16px" id="mcTasksList"></div>
<!-- Feed -->
<div style="padding:8px 16px 4px;border-top:1px solid var(--border);flex-shrink:0">
<div style="font-size:10px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px">Recent Activity</div>
</div>
<div id="mcFeed" style="flex-shrink:0;max-height:100px;overflow-y:auto;padding:0 16px 12px;font-size:10px;color:var(--muted)"></div>
</div>
<!-- Agents panel (Rose + Tier-2) -->
<div class="panel-view" id="panelAgents">
<div style="padding:14px 16px 10px;flex-shrink:0;border-bottom:1px solid var(--border)">
@@ -194,17 +120,103 @@
</div>
<div style="font-size:10px;color:var(--muted);margin-top:4px">Rose + 7 Tier-2 Domain Agents</div>
</div>
<!-- Agents list -->
<!-- agents list -->
<div style="flex:1;overflow-y:auto;padding:10px 16px" id="agentsList">
<div style="color:var(--muted);font-size:12px;text-align:center;padding:20px">Loading...</div>
</div>
<!-- Agent inbox slide-in panel -->
<div id="agentInbox" style="display:none;position:absolute;top:0;right:0;bottom:0;width:320px;background:var(--surface);border-left:1px solid var(--border);z-index:100;overflow-y:auto;padding:16px;box-shadow:-4px 0 20px rgba(0,0,0,.3)"></div>
</div>
<!-- Workspaces panel -->
<div class="panel-view" id="panelWorkspaces">
<div style="padding:10px 12px 4px;font-size:11px;color:var(--muted)" data-i18n="workspace_desc">Add and switch workspaces for your sessions.</div>
<div style="flex:1;overflow-y:auto;padding:0 12px 12px" id="workspacesPanel"><div style="color:var(--muted);font-size:12px" data-i18n="loading">Loading...</div></div>
<!-- Projects panel -->
<div class="panel-view" id="panelProjects">
<!-- Header: Title + Expand Button -->
<div class="projects-header">
<div class="projects-title">📋 Projects</div>
<div class="projects-header-stats" id="projectsHeaderStats"></div>
<button class="panel-icon-btn" id="btnExpandProjects" title="Expand" onclick="expandPanel('projects')">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>
</button>
</div>
<!-- Quick Add Bar -->
<div class="projects-quick-add">
<span class="quick-add-icon">+</span>
<input id="quickAddInput" placeholder="Add a task..." onkeydown="if(event.key==='Enter')quickAddTask()">
<input id="quickAddDue" type="date" title="Due date" id="quickAddDue">
<select id="quickAddType">
<option value="project">📁 Project</option>
<option value="daily">📅 Daily</option>
<option value="recurring">🔄 Recurring</option>
</select>
<button class="quick-add-btn" onclick="quickAddTask()">Add</button>
</div>
<!-- Filter Bar -->
<div class="projects-filter-bar" id="projectsFilterBar">
<div class="filter-group">
<button class="filter-btn active" data-filter="all" onclick="filterTasks('all')">All</button>
<button class="filter-btn" data-filter="project" onclick="filterTasks('project')">📁 Projects</button>
<button class="filter-btn" data-filter="daily" onclick="filterTasks('daily')">📅 Daily</button>
<button class="filter-btn" data-filter="recurring" onclick="filterTasks('recurring')">🔄 Recurring</button>
</div>
<div class="filter-sep"></div>
<div class="filter-group">
<button class="filter-btn p1" onclick="filterTasks('p1')">🔴 P1</button>
<button class="filter-btn p2" onclick="filterTasks('p2')">🟡 P2</button>
<button class="filter-btn p3" onclick="filterTasks('p3')">🟢 P3</button>
</div>
<div class="filter-sep"></div>
<div class="filter-group filter-group-right">
<span class="filter-streak" id="filterStreak"></span>
<span class="filter-overdue" id="filterOverdue"></span>
</div>
</div>
<!-- Main Content: Split View -->
<div class="projects-main" id="projectsMain">
<!-- Linke Spalte: Projektliste -->
<div class="projects-sidebar" id="projectsSidebar">
<div class="sidebar-section-header">📁 Projects</div>
<div id="projectsList"></div>
<div class="sidebar-section-header" style="margin-top:16px">📅 Daily Tasks</div>
<div id="dailyTasksList"></div>
<div class="sidebar-section-header" style="margin-top:16px">🔄 Recurring</div>
<div id="recurringTasksList"></div>
</div>
<!-- Rechte Spalte: Globales Kanban -->
<div class="projects-kanban" id="projectsKanban">
<div class="kanban-col" ondragover="onKanbanDragOver(event)" ondrop="onKanbanDrop(event, 'todo')">
<div class="kanban-col-header">
<span class="kanban-col-title">📋 TODO</span>
<span class="kanban-col-count" id="kanbanTodoCount"></span>
</div>
<div class="kanban-col-content" id="kanbanTodoContent"></div>
</div>
<div class="kanban-col" ondragover="onKanbanDragOver(event)" ondrop="onKanbanDrop(event, 'in_progress')">
<div class="kanban-col-header">
<span class="kanban-col-title">⚡ IN PROGRESS</span>
<span class="kanban-col-count" id="kanbanInProgressCount"></span>
</div>
<div class="kanban-col-content" id="kanbanInProgressContent"></div>
</div>
<div class="kanban-col" ondragover="onKanbanDragOver(event)" ondrop="onKanbanDrop(event, 'review')">
<div class="kanban-col-header">
<span class="kanban-col-title">👀 REVIEW</span>
<span class="kanban-col-count" id="kanbanReviewCount"></span>
</div>
<div class="kanban-col-content" id="kanbanReviewContent"></div>
</div>
<div class="kanban-col" ondragover="onKanbanDragOver(event)" ondrop="onKanbanDrop(event, 'done')">
<div class="kanban-col-header">
<span class="kanban-col-title">✅ DONE</span>
<span class="kanban-col-count" id="kanbanDoneCount"></span>
</div>
<div class="kanban-col-content" id="kanbanDoneContent"></div>
</div>
</div>
</div>
</div>
<div class="sidebar-bottom">
@@ -242,6 +254,14 @@
</button>
<div style="flex:1;min-width:0;overflow:hidden"><div class="topbar-title" id="topbarTitle">Hermes</div><div class="topbar-meta" id="topbarMeta" data-i18n="new_conversation">Start a new conversation</div></div>
<div class="topbar-chips">
<div class="ws-selector-wrap" id="wsSelectorWrap" style="position:relative">
<button class="chip ws-selector-chip" id="wsSelectorChip" type="button" onclick="toggleWsSelectorDropdown()" title="Switch workspace">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
<span id="wsSelectorLabel">Workspace</span>
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</button>
<div class="ws-selector-dropdown" id="wsSelectorDropdown" style="display:none;position:absolute;top:100%;left:0;margin-top:4px;background:var(--surface);border:1px solid var(--border);border-radius:10px;min-width:260px;max-height:320px;overflow-y:auto;z-index:200;box-shadow:0 8px 24px rgba(0,0,0,.3)"></div>
</div>
<button class="chip workspace-toggle-btn" id="btnWorkspacePanelToggle" onclick="toggleWorkspacePanel()" title="Show workspace panel" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg><span class="workspace-toggle-label">Files</span></button>
</div>
</div>
@@ -361,21 +381,6 @@
<line x1="8" y1="23" x2="16" y2="23"/>
</svg>
</button>
<div class="composer-divider" aria-hidden="true"></div>
<div id="profileChipWrap" class="composer-profile-wrap">
<button class="composer-profile-chip profile-chip" id="profileChip" type="button" onclick="toggleProfileDropdown()" title="Switch profile">
<span class="composer-profile-icon" aria-hidden="true"><svg width="14" height="14" 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></span>
<span class="composer-profile-label" id="profileChipLabel">default</span>
<span class="composer-profile-chevron" aria-hidden="true"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></span>
</button>
</div>
<div class="composer-ws-wrap">
<button class="composer-workspace-chip ws-chip" id="composerWorkspaceChip" type="button" onclick="toggleComposerWsDropdown()" title="Switch workspace" disabled>
<span class="composer-workspace-icon" aria-hidden="true"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg></span>
<span class="composer-workspace-label" id="composerWorkspaceLabel">Workspace</span>
<span class="composer-workspace-chevron" aria-hidden="true"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></span>
</button>
</div>
<div class="composer-model-wrap">
<button class="composer-model-chip" id="composerModelChip" type="button" onclick="toggleModelDropdown()" title="Conversation model">
<span class="composer-model-icon" aria-hidden="true"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="2"/><rect x="9" y="9" width="6" height="6"/><path d="M15 2v2"/><path d="M15 20v2"/><path d="M2 15h2"/><path d="M2 9h2"/><path d="M20 15h2"/><path d="M20 9h2"/><path d="M9 2v2"/><path d="M9 20v2"/></svg></span>
@@ -401,6 +406,23 @@
</optgroup>
</select>
</div>
<div class="composer-agent-wrap">
<button class="composer-agent-chip" id="composerAgentChip" type="button" onclick="toggleAgentDropdown()" title="Chat with agent">
<span class="composer-agent-icon" id="composerAgentIcon" aria-hidden="true">🌹</span>
<span class="composer-agent-label" id="composerAgentLabel">Rose</span>
<span class="composer-agent-chevron" aria-hidden="true"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></span>
</button>
<select id="agentSelect" class="composer-agent-select" title="Chat with agent" aria-hidden="true" tabindex="-1">
<option value="rose">🌹 Rose</option>
<option value="lotus">🪷 Lotus</option>
<option value="forget-me-not">🌼 Forget-me-not</option>
<option value="sunflower">🌻 Sunflower</option>
<option value="iris">⚜️ Iris</option>
<option value="ivy">🌿 Ivy</option>
<option value="dandelion">🛡️ Dandelion</option>
<option value="root">🌳 Root</option>
</select>
</div>
</div>
<div class="composer-right">
<span class="composer-status" id="composerStatus" style="display:none"></span>
@@ -432,6 +454,7 @@
<div class="profile-dropdown" id="profileDropdown"></div>
<div class="ws-dropdown ws-dropdown-footer" id="composerWsDropdown"></div>
<div class="model-dropdown" id="composerModelDropdown"></div>
<div class="agent-dropdown" id="composerAgentDropdown"></div>
</div>
<div class="upload-bar-wrap" id="uploadBarWrap"><div class="upload-bar" id="uploadBar"></div></div>
</div>
@@ -473,6 +496,72 @@
</div>
</aside>
</div>
<!-- Task Detail Modal -->
<div id="taskDetailModal" class="modal-overlay" style="display:none" onclick="if(event.target===this)closeTaskModal()">
<div class="modal-card">
<div class="modal-header">
<div class="modal-title" id="taskModalTitle"></div>
<button class="modal-close" onclick="closeTaskModal()">×</button>
</div>
<div class="modal-body">
<div class="modal-field">
<label>Title</label>
<input id="taskModalInputTitle" class="modal-input" type="text" placeholder="Task title...">
</div>
<div class="modal-field-row">
<div class="modal-field">
<label>Status</label>
<select id="taskModalSelectStatus" class="modal-select">
<option value="todo">Todo</option>
<option value="in_progress">In Progress</option>
<option value="review">Review</option>
<option value="done">Done</option>
</select>
</div>
<div class="modal-field">
<label>Priority</label>
<select id="taskModalSelectPrio" class="modal-select">
<option value="p1">🔴 P1 — Critical</option>
<option value="p2">🟡 P2 — Normal</option>
<option value="p3">🟢 P3 — Low</option>
</select>
</div>
</div>
<div class="modal-field-row">
<div class="modal-field">
<label>Due Date</label>
<input id="taskModalInputDue" class="modal-input" type="date">
</div>
<div class="modal-field">
<label>Owner</label>
<select id="taskModalSelectOwner" class="modal-select">
<option value="user">👤 Sabo</option>
<option value="rose">🌹 Rose</option>
<option value="agent:lotus">🪷 Lotus</option>
<option value="agent:sunflower">🌻 Sunflower</option>
<option value="agent:iris">⚜️ Iris</option>
<option value="agent:ivy">🌿 Ivy</option>
<option value="agent:dandelion">🛡️ Dandelion</option>
<option value="agent:root">🌳 Root</option>
</select>
</div>
</div>
<div class="modal-field">
<label>Tags (comma-separated)</label>
<input id="taskModalInputTags" class="modal-input" type="text" placeholder="webui, api, bug...">
</div>
<div class="modal-meta" id="taskModalMeta"></div>
</div>
<div class="modal-footer">
<button class="modal-btn-danger" id="taskModalDeleteBtn" onclick="deleteTaskFromModal()">Delete</button>
<div style="flex:1"></div>
<button class="modal-btn-secondary" onclick="closeTaskModal()">Cancel</button>
<button class="modal-btn-primary" onclick="saveTaskFromModal()">Save</button>
</div>
</div>
</div>
<div class="onboarding-overlay" id="onboardingOverlay" style="display:none" role="dialog" aria-modal="true" aria-labelledby="onboardingTitle">
<div class="onboarding-card">
<div class="onboarding-shell">
@@ -527,6 +616,10 @@
<svg class="settings-tab-icon" width="16" height="16" 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"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
<span class="settings-tab-title">Logs</span>
</button>
<button class="settings-tab" id="settingsTabHeartbeats" type="button" role="tab" aria-selected="false" aria-controls="settingsPaneHeartbeats" onclick="switchSettingsSection('heartbeats')">
<svg class="settings-tab-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>
<span class="settings-tab-title">Heartbeats</span>
</button>
</div>
<div class="settings-main">
<div class="settings-pane active" id="settingsPaneConversation" role="tabpanel" aria-labelledby="settingsTabConversation">
@@ -689,19 +782,30 @@
<input type="checkbox" id="logsAutoRefresh" onchange="toggleLogAutoRefresh()" style="width:13px;height:13px;accent-color:var(--accent)">
Live
</label>
<button class="sm-btn" id="btnRefreshLog" onclick="refreshLogManual()" style="display:none;padding:3px 8px;font-size:11px">Refresh</button>
<button class="icon-btn" id="logsRefreshBtn" style="display:none" onclick="loadLogsPanel()" title="Refresh logs">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>
</button>
</div>
</div>
<pre class="logs-pre" id="logsContent"><span style="color:var(--muted);font-size:12px">Select a log file from the list to view its contents.</span></pre>
<div class="logs-footer" id="logsFooter" style="display:none;font-size:10px;color:var(--muted);padding:4px 12px;border-top:1px solid var(--border)">
<span id="logsMatchCount"></span>
<div class="logs-body" id="logsBody">
<div style="color:var(--muted);font-size:12px;padding:20px;text-align:center" id="logsEmptyState">Select a log file from the sidebar to view its contents.</div>
<div id="logsContent" style="display:none"></div>
</div>
</div>
</div>
</div>
<div class="settings-pane" id="settingsPaneHeartbeats" role="tabpanel" aria-labelledby="settingsTabHeartbeats">
<div class="settings-section-head">
<div>
<div class="settings-section-title">Heartbeats</div>
<div class="settings-section-meta">Proaktive zeitbasierte Callbacks für Rose.</div>
</div>
</div>
<div id="heartbeatsPanelContent" style="padding:16px">
<div style="color:var(--muted);font-size:12px;text-align:center;padding:20px">Lädt...</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -733,5 +837,93 @@
<script src="/static/panels.js"></script>
<script src="/static/onboarding.js"></script>
<script src="/static/boot.js"></script>
<!-- Task Edit Modal -->
<div id="taskEditModal" class="modal-overlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:2000;justify-content:center;align-items:center">
<div style="background:var(--surface);border:1px solid var(--border2);border-radius:12px;padding:24px;width:420px;max-width:90vw">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px">
<span style="font-weight:600;font-size:15px;color:var(--text)">Edit Task</span>
<button onclick="closeTaskEditModal()" style="background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:18px;padding:4px"></button>
</div>
<input id="editTaskTitle" style="width:100%;padding:10px 12px;background:var(--bg);border:1px solid var(--border2);border-radius:8px;color:var(--text);font-size:14px;box-sizing:border-box;margin-bottom:12px;outline:none">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:12px">
<div>
<label style="font-size:11px;color:var(--text-secondary);display:block;margin-bottom:4px">TYPE</label>
<select id="editTaskType" onchange="onEditTypeChange()" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border2);border-radius:6px;color:var(--text);font-size:13px;outline:none">
<option value="project">Project</option>
<option value="daily">Daily</option>
<option value="recurring">Recurring</option>
</select>
</div>
<div>
<label style="font-size:11px;color:var(--text-secondary);display:block;margin-bottom:4px">PRIORITY</label>
<select id="editTaskPriority" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border2);border-radius:6px;color:var(--text);font-size:13px;outline:none">
<option value="p1">🔴 P1 — Critical</option>
<option value="p2">🟡 P2 — High</option>
<option value="p3">🟢 P3 — Normal</option>
</select>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:12px">
<div>
<label style="font-size:11px;color:var(--text-secondary);display:block;margin-bottom:4px">PROJECT</label>
<select id="editTaskProject" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border2);border-radius:6px;color:var(--text);font-size:13px;outline:none">
<option value="">— None —</option>
</select>
</div>
<div>
<label style="font-size:11px;color:var(--text-secondary);display:block;margin-bottom:4px">DUE DATE</label>
<input id="editTaskDue" type="date" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border2);border-radius:6px;color:var(--text);font-size:13px;outline:none;box-sizing:border-box;color-scheme:dark">
</div>
</div>
<div id="editTaskRecurringOpts" style="display:none;margin-bottom:12px">
<label style="font-size:11px;color:var(--text-secondary);display:block;margin-bottom:4px">RECURRING — EVERY</label>
<div style="display:flex;gap:8px;align-items:center">
<input id="editRecInterval" type="number" min="1" value="1" style="width:60px;padding:8px;background:var(--bg);border:1px solid var(--border2);border-radius:6px;color:var(--text);font-size:13px;text-align:center;outline:none">
<select id="editRecUnit" style="padding:8px;background:var(--bg);border:1px solid var(--border2);border-radius:6px;color:var(--text);font-size:13px;outline:none">
<option value="days">day(s)</option>
<option value="weeks">week(s)</option>
<option value="months">month(s)</option>
</select>
</div>
</div>
<div style="display:flex;gap:8px;justify-content:space-between;margin-top:16px">
<button onclick="deleteTask(editingTaskId)" style="padding:8px 16px;background:rgba(239,68,68,.15);border:1px solid rgba(239,68,68,.3);border-radius:8px;color:#ef4444;cursor:pointer;font-size:13px">🗑 Delete</button>
<div style="display:flex;gap:8px">
<button onclick="closeTaskEditModal()" style="padding:8px 16px;background:var(--bg);border:1px solid var(--border2);border-radius:8px;color:var(--text);cursor:pointer;font-size:13px">Cancel</button>
<button onclick="saveTaskEdit()" style="padding:8px 16px;background:var(--accent);border:none;border-radius:8px;color:#fff;cursor:pointer;font-size:13px;font-weight:500">Save Changes</button>
</div>
</div>
</div>
</div>
<!-- Project Edit Modal -->
<div id="projectEditModal" class="modal-overlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:2000;justify-content:center;align-items:center">
<div style="background:var(--surface);border:1px solid var(--border2);border-radius:12px;padding:24px;width:400px;max-width:90vw">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px">
<span style="font-weight:600;font-size:15px;color:var(--text)">Edit Project</span>
<button onclick="closeProjectEditModal()" style="background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:18px;padding:4px"></button>
</div>
<div style="margin-bottom:12px">
<label style="font-size:11px;color:var(--text-secondary);display:block;margin-bottom:4px">NAME</label>
<input id="editProjectName" style="width:100%;padding:10px 12px;background:var(--bg);border:1px solid var(--border2);border-radius:8px;color:var(--text);font-size:14px;box-sizing:border-box;outline:none">
</div>
<div style="margin-bottom:12px">
<label style="font-size:11px;color:var(--text-secondary);display:block;margin-bottom:4px">COLOR</label>
<div style="display:flex;gap:6px">
<button class="color-dot selected" data-color="#6366f1" onclick="selectProjectColor(this)" style="width:24px;height:24px;border-radius:50%;background:#6366f1;border:2px solid transparent;cursor:pointer"></button>
<button class="color-dot" data-color="#f59e0b" onclick="selectProjectColor(this)" style="width:24px;height:24px;border-radius:50%;background:#f59e0b;border:2px solid transparent;cursor:pointer"></button>
<button class="color-dot" data-color="#10b981" onclick="selectProjectColor(this)" style="width:24px;height:24px;border-radius:50%;background:#10b981;border:2px solid transparent;cursor:pointer"></button>
<button class="color-dot" data-color="#ef4444" onclick="selectProjectColor(this)" style="width:24px;height:24px;border-radius:50%;background:#ef4444;border:2px solid transparent;cursor:pointer"></button>
<button class="color-dot" data-color="#8b5cf6" onclick="selectProjectColor(this)" style="width:24px;height:24px;border-radius:50%;background:#8b5cf6;border:2px solid transparent;cursor:pointer"></button>
<button class="color-dot" data-color="#ec4899" onclick="selectProjectColor(this)" style="width:24px;height:24px;border-radius:50%;background:#ec4899;border:2px solid transparent;cursor:pointer"></button>
</div>
</div>
<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:16px">
<button onclick="closeProjectEditModal()" style="padding:8px 16px;background:var(--bg);border:1px solid var(--border2);border-radius:8px;color:var(--text);cursor:pointer;font-size:13px">Cancel</button>
<button onclick="saveProjectEdit()" style="padding:8px 16px;background:var(--accent);border:none;border-radius:8px;color:#fff;cursor:pointer;font-size:13px;font-weight:500">Save</button>
</div>
</div>
</div>
</body>
</html>

View File

@@ -71,7 +71,9 @@ async function send(){
try{
const startData=await api('/api/chat/start',{method:'POST',body:JSON.stringify({
session_id:activeSid,message:msgText,
model:S.session.model||$('modelSelect').value,workspace:S.session.workspace,
model:S.session.model||$('modelSelect').value,
agent:S.session.agent||$('agentSelect').value,
workspace:S.session.workspace,
attachments:uploaded.length?uploaded:undefined
})});
streamId=startData.stream_id;

File diff suppressed because it is too large Load Diff

View File

@@ -347,25 +347,41 @@
.cron-list{flex:1;overflow-y:auto;padding:8px;}
.cron-item{border-radius:10px;border:1px solid var(--border);margin-bottom:6px;overflow:hidden;transition:border-color .15s,background .15s;background:rgba(255,255,255,.02);}
.cron-item:hover{border-color:var(--border2);}
.cron-header{display:flex;align-items:center;gap:8px;padding:10px 12px;cursor:pointer;}
.cron-header{display:flex;align-items:center;gap:10px;padding:10px 12px;cursor:pointer;}
.cron-left{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px;}
.cron-name{flex:1;font-size:13px;color:var(--text);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.cron-status{font-size:10px;font-weight:700;padding:2px 8px;border-radius:99px;flex-shrink:0;}
.cron-meta{display:flex;gap:6px;align-items:center;}
.cron-badge-sched{font-size:10px;font-weight:600;background:rgba(124,185,255,.12);color:var(--blue);padding:1px 7px;border-radius:99px;border:1px solid rgba(124,185,255,.25);white-space:nowrap;}
.cron-right{display:flex;align-items:center;gap:10px;flex-shrink:0;}
.cron-next-time{font-size:12px;font-weight:700;color:var(--text);white-space:nowrap;}
.cron-last-time{font-size:11px;color:var(--muted);white-space:nowrap;}
.cron-status{font-size:10px;font-weight:700;padding:2px 8px;border-radius:99px;flex-shrink:0;letter-spacing:.04em;}
.cron-status.active{background:rgba(34,197,94,.15);color:#4ade80;}
.cron-status.paused{background:rgba(201,168,76,.15);color:var(--gold);}
.cron-status.disabled{background:rgba(255,255,255,.07);color:var(--muted);}
.cron-status.error{background:rgba(233,69,96,.15);color:var(--accent);}
.cron-body{display:none;padding:0 12px 10px;border-top:1px solid var(--border);overflow:hidden;}
.cron-body.open{display:block;}
.cron-detail-grid{display:flex;flex-direction:column;gap:4px;margin:8px 0 6px;padding:8px;background:rgba(255,255,255,.03);border-radius:8px;border:1px solid var(--border);}
.cron-detail-row{display:flex;justify-content:space-between;align-items:center;font-size:11px;}
.cron-detail-label{color:var(--muted);font-weight:500;}
.cron-detail-value{color:var(--text);font-weight:600;}
.cron-next-val{color:var(--blue);}
.cron-prompt-preview{display:flex;align-items:baseline;gap:8px;margin-bottom:8px;font-size:11px;}
.cron-prompt-label{color:var(--muted);font-weight:500;flex-shrink:0;}
.cron-prompt-text{color:var(--muted);line-height:1.4;overflow:hidden;text-overflow:ellipsis;}
.cron-schedule{font-size:11px;color:var(--muted);margin:8px 0 6px;}
.cron-prompt{font-size:11px;color:var(--muted);line-height:1.55;max-height:80px;overflow-y:auto;background:rgba(0,0,0,.2);padding:6px 8px;border-radius:6px;white-space:pre-wrap;margin-bottom:8px;box-sizing:border-box;}
.cron-actions{display:flex;gap:6px;margin-bottom:8px;}
.cron-actions{display:flex;gap:6px;margin-bottom:8px;flex-wrap:wrap;}
.cron-btn{padding:4px 10px;border-radius:6px;font-size:11px;font-weight:600;border:1px solid var(--border2);background:rgba(255,255,255,.05);color:var(--muted);cursor:pointer;transition:all .15s;}
.cron-btn:hover{background:rgba(255,255,255,.1);color:var(--text);}
.cron-btn.run{border-color:rgba(124,185,255,.4);color:var(--blue);}
.cron-btn.run:hover{background:rgba(124,185,255,.12);}
.cron-btn.pause{border-color:rgba(201,168,76,.4);color:var(--gold);}
.cron-btn.danger{border-color:rgba(233,69,96,.3);color:var(--accent);}
.cron-btn.danger:hover{background:rgba(233,69,96,.12);}
.cron-last{font-size:11px;color:var(--muted);border-top:1px solid var(--border);padding-top:8px;max-height:220px;overflow-y:auto;white-space:pre-wrap;line-height:1.5;word-break:break-word;}
.cron-last-header{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);margin-bottom:4px;}
.cron-last-header{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);margin-bottom:4px;display:flex;align-items:center;justify-content:space-between;}
.cron-schedule-inline{font-size:11px;color:var(--text);opacity:.6;flex-shrink:0;white-space:nowrap;margin-left:auto;}
.cron-last-inline{font-size:10px;color:var(--muted);opacity:.7;flex-shrink:0;white-space:nowrap;margin-right:4px;}
/* YAML / code syntax highlighting classes */
@@ -559,7 +575,28 @@
.composer-model-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.composer-model-icon,.composer-model-chevron{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;line-height:1;}
.composer-model-select{position:absolute!important;left:-9999px!important;width:1px!important;height:1px!important;opacity:0!important;pointer-events:none!important;}
.composer-right{display:flex;gap:8px;align-items:center;flex-shrink:0;}
/* Agent selector chip */
.composer-agent-wrap{position:relative;flex:0 1 auto;min-width:0;}
.composer-agent-chip{display:inline-flex;align-items:center;gap:6px;max-width:160px;padding:8px 10px 8px 10px;border-radius:999px;border:1px solid transparent;background-color:transparent;color:var(--muted);font-weight:500;cursor:pointer;transition:color .15s,background-color .15s,border-color .15s;}
.composer-agent-chip:hover{color:var(--text);background-color:var(--hover-bg);}
.composer-agent-chip.active{color:var(--text);background:rgba(124,185,255,.08);border-color:rgba(124,185,255,.22);}
.composer-agent-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.composer-agent-icon,.composer-agent-chevron{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;line-height:1;}
.composer-agent-select{position:absolute!important;left:-9999px!important;width:1px!important;height:1px!important;opacity:0!important;pointer-events:none!important;}
.agent-dropdown{display:none;position:absolute;bottom:calc(100% + 4px);left:0;min-width:220px;max-width:min(380px,calc(100vw - 32px));background:var(--surface);border:1px solid var(--border2);border-radius:10px;box-shadow:0 -4px 24px rgba(0,0,0,.4);z-index:200;overflow:hidden;max-height:320px;overflow-y:auto;}
.agent-dropdown.open{display:block;}
.agent-opt{padding:10px 14px;cursor:pointer;transition:background .12s;display:flex;flex-direction:column;gap:3px;align-items:flex-start;}
.agent-opt:hover{background:rgba(255,255,255,.07);}
.agent-opt.active{background:rgba(124,185,255,.1);}
.agent-opt-name{display:block;font-size:13px;color:var(--text);font-weight:500;line-height:1.25;}
.agent-opt-domain{display:block;font-size:10px;color:var(--muted);line-height:1.3;opacity:.72;}
/* Typing indicator */
.typing-indicator{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:999px;background:rgba(255,255,255,.06);font-size:11px;color:var(--muted);}
.typing-dots{display:flex;gap:3px;}
.typing-dot{width:5px;height:5px;border-radius:50%;background:var(--muted);animation:typingPulse .9s ease-in-out infinite;}
.typing-dot:nth-child(2){animation-delay:.15s;}
.typing-dot:nth-child(3){animation-delay:.3s;}
@keyframes typingPulse{0%,60%,100%{opacity:.3;transform:scale(.8);}30%{opacity:1;transform:scale(1);}}
.composer-status{font-size:11px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:170px;}
/* Context usage indicator */
.ctx-indicator-wrap{position:relative;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;}
@@ -1714,3 +1751,821 @@ mark.log-highlight{background:rgba(255,220,50,.35);color:inherit;border-radius:2
flex-shrink: 0;
margin-top: 4px;
}
/* ── Heartbeats Panel ─────────────────────────────────────────────────────── */
#heartbeatsPanelContent {
padding: 0;
overflow-y: auto;
}
.heartbeats-header {
padding: 12px 16px;
border-bottom: 1px solid var(--border);
display: flex;
flex-direction: column;
gap: 8px;
}
.hb-stats {
display: flex;
gap: 16px;
font-size: 12px;
color: var(--muted);
}
.hb-stat b { color: var(--text); }
.hb-manager-status {
display: flex;
align-items: center;
gap: 6px;
font-size: 11px;
color: var(--muted);
}
.hb-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.hb-dot-ok { background: #22c55e; }
.hb-dot-dead { background: #ef4444; }
.hb-stat-warn b { color: #f59e0b; }
.hb-quick-actions {
display: flex;
gap: 6px;
padding: 10px 16px;
border-bottom: 1px solid var(--border);
flex-wrap: wrap;
}
.btn-hb-action {
background: var(--surface-2);
border: 1px solid var(--border);
color: var(--text);
border-radius: 6px;
padding: 4px 10px;
font-size: 11px;
cursor: pointer;
transition: background 0.15s;
}
.btn-hb-action:hover { background: var(--surface-3); }
.hb-create-section {
padding: 12px 16px;
border-bottom: 1px solid var(--border);
}
.hb-create-section h4,
.hb-list-section h4 {
font-size: 11px;
font-weight: 600;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.05em;
margin: 0 0 8px 0;
}
.hb-form {
display: flex;
flex-direction: column;
gap: 8px;
}
.hb-input {
background: var(--surface-2);
border: 1px solid var(--border);
color: var(--text);
border-radius: 6px;
padding: 6px 10px;
font-size: 12px;
width: 100%;
box-sizing: border-box;
}
.hb-input:focus { outline: 1px solid var(--accent); border-color: var(--accent); }
.hb-select {
background: var(--surface-2);
border: 1px solid var(--border);
color: var(--text);
border-radius: 6px;
padding: 5px 8px;
font-size: 12px;
}
.hb-form-row {
display: flex;
gap: 6px;
align-items: center;
flex-wrap: wrap;
}
.hb-mini { width: 70px !important; }
.hb-agent-select { flex: 1; min-width: 160px; }
.hb-checkbox { font-size: 11px; color: var(--muted); cursor: pointer; }
.hb-list-section {
padding: 12px 16px;
flex: 1;
}
.hb-empty {
color: var(--muted);
font-size: 12px;
text-align: center;
padding: 20px;
}
.hb-item {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 8px 10px;
border-radius: 6px;
margin-bottom: 6px;
background: var(--surface-2);
border: 1px solid var(--border);
position: relative;
}
.hb-item-pending { border-left: 3px solid #f59e0b; }
.hb-item-fired { border-left: 3px solid var(--accent); opacity: 0.6; }
.hb-item-cancelled { border-left: 3px solid #6b7280; opacity: 0.5; }
.hb-item-main {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
flex: 1;
}
.hb-action-badge {
font-size: 10px;
background: var(--accent);
color: white;
padding: 1px 6px;
border-radius: 10px;
white-space: nowrap;
font-family: monospace;
}
.hb-source {
font-size: 11px;
color: var(--muted);
}
.hb-priority {
font-size: 10px;
padding: 1px 5px;
border-radius: 4px;
background: var(--surface-3);
color: var(--muted);
}
.hb-due {
font-size: 11px;
color: var(--muted);
}
.hb-iter {
font-size: 10px;
color: var(--accent);
font-family: monospace;
}
.hb-item-sub {
font-size: 11px;
color: var(--muted);
margin-top: 3px;
line-height: 1.3;
}
.hb-cancel-btn {
background: none;
border: none;
color: var(--muted);
cursor: pointer;
font-size: 12px;
padding: 2px 4px;
border-radius: 4px;
flex-shrink: 0;
}
.hb-cancel-btn:hover {
background: #ef4444;
color: white;
}
/* ── Projects Panel ────────────────────────────────────────────────────────── */
#panelProjects {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.projects-header {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 12px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.projects-title {
font-size: 13px;
font-weight: 600;
color: var(--text);
}
.projects-stats {
display: flex;
gap: 12px;
font-size: 11px;
color: var(--muted);
flex: 1;
}
.stat-item {
display: flex;
align-items: center;
gap: 4px;
}
.projects-quick-add {
display: flex;
gap: 8px;
padding: 8px 12px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
/* Hide Projects panel completely unless it is the active panel */
.panel-view#panelProjects:not(.active) {
display: none !important;
}
/* Ensure only the active panel is visible in sidebar */
.sidebar .panel-view {
display: none;
}
.sidebar .panel-view.active {
display: flex;
}
.projects-quick-add input {
flex: 1;
background: rgba(255,255,255,.05);
border: 1px solid var(--border2);
border-radius: 6px;
color: var(--text);
padding: 6px 10px;
font-size: 12px;
outline: none;
}
.projects-quick-add input:focus {
border-color: var(--accent);
}
.projects-quick-add select {
background: rgba(255,255,255,.05);
border: 1px solid var(--border2);
border-radius: 6px;
color: var(--text);
padding: 6px 8px;
font-size: 12px;
outline: none;
cursor: pointer;
}
.projects-filter-bar {
display: flex;
gap: 16px;
padding: 6px 12px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
overflow-x: auto;
}
.filter-group {
display: flex;
gap: 4px;
}
.filter-btn {
background: transparent;
border: 1px solid var(--border2);
border-radius: 4px;
color: var(--muted);
padding: 3px 8px;
font-size: 11px;
cursor: pointer;
transition: all .15s;
white-space: nowrap;
}
.filter-btn:hover {
border-color: var(--accent);
color: var(--text);
}
.filter-btn.active {
background: var(--accent);
border-color: var(--accent);
color: var(--bg);
}
.projects-main {
display: flex;
flex: 1;
overflow: hidden;
}
.projects-sidebar {
width: 240px;
border-right: 1px solid var(--border);
overflow-y: auto;
padding: 8px;
flex-shrink: 0;
}
.sidebar-section-header {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
color: var(--muted);
padding: 4px 4px;
margin-bottom: 4px;
letter-spacing: .5px;
}
.project-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 8px;
border-radius: 6px;
cursor: pointer;
transition: background .15s;
}
.project-item:hover {
background: rgba(255,255,255,.05);
}
.project-name {
font-size: 12px;
color: var(--text);
}
.project-task-count {
font-size: 10px;
color: var(--muted);
background: rgba(255,255,255,.1);
padding: 2px 6px;
border-radius: 10px;
}
.empty-hint {
font-size: 11px;
color: var(--muted);
padding: 8px;
text-align: center;
}
.task-item {
display: flex;
align-items: center;
gap: 6px;
padding: 5px 8px;
border-radius: 4px;
font-size: 11px;
cursor: pointer;
}
.task-item:hover {
background: rgba(255,255,255,.05);
}
.task-check {
width: 16px;
height: 16px;
border: 1px solid var(--border2);
border-radius: 3px;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
cursor: pointer;
flex-shrink: 0;
color: var(--muted);
}
.task-check:hover {
border-color: var(--accent);
color: var(--accent);
}
.task-title {
flex: 1;
color: var(--text);
font-size: 11px;
line-height: 1.3;
}
.task-title.done {
text-decoration: line-through;
color: var(--muted);
}
.task-prio {
font-size: 10px;
flex-shrink: 0;
}
.task-due {
font-size: 10px;
color: var(--muted);
}
.task-tag {
font-size: 9px;
background: rgba(255,255,255,.1);
padding: 1px 4px;
border-radius: 3px;
color: var(--muted);
}
.projects-kanban {
flex: 1;
display: flex;
gap: 8px;
padding: 8px 12px;
overflow-x: auto;
overflow-y: hidden;
}
.kanban-col {
flex: 1;
min-width: 180px;
max-width: 280px;
display: flex;
flex-direction: column;
background: rgba(255,255,255,.03);
border-radius: 8px;
overflow: hidden;
}
.kanban-col-header {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
color: var(--muted);
padding: 8px 10px;
letter-spacing: .5px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.kanban-col-content {
flex: 1;
overflow-y: auto;
padding: 8px;
display: flex;
flex-direction: column;
gap: 6px;
}
.kanban-card {
background: var(--surface);
border-radius: 6px;
padding: 8px 10px;
cursor: pointer;
transition: transform .15s, box-shadow .15s;
border: 1px solid var(--border);
}
.kanban-card:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0,0,0,.3);
}
.kanban-card-header {
font-size: 12px;
color: var(--text);
margin-bottom: 4px;
line-height: 1.3;
}
.kanban-project {
font-size: 10px;
color: var(--accent);
display: block;
margin-bottom: 4px;
}
.kanban-card-footer {
display: flex;
gap: 4px;
align-items: center;
flex-wrap: wrap;
margin-top: 4px;
}
.kanban-due {
font-size: 9px;
color: var(--muted);
}
.kanban-tag {
font-size: 9px;
background: rgba(255,255,255,.1);
padding: 1px 4px;
border-radius: 3px;
color: var(--muted);
}
/* Expanded Mode - reusable for all panels */
.panel-view.panel-expanded {
display: flex !important;
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100vw !important;
height: 100vh !important;
z-index: 1000 !important;
background: var(--bg) !important;
}
/* ── Task Detail Modal ─────────────────────────────────────────────────────── */
.modal-overlay {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,.6);
backdrop-filter: blur(4px);
z-index: 2000;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.modal-card {
background: var(--surface);
border: 1px solid var(--border2);
border-radius: 12px;
width: 100%;
max-width: 520px;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 20px 60px rgba(0,0,0,.5);
}
.modal-header {
display: flex;
align-items: center;
padding: 16px 20px 12px;
border-bottom: 1px solid var(--border);
}
.modal-title {
font-size: 15px;
font-weight: 600;
color: var(--text);
flex: 1;
}
.modal-close {
background: none;
border: none;
color: var(--muted);
font-size: 20px;
cursor: pointer;
padding: 0 4px;
line-height: 1;
}
.modal-close:hover { color: var(--text); }
.modal-body {
padding: 16px 20px;
display: flex;
flex-direction: column;
gap: 12px;
}
.modal-field {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
}
.modal-field label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
color: var(--muted);
letter-spacing: .5px;
}
.modal-field-row {
display: flex;
gap: 12px;
}
.modal-input,
.modal-select {
background: rgba(255,255,255,.05);
border: 1px solid var(--border2);
border-radius: 6px;
color: var(--text);
padding: 8px 10px;
font-size: 13px;
outline: none;
width: 100%;
}
.modal-input:focus,
.modal-select:focus {
border-color: var(--accent);
}
.modal-select option {
background: var(--surface);
color: var(--text);
}
.modal-meta {
font-size: 11px;
color: var(--muted);
margin-top: 4px;
}
.modal-footer {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 20px 16px;
border-top: 1px solid var(--border);
}
.modal-btn-primary {
background: var(--accent);
border: none;
border-radius: 6px;
color: var(--bg);
padding: 8px 16px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
}
.modal-btn-primary:hover { opacity: .9; }
.modal-btn-secondary {
background: transparent;
border: 1px solid var(--border2);
border-radius: 6px;
color: var(--muted);
padding: 8px 16px;
font-size: 13px;
cursor: pointer;
}
.modal-btn-secondary:hover {
border-color: var(--muted);
color: var(--text);
}
.modal-btn-danger {
background: transparent;
border: 1px solid #ef4444;
border-radius: 6px;
color: #ef4444;
padding: 8px 16px;
font-size: 13px;
cursor: pointer;
}
.modal-btn-danger:hover {
background: #ef4444;
color: white;
}
/* ── Task Owner Badge ───────────────────────────────────────────────────────── */
.task-owner {
font-size: 10px;
flex-shrink: 0;
}
/* ── Projects Sidebar Section Headers ─────────────────────────────────────── */
.sidebar-section-header {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
color: var(--muted);
padding: 4px 4px;
margin-bottom: 4px;
letter-spacing: .5px;
}
/* ── Task Item: clicking opens modal ───────────────────────────────────────── */
.task-item {
cursor: pointer;
}
/* ── Due Date Colors ───────────────────────────────────────────────────────── */
.task-due {
font-size: 10px;
color: var(--muted);
}
.task-due.overdue {
color: var(--accent);
}
/* ── Quick Add subtle styling ───────────────────────────────────────────────── */
.projects-quick-add input::placeholder {
color: var(--muted);
font-size: 12px;
}
/* ── Stats Grid ────────────────────────────────────────────────────────────── */
.stats-grid {
display: flex;
gap: 8px;
padding: 8px 12px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
flex-wrap: wrap;
}
.stat-card {
flex: 1;
min-width: 60px;
background: rgba(255,255,255,.04);
border: 1px solid var(--border2);
border-radius: 8px;
padding: 8px 10px;
text-align: center;
}
.stat-card.stat-overdue {
border-color: #ef444450;
background: rgba(239,68,68,.08);
}
.stat-value {
font-size: 18px;
font-weight: 700;
color: var(--text);
line-height: 1;
}
.stat-label {
font-size: 10px;
color: var(--muted);
margin-top: 2px;
text-transform: uppercase;
letter-spacing: .3px;
}
/* ── Due Date in Kanban ───────────────────────────────────────────────────── */
.kanban-due {
font-size: 9px;
color: var(--muted);
}
.kanban-due.overdue {
color: #ef4444;
font-weight: 600;
}
/* ── Owner Badge ──────────────────────────────────────────────────────────── */
.owner-badge {
display: inline-flex;
align-items: center;
gap: 3px;
font-size: 9px;
padding: 1px 5px;
border-radius: 10px;
background: rgba(255,255,255,.1);
color: var(--muted);
}
.owner-badge.rose { background: rgba(251,113,133,.15); color: #fb7185; }
.owner-badge.agent { background: rgba(132,204,22,.15); color: #a3e635; }
.owner-badge.sabo, .owner-badge.user { background: rgba(96,165,250,.15); color: #60a5fa; }

View File

@@ -91,6 +91,7 @@ async function populateModelDropdown(){
_applyModelToDropdown(data.default_model, sel);
}
if(typeof syncModelChip==='function') syncModelChip();
if(typeof syncAgentChip==='function') syncAgentChip();
// Kick off a background live-model fetch for the active provider.
// This runs after the static list is already shown (no blocking flicker).
if(data.active_provider) _fetchLiveModels(data.active_provider, sel);
@@ -98,6 +99,7 @@ async function populateModelDropdown(){
// API unavailable -- keep the hardcoded HTML options as fallback
console.warn('Failed to load models from server:',e.message);
if(typeof syncModelChip==='function') syncModelChip();
if(typeof syncAgentChip==='function') syncAgentChip();
}
}
@@ -148,6 +150,7 @@ async function _fetchLiveModels(provider, sel){
// Restore selection
if(currentVal) _applyModelToDropdown(currentVal, sel);
if(typeof syncModelChip==='function') syncModelChip();
if(typeof syncAgentChip==='function') syncAgentChip();
console.log('[hermes] Live models loaded for',provider+':',added,'new models added');
}
}catch(e){
@@ -315,6 +318,108 @@ window.addEventListener('resize',()=>{
if(dd&&dd.classList.contains('open')) _positionModelDropdown();
});
// ── Agent selector dropdown ─────────────────────────────────────────────────
const AGENT_META = {
rose: {emoji:'🌹', name:'Rose', domain:'Orchestrator & Main Interface'},
lotus: {emoji:'🪷', name:'Lotus', domain:'Health, Fitness & Recovery'},
'forget-me-not':{emoji:'🌼', name:'Forget-me-not', domain:'Calendar, Time & Social'},
sunflower: {emoji:'🌻', name:'Sunflower', domain:'Finance, Wealth & Subscriptions'},
iris: {emoji:'⚜️', name:'Iris', domain:'Career, Learning & Focus'},
ivy: {emoji:'🌿', name:'Ivy', domain:'Smart Home & Environment'},
dandelion: {emoji:'🛡️', name:'Dandelion', domain:'Communication Triage'},
root: {emoji:'🌳', name:'Root', domain:'DevOps, Logs & System Health'},
};
function renderAgentDropdown(){
const dd=$('composerAgentDropdown');
const sel=$('agentSelect');
if(!dd||!sel) return;
const current=sel.value;
const groups={'Tier-1':['rose'],'Tier-2':['lotus','forget-me-not','sunflower','iris','ivy','dandelion','root']};
let html='';
for(const [grp,ids] of Object.entries(groups)){
html+=`<div class="model-group">${grp}</div>`;
for(const id of ids){
const m=AGENT_META[id];
const active=id===current?' active':'';
html+=`<div class="agent-opt${active}" onclick="selectAgentFromDropdown('${id}')">
<span class="agent-opt-name">${m.emoji} ${m.name}</span>
<span class="agent-opt-domain">${m.domain}</span>
</div>`;
}
}
dd.innerHTML=html;
}
function toggleAgentDropdown(){
const dd=$('composerAgentDropdown');
const chip=$('composerAgentChip');
if(!dd||!chip) return;
if(dd.classList.contains('open')){
dd.classList.remove('open');
chip.classList.remove('active');
return;
}
closeModelDropdown();
if(typeof closeProfileDropdown==='function') closeProfileDropdown();
if(typeof closeWsDropdown==='function') closeWsDropdown();
renderAgentDropdown();
dd.classList.add('open');
chip.classList.add('active');
// position below chip
const chipRect=chip.getBoundingClientRect();
const wrap=chip.closest('.composer-agent-wrap');
const wrapRect=wrap.getBoundingClientRect();
dd.style.left=(chipRect.left-wrapRect.left)+'px';
}
function closeAgentDropdown(){
const dd=$('composerAgentDropdown');
const chip=$('composerAgentChip');
if(dd) dd.classList.remove('open');
if(chip) chip.classList.remove('active');
}
function selectAgentFromDropdown(value){
const sel=$('agentSelect');
if(!sel) return;
sel.value=value;
syncAgentChip();
closeAgentDropdown();
// Save to session / localStorage
if(typeof S!=='undefined'&&S.session) S.session.agent=value;
try{localStorage.setItem('hermes-webui-agent',value);}catch(e){}
}
function syncAgentChip(){
const sel=$('agentSelect');
const icon=$('composerAgentIcon');
const label=$('composerAgentLabel');
if(!sel||!icon||!label) return;
const m=AGENT_META[sel.value]||AGENT_META.rose;
icon.textContent=m.emoji;
label.textContent=m.name;
}
// Init agent chip from localStorage on load
window.addEventListener('DOMContentLoaded',()=>{
try{
const saved=localStorage.getItem('hermes-webui-agent');
if(saved){
const sel=$('agentSelect');
if(sel&&Array.from(sel.options).some(o=>o.value===saved)){
sel.value=saved;
}
}
}catch(e){}
syncAgentChip();
});
document.addEventListener('click',e=>{
if(!e.target.closest('#composerAgentChip') && !e.target.closest('#composerAgentDropdown')) closeAgentDropdown();
});
// ── Scroll pinning ──────────────────────────────────────────────────────────
// When streaming, auto-scroll only if the user hasn't manually scrolled up.
// Once the user scrolls back to within 80px of the bottom, re-pin.
@@ -1033,6 +1138,7 @@ function syncTopbar(){
document.title=window._botName||'Hermes';
if(typeof syncWorkspaceDisplays==='function') syncWorkspaceDisplays();
if(typeof syncModelChip==='function') syncModelChip();
if(typeof syncAgentChip==='function') syncAgentChip();
if(typeof _syncHermesPanelSessionActions==='function') _syncHermesPanelSessionActions();
else {
const sidebarName=$('sidebarWsName');
@@ -1072,6 +1178,7 @@ function syncTopbar(){
}
}
if(typeof syncModelChip==='function') syncModelChip();
if(typeof syncAgentChip==='function') syncAgentChip();
// Show Clear button only when session has messages
const clearBtn=$('btnClearConv');
if(clearBtn) clearBtn.style.display=(S.messages&&S.messages.filter(msg=>msg.role!=='tool').length>0)?'':'none';
@@ -1283,10 +1390,6 @@ function renderMessages(){
scrollToBottom();
// Apply syntax highlighting after DOM is built
requestAnimationFrame(()=>{highlightCode();addCopyButtons();renderMermaidBlocks();renderKatexBlocks();});
// Refresh todo panel if it's currently open
if(typeof loadTodos==='function' && document.getElementById('panelTodos') && document.getElementById('panelTodos').classList.contains('active')){
loadTodos();
}
}
function toolIcon(name){