
    4rj7H                     V   d dl mZmZmZmZ d dlZd dlZ ee      Zddddddd	d
dddddddd
ddddddddddddddddddd gZ	d! Z
d" Zd#Zej                  d$      d%        Zej                  d&      d'        Zej                  d(d)g*      d+        Zd,Zd-d.d/d0d1d2d-d3d4d0d1d2d-d5d6d0d1d2d7d8d9d:d;d2d7d<d=d:d;d2d7d>d?d:d;d2d7d@dAd:d;d2gZdB ZdC Zej                  dDdEg*      dF        Zej                  dDd)g*      dG        ZdHZh dIZdJ Zej                  dKdEg*      dL        Zej                  dMd)g*      dN        Zej                  dOdPg*      dQ        Zej                  dOdRg*      dS        Zej                  dTd)g*      dU        ZedVk(  rej?                  dWdXdYZ       yy)[    )Flaskjsonifyrequestrender_template_stringNzhermes-komfortzHermes Workspacez%Arbeitsoberflaeche fuer Hermes Carlo.u   ⚕z5https://hermes-workspace.agentsolutions-mallorca.com/zhermes-carlo-komfortz/opt/struktur/hermes-ui-auswahlzJhttps://cockpit-carlo.agentsolutions-mallorca.com/personas/?instance=carlo)idnamedesciconurl	containercomposepersona_urlzhermes-carlozHermes Carloz Produktive Hermes-Carlo-Instanz.u   ⚡z0https://hermes-carlo.agentsolutions-mallorca.comz/opt/struktur/hermes-carlo)r   r   r	   r
   r   r   r   r   zcarlo-workerzWorker aufrufenzSDirekter Aufruf der laufenden Worker-Oberflaeche fuer KI-Aufgaben und Automationen.u   🤖z*https://worker.agentsolutions-mallorca.comT)r   r   r	   r
   r   r   r   	open_onlymirofishMiroFishzCMulti-Agenten-Simulation zur Zukunftsvorhersage und Marktforschung.u   🐟z,https://mirofish.agentsolutions-mallorca.comz/opt/struktur/mirofish)r   r   r	   r
   r   r   r   c                     	 t        j                  dddd| gddd      }|j                  j                         }|dk(  ry	|d
k(  ry|xs dS #  Y yxY w)Ndockerinspectz--formatz{{.State.Status}}T   capture_outputtexttimeoutrunningonlineexitedoffline	unbekannt)
subprocessrunstdoutstrip)r   resultstatuss      $/opt/struktur/dashboard/app-carlo.py
get_statusr&      sj    y*.A9MdA
 $$&Yx([(s   =A  A A Ac                     | d   rC| d   }|dk(  rd| d}n|dk(  rd| d}n|dk(  rd| d}t        j                  d	d	d	d
      }nt        j                  d|| d   gd	d	d      }|j                  dk(  S )Nr   startzcd z && docker compose up -dstopz && docker compose stoprestartz && docker compose restartT<   )shellr   r   r   r   r      r   r   )r   r    
returncode)serviceactioncompose_dircmdr#   s        r%   docker_actionr3      s    yi(W}$<=Cv}$;<Cy }$>?C44Y[\vw{34dB
 !!    uE  <!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cockpit Carlo</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body { background: #0f1117; color: #e2e8f0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; min-height: 100vh; }
  header { padding: 2rem 3rem; border-bottom: 1px solid #1e2330; }
  header h1 { font-size: 1.5rem; font-weight: 600; color: #fff; }
  header span { font-size: 0.85rem; color: #64748b; }
  .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 1.5rem; padding: 3rem; }
  .card { background: #161b27; border: 1px solid #1e2330; border-radius: 12px; padding: 1.5rem; }
  .card-header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 1rem; }
  .card-icon { font-size: 2rem; }
  .badge { font-size: 0.7rem; padding: 0.25rem 0.7rem; border-radius: 99px; font-weight: 600; }
  .badge.online { background: #1a3a2a; color: #4ade80; border: 1px solid #166534; }
  .badge.offline { background: #3a1a1a; color: #f87171; border: 1px solid #7f1d1d; }
  .badge.loading { background: #2a2a1a; color: #fbbf24; border: 1px solid #78350f; }
  .badge.planned { background: #1a2a3a; color: #93c5fd; border: 1px solid #1e3a5f; }
  .card-title { font-size: 1.1rem; font-weight: 600; color: #fff; margin-bottom: 0.4rem; }
  .card-desc { font-size: 0.85rem; color: #64748b; line-height: 1.5; margin-bottom: 1.2rem; }
  .card-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
  .btn { font-size: 0.75rem; padding: 0.4rem 0.9rem; border-radius: 6px; border: none; cursor: pointer; font-weight: 500; transition: all 0.15s; }
  .btn-open { background: #1e3a5f; color: #60a5fa; }
  .btn-open:hover { background: #1e4a7f; }
  .btn-restart { background: #1e3a2a; color: #4ade80; }
  .btn-restart:hover { background: #1e5a3a; }
  .btn-stop { background: #3a1e1e; color: #f87171; }
  .btn-stop:hover { background: #5a1e1e; }
  .btn-start { background: #1a3a2a; color: #4ade80; }
  .btn-start:hover { background: #1a5a3a; }
  .btn:disabled { opacity: 0.4; cursor: not-allowed; }

  .modal-overlay { display:none; position:fixed; inset:0; background:rgba(0,0,0,0.7); z-index:1000; align-items:center; justify-content:center; }
  .modal-overlay.visible { display:flex; }
  .modal { background:#1e2330; border:1px solid #2d3748; border-radius:14px; padding:2rem; min-width:420px; max-width:95vw; }
  .modal h2 { font-size:1.1rem; font-weight:700; color:#fff; margin-bottom:1.2rem; }
  .modal select { width:100%; padding:0.5rem 0.75rem; background:#0f1117; border:1px solid #2d3748; border-radius:7px; color:#e2e8f0; font-size:0.9rem; margin-bottom:1rem; }
  .modal-actions { display:flex; gap:0.75rem; justify-content:flex-end; margin-top:0.5rem; }
  .btn-cancel { background:#1e2330; border:1px solid #2d3748; color:#94a3b8; padding:0.45rem 1.1rem; border-radius:7px; cursor:pointer; font-size:0.85rem; }
  .btn-save { background:#1e3a5f; color:#60a5fa; border:none; padding:0.45rem 1.1rem; border-radius:7px; cursor:pointer; font-size:0.85rem; font-weight:600; }
  .current-model { font-size:0.8rem; color:#64748b; margin-bottom:1rem; }

  footer { text-align: center; padding: 2rem; color: #334155; font-size: 0.8rem; }
</style>
</head>
<body>
<header style="display:flex; align-items:center; justify-content:space-between;">
  <div>
    <h1>Cockpit Carlo</h1>
    <span>Werkzeuge & Dienste — VPS 31.70.68.228</span>
  </div>
  <a href="https://agentsolutions-mallorca.com" style="font-size:0.85rem; color:#3b82f6; text-decoration:none; border:1px solid #1e3a5f; padding:0.4rem 1rem; border-radius:8px;">&#8592; Zurück</a>
</header>

<div class="grid" id="grid">
  {% for s in services %}
  <div class="card" id="card-{{s.id}}">
    <div class="card-header">
      <div class="card-icon">{{s.icon}}</div>
      <span class="badge loading" id="badge-{{s.id}}">{% if s.get('external') %}extern{% elif s.get('planned') %}geplant{% else %}...{% endif %}</span>
    </div>
    <div class="card-title">{{s.name}}</div>
    <div class="card-desc">{{s.desc}}</div>
    <div class="card-actions">
      <a href="{{s.url}}" target="_blank"><button class="btn btn-open">Öffnen</button></a>
      {% if s.get('persona_url') %}<a href="{{s.persona_url}}" target="_blank"><button class="btn" style="background:#7c3aed;color:#fff">Personas</button></a>{% endif %}
      {% if not s.get('external') and not s.get('open_only') %}
      <button class="btn btn-restart" onclick="action('{{s.id}}','restart')">Restart</button>
      <button class="btn btn-stop" onclick="action('{{s.id}}','stop')">Stop</button>
      <button class="btn btn-start" onclick="action('{{s.id}}','start')">Start</button>
      {% if s.id == 'hermes-carlo' %}
      <button class="btn" style="background:#3a1a1a;color:#f87171;font-weight:700;" onclick="hermesJobsReset()">&#128465; Jobs Reset</button>
      {% endif %}
      {% if s.id == 'mirofish' %}
      <button class="btn" style="background:#1e2a3a;color:#a78bfa;" onclick="openMirofishModal()">🧠 Modell</button>
      {% endif %}
      {% endif %}
    </div>
  </div>
  {% endfor %}
</div>
<footer>Agent Solutions · VPS IONOS · 31.70.68.228</footer>
<script>
function updateStatus() {
  fetch('/api/status').then(r=>r.json()).then(data => {
    data.forEach(s => {
      const b = document.getElementById('badge-'+s.id);
      b.textContent = s.status;
      b.className = 'badge ' + (s.status === 'online' ? 'online' : s.status === 'offline' ? 'offline' : s.status === 'geplant' ? 'planned' : 'loading');
    });
  });
}
function action(id, act) {
  const b = document.getElementById('badge-'+id);
  b.textContent = '...';
  b.className = 'badge loading';
  fetch('/api/action', {
    method: 'POST',
    headers: {'Content-Type':'application/json'},
    body: JSON.stringify({id, action: act})
  }).then(()=> setTimeout(updateStatus, 3000));
}
updateStatus();
setInterval(updateStatus, 15000);
</script>

<div class="modal-overlay" id="mirofish-modal">
  <div class="modal">
    <h2>🐟 MiroFish — Modell wechseln</h2>
    <div class="current-model">Aktuell: <span id="modal-current-model">...</span></div>
    <select id="modal-model-select"></select>
    <div class="modal-actions">
      <button class="btn-cancel" onclick="closeMirofishModal()">Abbrechen</button>
      <button class="btn-save" id="btn-modal-save" onclick="saveMirofishModel()">Speichern &amp; Neustart</button>
    </div>
  </div>
</div>

<script>
async function openMirofishModal() {
  document.getElementById('mirofish-modal').classList.add('visible');
  const res = await fetch('/api/mirofish/model');
  const data = await res.json();
  document.getElementById('modal-current-model').textContent = data.current;
  const sel = document.getElementById('modal-model-select');
  sel.innerHTML = data.models.map(m =>
    `<option value="${m.model}" ${m.model===data.current?'selected':''}>[${m.provider}] ${m.label}</option>`
  ).join('');
}
function closeMirofishModal() {
  document.getElementById('mirofish-modal').classList.remove('visible');
}
async function saveMirofishModel() {
  const btn = document.getElementById('btn-modal-save');
  btn.disabled = true; btn.textContent = '⏳ Neustart...';
  const model = document.getElementById('modal-model-select').value;
  const res = await fetch('/api/mirofish/model', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({model})});
  const data = await res.json();
  if (data.ok) { btn.textContent = '✅ Gespeichert'; setTimeout(closeMirofishModal, 1500); }
  else { btn.textContent = '❌ Fehler'; btn.disabled = false; }
}
</script>

</body>
</html>/c                  ,    t        t        t              S )N)services)r   HTMLSERVICES r4   r%   indexr;      s    !$::r4   z/api/statusc            
          t        t        D  cg c]<  } | d   | j                  d      rdn | j                  d      rt        | d         ndd> c}       S c c} w )Nr   plannedgeplantr   extern)r   r$   )r   r9   getr&   )ss    r%   r$   r$      sx      ]e  f  XY1T7155;Kipqpupu  wB  qCR\]^_j]kRl  IQ  S  f  g  g  fs   AAz/api/actionPOST)methodsc                      t         j                  t        fdt        D        d       } | st	        ddi      dfS t        | d         }t	        d|i      S )Nc              3   :   K   | ]  }|d    d    k(  s|  yw)r   Nr:   ).0rA   datas     r%   	<genexpr>zaction.<locals>.<genexpr>   s      A!1T7d4j+@AAs   okFi  r0   )r   jsonnextr9   r   r3   )r/   rI   rG   s     @r%   r0   r0      sT    <<DAxA4HGe}%s**	wX	/BD":r4   z/opt/struktur/mirofish/.envOpenAIu%   GPT-4.1-mini (empfohlen, ~3€/15Rdn)zgpt-4.1-mini-2025-04-14zhttps://api.openai.com/v1zsk-proj-VBaZD9yxd4_eAzrXGNGyvr2Tn0dM1WxUl8yhjvvETdl5B_cJUpTfuXlr32gihSJ355aHfrcUqUT3BlbkFJKt2AXBxHRy3l7HrUwfrFOeT7ku7Z5cw8HRuXd8O2umxFWKvK1g6Zn6m2i9ZPq_44ah2egN0n8A)providerlabelmodelbase_urlapi_keyu   GPT-4.1 (~15€/15Rdn)zgpt-4.1-2025-04-14u   GPT-4o (~30€/15Rdn)zgpt-4o
OpenRouteru   DeepSeek-V3 (~2€/15Rdn)zdeepseek/deepseek-chatzhttps://openrouter.ai/api/v1zIsk-or-v1-3425729d152eb4a647e1db7b0411de55d01f178f846ba37caf1465df0f3e5c8fu   DeepSeek-R1 (~5€/15Rdn)zdeepseek/deepseek-reasoneru   Qwen2.5-72B (~2€/15Rdn)zqwen/qwen2.5-72b-instructu   Kimi K2 (~2€/15Rdn)zmoonshotai/kimi-k2c                      	 t        t              5 } | D ]A  }|j                  d      s|j                         j	                  dd      d   c cd d d        S  	 d d d        y# 1 sw Y   yxY w#  Y yxY w)NzLLM_MODEL_NAME==   r   )openMIROFISH_ENV
startswithr"   split)flines     r%   _read_mirofish_modelr\      s{    , 	91 9??#45::<--c15a88	9 	99	9 	9 s9   A. A"$A"	A. A"A. "A+'A. +A. .A2c                    t        t              5 }|j                         }d d d        | d   | d   | d   | d   | d   d}g }D ]k  }d}|j                         D ]@  \  }}|j	                  |dz         s|j                  |dz   |z   t        d      z          d} n |r[|j                  |       m t        t        d	      5 }|j                  |       d d d        y # 1 sw Y   xY w# 1 sw Y   y xY w)
NrQ   rP   rO   )LLM_API_KEYLLM_BASE_URLLLM_MODEL_NAMEOPENAI_API_KEYOPENAI_API_BASE_URLFrT   
   Tw)rV   rW   	readlinesitemsrX   appendchr
writelines)		model_cfgrZ   lineskeys	new_linesr[   replacedkvs	            r%   _write_mirofish_envrq      s   	l	 q  )3(4(1(3(4D I #JJL 	DAqq3w'  S1s2w!67		
 T"# 
lC	   A	Y   ' &   s   CC)C&)C2z/api/mirofish/modelGETc                  :    t               } t        | t        d      S )N)currentmodels)r\   r   MIROFISH_MODELS)rt   s    r%   mirofish_model_getrw     s    "$Gw/BCCr4   c                      t         j                  } | j                  d      t        fdt        D        d       }|st        ddd      dfS t        |       t        j                  ddd	
       t        dd      S )NrO   c              3   4   K   | ]  }|d    k(  s|  yw)rO   Nr:   )rF   mmodel_ids     r%   rH   z%mirofish_model_set.<locals>.<genexpr>
  s     EaajH.DEs   FzUnbekanntes ModellrI   error  z<cd /opt/struktur/mirofish && docker compose restart mirofishTr+   )r,   r   )rI   rO   )	r   rJ   r@   rK   rv   r   rq   r   r    )rG   cfgr{   s     @r%   mirofish_model_setr     sp    <<Dxx H
E?Et
LCe.BCDcIINNQY]gij$233r4   z)/opt/struktur/hermes-carlo/data/kanban.db>   donetodotriagebacklogblockedin_progressc                  Z    dd l } | j                  t              }| j                  |_        |S )Nr   )sqlite3connect	KANBAN_DBRowrow_factory)r   conns     r%   _kanban_connr     s$    ??9%D{{DKr4   z/api/kanban/tasksc                  H   t         j                  j                  d      } t               }| r"|j	                  d| f      j                         }n|j	                  d      j                         }|j                          t        |D cg c]  }t        |       c}      S c c}w )Nr$   zSELECT id, title, body, status, priority, created_by, created_at FROM tasks WHERE status=? ORDER BY priority DESC, created_at DESCzsSELECT id, title, body, status, priority, created_by, created_at FROM tasks ORDER BY priority DESC, created_at DESC)	r   argsr@   r   executefetchallcloser   dict)r$   r   rowsrs       r%   kanban_tasks_listr     s    \\h'F>D|| QI
 (* 	
 || B

(* 	 	JJLT*DG*++*s   Bz/api/kanban/taskc                  *   dd l } dd l}dd l}t        j                  xs i }|j                  d      xs dj                         }|st        ddd      dfS |j                  d      xs |j                  d	      xs dj                         }t        |j                  d
d            }|j                  dd      }|t        vrd}d|j                  dd|j                               d d z   }t               }		 |	j                  d|||||dt        |j                               df       |	j                          |	j!                          t        d|||d      S # t        $ r3}
|	j!                          t        dt#        |
      d      dfcY d }
~
S d }
~
ww xY w)Nr   title Fztitle fehltr|   r~   bodydescriptionpriority2   r$   r   proj_z	[^a-z0-9]_(   zINSERT OR IGNORE INTO tasks (id, title, body, status, priority, created_by, created_at, workspace_kind) VALUES (?,?,?,?,?,?,?,?)carloscratch  T)rI   r   r   r$   )r   retimer   rJ   r@   r"   r   intVALID_STATUSESsublowerr   r   commit	Exceptionr   str)r   r   r   rG   r   r   r   r$   task_idr   es              r%   kanban_task_creater   -  sf   <<2DXXg$"++-Eem<=sBB ADHH]$;ArHHJD488J+,Hxx),F^#|S%++-@"EEG>D< OeT68Wc$))+>NPYZ	
 	 	JJL$gPQQ	  <

ec!f56;;<s   5AE 	F(FFFz/api/kanban/task/<task_id>PATCHc                 <   dd l }dd l}t        j                  xs i }|j	                         D ci c]  \  }}|dv s|| }}}d|v r"|d   t
        vrt        ddt
         d      dfS |st        ddd      dfS d	j                  d
 |D              }t        |j                               | gz   }t               }	|	j                  d| d|       |	j                          |	j                          t        d| t        |j                               d      S c c}}w )Nr   )r   r   r$   r   r$   Fu   Ungültiger Status. Erlaubt: r|   r~   u   Keine gültigen Felderz, c              3   &   K   | ]	  }| d   yw)z=?Nr:   )rF   ro   s     r%   rH   z%kanban_task_update.<locals>.<genexpr>P  s     5aS85s   zUPDATE tasks SET z WHERE id=?T)rI   r   updated)r   r   r   rJ   rf   r   r   joinlistvaluesr   r   r   r   rl   )
r   r   r   rG   ro   rp   allowed
set_clauser   r   s
             r%   kanban_task_updater   G  s   <<2D $

]15\0\q!t]G]7wx0Fe0MnM].^_`beeee.FGH#MM5W55J'.."#wi/F>DLL$ZL<fEKKMJJL$g$w||~:NOPP ^s
   DDDELETEc                     t               }|j                  d| f       |j                          |j                          t	        d| d      S )NzDELETE FROM tasks WHERE id=?T)rI   r   )r   r   r   r   r   )r   r   s     r%   kanban_task_deleter   X  s<    >DLL/'<KKMJJL$g.//r4   z/api/hermes-resetc                  .   dd l } dd l} 	 | j                  j                  ddd      }| j                  j	                  |d      5 }t        dd	i      cd d d        S # 1 sw Y   y xY w# t        $ r#}t        d
t        |      d      dfcY d }~S d }~ww xY w)Nr   zhttp://127.0.0.1:9099/resetrB   r4   )methodrG   rc   )r   rI   TFr|   r   )urllib.requesturllib.errorr   Requesturlopenr   r   r   )urllibreqr   r   s       r%   hermes_resetr   a  s    '<nn$$%B6X[$\^^##C#4 	)D$<(	) 	) 	) <ec!f56;;<s;   ;A( A	A( A%!A( %A( (	B1B	BB__main__z	127.0.0.1i  F)hostportdebug) flaskr   r   r   r   r   rJ   __name__appr9   r&   r3   r8   router;   r$   r0   rW   rv   r\   rq   rw   r   r   r   r   r   r   r   r   r   r    r:   r4   r%   <module>r      sb   A A Ho %7Ahrw  Ax  G]  jK  \h  i>;]gl  vh  wE  Vb  oK  L#4  ?T  ^d  mY  hv  C_  nr  sz3x  CI  R@  OY  f~  	 ""Sj 3; ; =g g =6(+ , -(OZs  D_  pV  W(@[o  E`  qW  X(?[c  E`  qW  X(C[s  Ec  p{  |(C[w  Ec  p{  |(C[v  Ec  p{  |(?[o  Ec  p{  |	 .  5'2D 3D  6(34 44 8	P 0, 1, x0R 1R2 '';Q <Q  '(<0 =0 1< 2< zGG4uG5 r4   