@omerrgocmen/crewctl 1.1.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +193 -193
- package/orchestrator/README.md +473 -462
- package/orchestrator/config.default.json +79 -71
- package/orchestrator/roles/executor.md +57 -57
- package/orchestrator/roles/operator.md +77 -77
- package/orchestrator/roles/planner.md +58 -58
- package/orchestrator/roles/reviewer.md +63 -63
- package/orchestrator/skills/acceptance-criteria.md +18 -18
- package/orchestrator/skills/accessibility-audit.md +16 -16
- package/orchestrator/skills/accessible-forms.md +16 -16
- package/orchestrator/skills/api-design.md +27 -27
- package/orchestrator/skills/api-documentation.md +16 -16
- package/orchestrator/skills/architecture-decision-record.md +18 -18
- package/orchestrator/skills/authentication-design.md +16 -16
- package/orchestrator/skills/authorization-review.md +16 -16
- package/orchestrator/skills/backward-compatibility.md +17 -17
- package/orchestrator/skills/changelog-writing.md +16 -16
- package/orchestrator/skills/ci-pipeline-design.md +16 -16
- package/orchestrator/skills/cli-design.md +16 -16
- package/orchestrator/skills/code-review.md +27 -27
- package/orchestrator/skills/configuration-management.md +16 -16
- package/orchestrator/skills/container-review.md +16 -16
- package/orchestrator/skills/contract-testing.md +16 -16
- package/orchestrator/skills/dashboard-design.md +16 -16
- package/orchestrator/skills/database-migration.md +16 -16
- package/orchestrator/skills/database-schema-design.md +16 -16
- package/orchestrator/skills/debugging.md +26 -26
- package/orchestrator/skills/dependency-review.md +16 -16
- package/orchestrator/skills/design-review.md +29 -29
- package/orchestrator/skills/design-system.md +16 -16
- package/orchestrator/skills/docs-api-reference.md +16 -16
- package/orchestrator/skills/docs-troubleshooting.md +16 -16
- package/orchestrator/skills/docs-tutorial.md +16 -16
- package/orchestrator/skills/docs-writing.md +25 -25
- package/orchestrator/skills/empty-error-loading-states.md +16 -16
- package/orchestrator/skills/end-to-end-testing.md +16 -16
- package/orchestrator/skills/error-handling.md +16 -16
- package/orchestrator/skills/frontend-design.md +28 -28
- package/orchestrator/skills/git-commit-writing.md +16 -16
- package/orchestrator/skills/graphql-design.md +16 -16
- package/orchestrator/skills/incident-runbook.md +18 -18
- package/orchestrator/skills/input-validation.md +16 -16
- package/orchestrator/skills/integration-testing.md +16 -16
- package/orchestrator/skills/interaction-design.md +16 -16
- package/orchestrator/skills/landing-page-design.md +16 -16
- package/orchestrator/skills/observability-design.md +16 -16
- package/orchestrator/skills/openapi-contract.md +16 -16
- package/orchestrator/skills/performance-profiling.md +16 -16
- package/orchestrator/skills/privacy-review.md +16 -16
- package/orchestrator/skills/property-based-testing.md +16 -16
- package/orchestrator/skills/pull-request-writing.md +16 -16
- package/orchestrator/skills/refactoring.md +16 -16
- package/orchestrator/skills/release-readiness.md +16 -16
- package/orchestrator/skills/responsive-design.md +16 -16
- package/orchestrator/skills/secrets-management.md +16 -16
- package/orchestrator/skills/secure-file-upload.md +16 -16
- package/orchestrator/skills/security-review.md +26 -26
- package/orchestrator/skills/semantic-versioning.md +17 -17
- package/orchestrator/skills/seo-on-page.md +16 -16
- package/orchestrator/skills/seo-structured-data.md +16 -16
- package/orchestrator/skills/seo-technical-audit.md +16 -16
- package/orchestrator/skills/sql-query-review.md +16 -16
- package/orchestrator/skills/supply-chain-security.md +16 -16
- package/orchestrator/skills/test-strategy.md +16 -16
- package/orchestrator/skills/threat-modeling.md +16 -16
- package/orchestrator/skills/unit-testing.md +16 -16
- package/orchestrator/skills/write-tests.md +28 -28
- package/orchestrator/src/cli-registry.js +740 -682
- package/orchestrator/src/cli.js +140 -135
- package/orchestrator/src/doctor.js +64 -64
- package/orchestrator/src/engine.js +1735 -1630
- package/orchestrator/src/schedule.js +126 -126
- package/orchestrator/src/server.js +818 -795
- package/orchestrator/src/skill-registry.js +272 -272
- package/orchestrator/src/store.js +470 -426
- package/orchestrator/web/OrbitControls.js +1417 -1417
- package/orchestrator/web/board.html +146 -146
- package/orchestrator/web/code.html +213 -213
- package/orchestrator/web/flow.html +741 -741
- package/orchestrator/web/index.html +602 -602
- package/orchestrator/web/jsm/postprocessing/EffectComposer.js +231 -231
- package/orchestrator/web/jsm/postprocessing/MaskPass.js +104 -104
- package/orchestrator/web/jsm/postprocessing/Pass.js +95 -95
- package/orchestrator/web/jsm/postprocessing/RenderPass.js +99 -99
- package/orchestrator/web/jsm/postprocessing/ShaderPass.js +77 -77
- package/orchestrator/web/jsm/postprocessing/UnrealBloomPass.js +415 -415
- package/orchestrator/web/jsm/shaders/CopyShader.js +45 -45
- package/orchestrator/web/jsm/shaders/LuminosityHighPassShader.js +66 -66
- package/orchestrator/web/site.webmanifest +12 -12
- package/orchestrator/web/three.module.min.js +6 -6
- package/package.json +51 -51
|
@@ -1,213 +1,213 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="tr">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
-
<title>Canlı Kodlama · CrewCtl</title>
|
|
7
|
-
<link rel="icon" href="/favicon.ico" sizes="any">
|
|
8
|
-
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
|
9
|
-
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
|
10
|
-
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
|
11
|
-
<link rel="manifest" href="/site.webmanifest">
|
|
12
|
-
<style>
|
|
13
|
-
*{box-sizing:border-box}
|
|
14
|
-
:root{--bg:#090c12;--panel:#111826;--panel2:#182234;--line:#243149;--tx:#e9eef7;--dim:#8494ab;--acc:#5e8bff;--ok:#2fd08a;--warn:#eab24d;--err:#f2565f;--purple:#a98bff;
|
|
15
|
-
--headerbg:#0a0f1acc;--input:#0b111c;--nodebg:#0b1220;--shadow:0 14px 40px #0008;
|
|
16
|
-
--appbg:radial-gradient(1200px 620px at 50% -14%,#5e8bff16,transparent),radial-gradient(1000px 520px at 90% -6%,#a98bff12,transparent),var(--bg)}
|
|
17
|
-
[data-theme=light]{--bg:#edf1f8;--panel:#ffffff;--panel2:#f3f6fc;--line:#dce4ef;--tx:#131a27;--dim:#5a6779;--acc:#3a63f0;--ok:#0ca678;--warn:#a76a10;--err:#dc434e;--purple:#7350f0;
|
|
18
|
-
--headerbg:#ffffffe6;--input:#f4f7fd;--nodebg:#f7f9fd;--shadow:0 14px 36px #7a90b820;
|
|
19
|
-
--appbg:radial-gradient(1200px 620px at 50% -14%,#3a63f012,transparent),radial-gradient(1000px 520px at 90% -6%,#7350f00e,transparent),var(--bg)}
|
|
20
|
-
body{margin:0;background:var(--appbg);background-attachment:fixed;color:var(--tx);font:14px/1.5 Inter,Segoe UI,system-ui,sans-serif;transition:background .3s,color .3s}
|
|
21
|
-
button{cursor:pointer;color:var(--tx);background:var(--panel2);border:1px solid var(--line);border-radius:8px;padding:7px 11px;font:inherit;transition:border-color .18s,transform .1s,box-shadow .18s}
|
|
22
|
-
button:hover{border-color:var(--acc);box-shadow:0 4px 14px color-mix(in srgb,var(--acc) 16%,transparent)}button:active{transform:translateY(1px)}
|
|
23
|
-
a.btn{text-decoration:none;display:inline-block}
|
|
24
|
-
code,pre{font-family:ui-monospace,SFMono-Regular,Consolas,monospace}
|
|
25
|
-
/* Header */
|
|
26
|
-
header{height:58px;display:flex;align-items:center;gap:12px;padding:0 16px;background:var(--headerbg);backdrop-filter:blur(10px);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:20}
|
|
27
|
-
h1{font-size:15px;margin:0;letter-spacing:.2px}.brand{color:var(--acc);font-weight:800}.sep{color:var(--dim)}.grow{flex:1}
|
|
28
|
-
.pill{border:1px solid var(--line);background:var(--panel2);border-radius:999px;padding:5px 11px;color:var(--dim);font-size:12px;display:inline-flex;align-items:center;gap:6px}
|
|
29
|
-
.dot{width:8px;height:8px;border-radius:50%;background:var(--dim)}
|
|
30
|
-
.dot.on{background:var(--ok);box-shadow:0 0 10px var(--ok);animation:beat 1.6s ease-in-out infinite}
|
|
31
|
-
@keyframes beat{50%{box-shadow:0 0 3px var(--ok)}}
|
|
32
|
-
/* Banner + tiles */
|
|
33
|
-
.topbar{padding:14px 14px 0}
|
|
34
|
-
.banner{background:linear-gradient(120deg,var(--panel),var(--panel2));border:1px solid var(--line);border-radius:16px;padding:15px 18px;box-shadow:var(--shadow);display:flex;gap:16px;align-items:center;flex-wrap:wrap}
|
|
35
|
-
.banner .kick{font-size:10px;letter-spacing:1.4px;text-transform:uppercase;color:var(--acc);font-weight:800}
|
|
36
|
-
.banner .t{font-weight:800;font-size:17px;line-height:1.28;margin-top:2px;max-width:70ch}
|
|
37
|
-
.now{display:flex;align-items:center;gap:8px;color:var(--dim);font-size:12.5px;margin-top:6px}
|
|
38
|
-
.now .ndot{width:9px;height:9px;border-radius:50%;background:var(--dim);flex:none}
|
|
39
|
-
.now.live .ndot{background:var(--acc);box-shadow:0 0 10px var(--acc);animation:beat 1.3s ease-in-out infinite}
|
|
40
|
-
.now.done .ndot{background:var(--ok);box-shadow:0 0 8px var(--ok)}
|
|
41
|
-
.now.fail .ndot{background:var(--err);box-shadow:0 0 8px var(--err)}
|
|
42
|
-
.now b{color:var(--tx);font-weight:700}
|
|
43
|
-
.tiles{display:grid;grid-template-columns:repeat(4,minmax(96px,1fr));gap:10px;margin-left:auto}
|
|
44
|
-
.tile{background:color-mix(in srgb,var(--panel2) 60%,transparent);border:1px solid var(--line);border-radius:12px;padding:9px 12px;min-width:96px}
|
|
45
|
-
.tile .k{font-size:9.5px;text-transform:uppercase;letter-spacing:.6px;color:var(--dim)}
|
|
46
|
-
.tile .v{font-size:22px;font-weight:800;margin-top:1px;font-variant-numeric:tabular-nums;line-height:1.1}
|
|
47
|
-
.tile.add .v{color:var(--ok)}.tile.del .v{color:var(--err)}
|
|
48
|
-
.chips{display:flex;gap:8px;flex-wrap:wrap;margin:12px 14px 0}
|
|
49
|
-
.chip{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--line);background:var(--panel);border-radius:999px;padding:5px 11px;font-size:11.5px;color:var(--dim)}
|
|
50
|
-
.chip b{font-variant-numeric:tabular-nums}
|
|
51
|
-
.chip .c-created{color:var(--ok)}.chip .c-modified{color:var(--warn)}.chip .c-deleted{color:var(--err)}
|
|
52
|
-
/* Diff list */
|
|
53
|
-
main{padding:14px}
|
|
54
|
-
.panel{background:var(--panel);border:1px solid var(--line);border-radius:16px;box-shadow:var(--shadow);padding:14px}
|
|
55
|
-
.panel-head{display:flex;align-items:center;gap:10px;margin-bottom:10px}.panel-head h2{margin:0;font-size:13px;letter-spacing:.2px}
|
|
56
|
-
.muted{color:var(--dim);font-size:12px}
|
|
57
|
-
.legend{display:flex;gap:12px;flex-wrap:wrap;color:var(--dim);font-size:11px;margin-left:auto}
|
|
58
|
-
.legend i{font-style:normal;font-weight:700}.legend .lg-add{color:var(--ok)}.legend .lg-mod{color:var(--warn)}.legend .lg-del{color:var(--err)}
|
|
59
|
-
.empty{padding:34px 14px;text-align:center;color:var(--dim);border:1px dashed var(--line);border-radius:12px;background:color-mix(in srgb,var(--panel2) 40%,transparent)}
|
|
60
|
-
.files{display:flex;flex-direction:column;gap:8px}
|
|
61
|
-
.diff-file{border:1px solid var(--line);border-radius:10px;overflow:hidden;background:var(--panel);animation:pop .3s cubic-bezier(.2,.8,.2,1)}
|
|
62
|
-
@keyframes pop{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
|
|
63
|
-
.diff-file>summary{display:flex;align-items:center;gap:9px;padding:9px 11px;background:var(--panel2);list-style:none;font:11.5px ui-monospace;cursor:pointer}
|
|
64
|
-
.diff-file>summary::-webkit-details-marker{display:none}
|
|
65
|
-
.diff-file>summary:before{content:'›';font-size:17px;color:var(--dim);transition:.16s}.diff-file[open]>summary:before{transform:rotate(90deg)}
|
|
66
|
-
.diff-action{font-weight:800;text-transform:uppercase;font-size:9px;letter-spacing:.04em;padding:2px 6px;border-radius:5px;border:1px solid transparent}
|
|
67
|
-
.diff-action.created{color:var(--ok);background:color-mix(in srgb,var(--ok) 14%,transparent);border-color:color-mix(in srgb,var(--ok) 35%,transparent)}
|
|
68
|
-
.diff-action.modified{color:var(--warn);background:color-mix(in srgb,var(--warn) 14%,transparent);border-color:color-mix(in srgb,var(--warn) 35%,transparent)}
|
|
69
|
-
.diff-action.deleted{color:var(--err);background:color-mix(in srgb,var(--err) 14%,transparent);border-color:color-mix(in srgb,var(--err) 35%,transparent)}
|
|
70
|
-
.diff-path{color:var(--tx);font-weight:700;word-break:break-all}
|
|
71
|
-
.diff-stats{margin-left:auto;display:flex;gap:8px;white-space:nowrap;font-variant-numeric:tabular-nums}
|
|
72
|
-
.diff-add{color:var(--ok)}.diff-del{color:var(--err)}
|
|
73
|
-
.diff-code{overflow:auto;font:11px/1.5 ui-monospace,SFMono-Regular,Consolas,monospace;background:var(--input)}
|
|
74
|
-
.diff-hunk{padding:4px 10px;color:var(--acc);background:color-mix(in srgb,var(--acc) 12%,transparent);border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
|
|
75
|
-
.diff-line{display:grid;grid-template-columns:44px 44px 18px minmax(max-content,1fr);min-width:max-content;white-space:pre}
|
|
76
|
-
.diff-line .ln{padding:0 7px;text-align:right;color:var(--dim);user-select:none;border-right:1px solid var(--line);opacity:.75}
|
|
77
|
-
.diff-line .mark{padding-left:6px;user-select:none}.diff-line .code{padding:0 12px 0 5px;color:var(--tx)}
|
|
78
|
-
.diff-line.add{background:color-mix(in srgb,var(--ok) 13%,transparent)}.diff-line.add .mark{color:var(--ok)}
|
|
79
|
-
.diff-line.delete{background:color-mix(in srgb,var(--err) 13%,transparent)}.diff-line.delete .mark{color:var(--err)}
|
|
80
|
-
.diff-note{padding:9px 11px;color:var(--dim);font-size:11px}.diff-note.warn{color:var(--warn)}
|
|
81
|
-
.revert{border-color:color-mix(in srgb,var(--err) 45%,var(--line));color:var(--err);font-weight:700}
|
|
82
|
-
.revert:hover{border-color:var(--err);box-shadow:0 4px 14px color-mix(in srgb,var(--err) 20%,transparent)}
|
|
83
|
-
.revert:disabled{opacity:.45;cursor:default;box-shadow:none}
|
|
84
|
-
#toasts{position:fixed;right:16px;bottom:16px;display:flex;flex-direction:column;gap:8px;z-index:50}
|
|
85
|
-
.toast{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:10px 14px;box-shadow:var(--shadow);font-size:13px;max-width:360px;transition:opacity .3s}
|
|
86
|
-
.toast.ok{border-color:color-mix(in srgb,var(--ok) 45%,var(--line))}.toast.err{border-color:color-mix(in srgb,var(--err) 45%,var(--line))}
|
|
87
|
-
</style>
|
|
88
|
-
<link rel="stylesheet" href="app.css">
|
|
89
|
-
<script src="app.js" defer></script>
|
|
90
|
-
</head>
|
|
91
|
-
<body>
|
|
92
|
-
<header>
|
|
93
|
-
<h1><span class="brand">CrewCtl</span> <span class="sep">·</span> Canlı Kodlama</h1>
|
|
94
|
-
<span class="pill"><span id="dot" class="dot"></span><span id="engineState">durdu</span></span>
|
|
95
|
-
<span id="modePill" class="pill">boşta</span>
|
|
96
|
-
<div class="grow"></div>
|
|
97
|
-
<button id="revertBtn" class="revert" hidden onclick="revertVersion()" title="Bu görev öncesindeki koda dön"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg>Önceki sürüme dön</button>
|
|
98
|
-
<a class="btn" href="flow.html"><button>Ekip Akışı</button></a>
|
|
99
|
-
<button id="themeBtn" onclick="toggleTheme()" title="Açık / koyu tema"><svg class="i i-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg><svg class="i i-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg></button>
|
|
100
|
-
<a class="btn" href="index.html"><button>← Komuta Merkezi</button></a>
|
|
101
|
-
</header>
|
|
102
|
-
|
|
103
|
-
<div class="topbar">
|
|
104
|
-
<div class="banner">
|
|
105
|
-
<div style="min-width:0">
|
|
106
|
-
<div class="kick">Canlı Kodlama</div>
|
|
107
|
-
<div class="t" id="missionTitle">Görev bekleniyor</div>
|
|
108
|
-
<div class="now" id="nowLine"><span class="ndot"></span><span id="nowText">Bir görev başladığında burada ne yapıldığı canlı görünür.</span></div>
|
|
109
|
-
</div>
|
|
110
|
-
<div class="tiles">
|
|
111
|
-
<div class="tile"><div class="k">Değişen dosya</div><div class="v" id="tFiles">0</div></div>
|
|
112
|
-
<div class="tile add"><div class="k">Eklenen satır</div><div class="v" id="tAdd">+0</div></div>
|
|
113
|
-
<div class="tile del"><div class="k">Silinen satır</div><div class="v" id="tDel">−0</div></div>
|
|
114
|
-
<div class="tile"><div class="k">Tur · Çağrı</div><div class="v" id="tRounds" style="font-size:15px">0 · 0</div></div>
|
|
115
|
-
</div>
|
|
116
|
-
</div>
|
|
117
|
-
<div class="chips">
|
|
118
|
-
<span class="chip"><span class="c-created">●</span> <b id="cCreated">0</b> oluşturuldu</span>
|
|
119
|
-
<span class="chip"><span class="c-modified">●</span> <b id="cModified">0</b> değişti</span>
|
|
120
|
-
<span class="chip"><span class="c-deleted">●</span> <b id="cDeleted">0</b> silindi</span>
|
|
121
|
-
</div>
|
|
122
|
-
</div>
|
|
123
|
-
|
|
124
|
-
<main>
|
|
125
|
-
<div class="panel">
|
|
126
|
-
<div class="panel-head">
|
|
127
|
-
<h2>Değişen dosyalar</h2>
|
|
128
|
-
<span class="muted" id="filesSub">görev başından bu yana toplam</span>
|
|
129
|
-
<span class="legend"><span class="lg-add"><i>+</i> eklenen</span><span class="lg-del"><i>−</i> silinen</span> <span><i class="lg-add">eklendi</i> · <i class="lg-mod">değişti</i> · <i class="lg-del">silindi</i></span></span>
|
|
130
|
-
</div>
|
|
131
|
-
<div id="files" class="files"><div class="empty">Henüz dosya değişikliği yok. Bir görev çalışırken oluşturulan/değişen dosyalar burada, satır satır farkıyla belirir.</div></div>
|
|
132
|
-
</div>
|
|
133
|
-
</main>
|
|
134
|
-
|
|
135
|
-
<script>
|
|
136
|
-
const $=s=>document.querySelector(s);
|
|
137
|
-
function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]))}
|
|
138
|
-
/* ---- Tema ---- */
|
|
139
|
-
function applyTheme(t){document.documentElement.setAttribute('data-theme',t);try{localStorage.setItem('cliTheme',t)}catch{}}
|
|
140
|
-
function toggleTheme(){applyTheme(document.documentElement.getAttribute('data-theme')==='light'?'dark':'light')}
|
|
141
|
-
(function(){let t='dark';try{t=localStorage.getItem('cliTheme')||'dark'}catch{}applyTheme(t)})();
|
|
142
|
-
|
|
143
|
-
/* ---- Durum: "şu an ne oluyor" ---- */
|
|
144
|
-
function setNow(kind,text){const line=$('#nowLine');line.className='now'+(kind?(' '+kind):'');$('#nowText').innerHTML=text}
|
|
145
|
-
function roleOf(stage,agent){const s=String(stage||'');if(s.startsWith('operator'))return 'Operatör';return esc(agent||'Uzman')}
|
|
146
|
-
function phaseOf(stage){const s=String(stage||'');if(s==='operator-plan')return 'planı hazırlıyor';if(s==='operator-review')return 'sonucu değerlendiriyor';if(s==='operator-chat')return 'soruyu yanıtlıyor';if(s.startsWith('operator'))return 'ekibi yönetiyor';if(s==='delegate')return 'delege edilen işi uyguluyor';return 'çalışıyor'}
|
|
147
|
-
function applyStatus(s){$('#dot').className='dot'+(s.running?' on':'');$('#engineState').textContent=s.running?'çalışıyor':'durdu';$('#modePill').textContent=s.current?`${roleOf(s.current.stage,s.current.agent)} · ${s.current.stage||''}`:(s.busy?'meşgul':'boşta');engineActive=!!s.current;if(s.current&&s.current.id)currentTaskId=s.current.id;updateRevert()}
|
|
148
|
-
/* ---- Geri dönüş: bu görev öncesindeki sürüme dön ---- */
|
|
149
|
-
let currentTaskId='',engineActive=false;
|
|
150
|
-
function setCurrentTask(id){if(id&&id!==currentTaskId)currentTaskId=id;updateRevert()}
|
|
151
|
-
function updateRevert(){const b=$('#revertBtn');if(!b)return;if(!currentTaskId){b.hidden=true;return}b.hidden=false;b.disabled=!!engineActive;b.title=engineActive?'Görev çalışırken geri alınamaz; bitmesini bekleyin':'Bu görev öncesindeki koda dön'}
|
|
152
|
-
function toast(kind,msg){let box=document.getElementById('toasts');if(!box){box=document.createElement('div');box.id='toasts';document.body.appendChild(box)}const t=document.createElement('div');t.className='toast '+(kind||'');t.textContent=msg;box.appendChild(t);setTimeout(()=>{t.style.opacity='0';setTimeout(()=>t.remove(),300)},4600)}
|
|
153
|
-
async function revertVersion(){
|
|
154
|
-
if(!currentTaskId){toast('err','Geri dönülecek görev yok.');return}
|
|
155
|
-
if(engineActive){toast('err','Görev çalışırken geri alınamaz; bitmesini bekleyin.');return}
|
|
156
|
-
if(!await appConfirm({title:'Önceki sürüme dönülsün mü?',body:'Bu görev çalışmadan önceki koda dönülür:\n• Görev sonrası oluşan dosyalar silinir\n• Değişen/silinen dosyalar eski haline döner\n\nGeri alma işleminin kendisi de bir sürüm oluşturur, yani geri alınabilir.',confirmLabel:'Önceki sürüme dön',cancelLabel:'Vazgeç',danger:true}))return;
|
|
157
|
-
const b=$('#revertBtn');if(b)b.disabled=true;
|
|
158
|
-
try{
|
|
159
|
-
const r=await fetch(`/api/tasks/${currentTaskId}/restore`,{method:'POST'}).then(async res=>{const j=await res.json().catch(()=>({}));if(!res.ok)throw new Error(j.error||('HTTP '+res.status));return j});
|
|
160
|
-
toast('ok',`↩ Önceki sürüme dönüldü · ${r.restored} dosya geri yazıldı · ${r.deleted} silindi`);
|
|
161
|
-
setNow('done',`↩ <b>Önceki sürüme dönüldü</b> · ${r.restored} dosya geri yazıldı · ${r.deleted} sonradan oluşan dosya silindi`);
|
|
162
|
-
}catch(e){toast('err','Geri alınamadı: '+e.message)}
|
|
163
|
-
finally{if(b)b.disabled=false;updateRevert()}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/* ---- Toplamlar (tiles + chips) ---- */
|
|
167
|
-
function applyTotals(e){const c=e.counts||{created:0,modified:0,deleted:0},lc=e.lineCounts||{additions:0,deletions:0};const total=(c.created||0)+(c.modified||0)+(c.deleted||0);$('#tFiles').textContent=total;$('#tAdd').textContent='+'+(lc.additions||0);$('#tDel').textContent='−'+(lc.deletions||0);$('#cCreated').textContent=c.created||0;$('#cModified').textContent=c.modified||0;$('#cDeleted').textContent=c.deleted||0}
|
|
168
|
-
function resetTotals(){applyTotals({counts:{created:0,modified:0,deleted:0},lineCounts:{additions:0,deletions:0}})}
|
|
169
|
-
function setRounds(rounds,calls){$('#tRounds').textContent=`${rounds??0} · ${calls??0}`}
|
|
170
|
-
|
|
171
|
-
/* ---- Diff render ---- */
|
|
172
|
-
function diffPreviewMessage(status){return {redacted:'Hassas dosya içeriği güvenlik için gizlendi.',binary:'İkili dosya; satır önizlemesi gösterilemiyor.','too-large':'Dosya canlı satır önizleme sınırından büyük.','too-many-lines':'Dosya canlı önizleme için çok fazla satır içeriyor.','baseline-limit':'Görev başlangıcı içeriği önizleme bütçesine alınmadı.','event-limit':'Bu olayın satır önizleme sınırına ulaşıldı.',unavailable:'Dosya içeriği okunamadı.'}[status]||'Satır önizlemesi kullanılamıyor.'}
|
|
173
|
-
function renderDiffLine(line){const mark=line.type==='add'?'+':line.type==='delete'?'−':' ';return `<div class="diff-line ${esc(line.type)}"><span class="ln">${line.oldNumber??''}</span><span class="ln">${line.newNumber??''}</span><span class="mark">${mark}</span><span class="code">${esc(line.text)}</span></div>`}
|
|
174
|
-
function renderDiffFile(f,expanded){const labels={created:'eklendi',modified:'değişti',deleted:'silindi'},stats=f.additions==null?'<span class="muted">önizleme yok</span>':`<span class="diff-add">+${f.additions||0}</span><span class="diff-del">−${f.deletions||0}</span>`;let body='';if(f.previewStatus)body=`<div class="diff-note warn">${esc(diffPreviewMessage(f.previewStatus))}</div>`;else if(!(f.hunks||[]).length)body='<div class="diff-note">Satır farkı yok; yalnızca dosya metadatası değişmiş olabilir.</div>';else body=(f.hunks||[]).map(h=>`<div class="diff-hunk">@@ -${h.oldStart},${h.oldLines} +${h.newStart},${h.newLines} @@</div>${(h.lines||[]).map(renderDiffLine).join('')}`).join('')+(f.approximate?'<div class="diff-note warn">Büyük dosya: değişen bölüm yaklaşık olarak gösteriliyor.</div>':'')+(f.truncated?'<div class="diff-note warn">Diff önizlemesi kısaltıldı; dosyada daha fazla değişiklik var.</div>':'');return `<details class="diff-file"${expanded?' open':''}><summary><span class="diff-action ${esc(f.action)}">${esc(labels[f.action]||f.action)}</span><span class="diff-path">${esc(f.path)}</span><span class="diff-stats">${stats}</span></summary><div class="diff-code">${body}</div></details>`}
|
|
175
|
-
function renderFiles(e){const box=$('#files'),files=e.files||[];applyTotals(e);if(!files.length){box.innerHTML='<div class="empty">Henüz dosya değişikliği yok. Bir görev çalışırken oluşturulan/değişen dosyalar burada, satır satır farkıyla belirir.</div>';return}const expanded=files.length<=5;box.innerHTML=files.map(f=>renderDiffFile(f,expanded)).join('')}
|
|
176
|
-
function resetView(title){$('#missionTitle').textContent=title||'Görev bekleniyor';resetTotals();setRounds(0,0);$('#files').innerHTML='<div class="empty">Görev başladı; ilk dosya değişiklikleri birazdan görünecek…</div>'}
|
|
177
|
-
|
|
178
|
-
/* ---- Canlı olaylar ---- */
|
|
179
|
-
let liveCalls=0,bootstrapping=true,bufferedEvents=[];
|
|
180
|
-
function onLog(e){if(e.level==='task'){liveCalls=0;const title=String(e.msg||'').replace(/^GOREV\s+[^:]+:\s*/,'').slice(0,240)||'Yeni görev';resetView(title);setNow('live','<b>Operatör</b> hedefi analiz ediyor ve uygun uzmanları seçiyor…')}}
|
|
181
|
-
function onActivity(e){if(e.kind==='process.started'){if(String(e.stage||'').includes('operator')||e.stage==='delegate')liveCalls++;setRounds(currentRounds,liveCalls);setNow('live',`<b>${roleOf(e.stage,e.agent)}</b> ${phaseOf(e.stage)}…`)}else if(e.kind==='stdout'){setNow('live',`<b>${roleOf(e.stage,e.agent)}</b> ${phaseOf(e.stage)}…`)}else if(e.kind==='process.finished'){const sec=((e.durationMs||0)/1000).toFixed(1),failed=Number(e.exitCode)!==0;setNow(failed?'fail':'live',`<b>${roleOf(e.stage,e.agent)}</b> ${failed?'tamamlayamadı':'adımı tamamladı'} · ${sec}s`)}}
|
|
182
|
-
let currentRounds=0;
|
|
183
|
-
function onResult(r){if(r.kind==='operator-chat')return;currentRounds=r.rounds||currentRounds;setRounds(currentRounds,liveCalls);const files=(r.delivery?.files||[]).length;setNow('done',`✓ <b>Görev tamamlandı</b> · ${r.operator||'Operatör'} · ${r.rounds||0} tur · ${files} dosya${r.delivery?.verification?` · ✓ ${esc(r.delivery.verification)}`:''}`)}
|
|
184
|
-
|
|
185
|
-
/* ---- SSE ---- */
|
|
186
|
-
const es=new EventSource('/api/events');
|
|
187
|
-
function dispatchLiveEvent(type,data){if(data&&data.taskId)setCurrentTask(data.taskId);if(type==='status')applyStatus(data);else if(type==='filechange')renderFiles(data);else if(type==='activity')onActivity(data);else if(type==='log')onLog(data);else if(type==='result')onResult(data)}
|
|
188
|
-
function receiveLiveEvent(type,event){const data=JSON.parse(event.data);if(bootstrapping)bufferedEvents.push({type,data});else dispatchLiveEvent(type,data)}
|
|
189
|
-
for(const type of ['status','filechange','activity','log','result'])es.addEventListener(type,event=>receiveLiveEvent(type,event));
|
|
190
|
-
window.addEventListener('pagehide',()=>{try{es.close()}catch{}});
|
|
191
|
-
window.addEventListener('pageshow',x=>{if(x.persisted)location.reload()});
|
|
192
|
-
|
|
193
|
-
/* ---- Açılış: ?task önceliklidir; yoksa aktif görevin geçmişini otomatik yükle ---- */
|
|
194
|
-
function resolveBootTaskId(state,search=location.search){const q=state&&state.queue;const done=(q&&q.done)||[],failed=(q&&q.failed)||[];const recent=done.concat(failed).filter(t=>t&&t.kind!=='operator-chat').sort((a,b)=>a.id<b.id?1:-1)[0];return new URLSearchParams(search).get('task')||state?.status?.current?.id||(recent&&recent.id)||''}
|
|
195
|
-
async function boot(){
|
|
196
|
-
let replayed=[];
|
|
197
|
-
try{
|
|
198
|
-
const st=await fetch('/api/state').then(r=>r.ok?r.json():null);if(st&&st.status)applyStatus(st.status);
|
|
199
|
-
const id=resolveBootTaskId(st);if(!id)return;
|
|
200
|
-
setCurrentTask(id);
|
|
201
|
-
const found=await fetch(`/api/tasks/${id}`).then(r=>r.ok?r.json():null);
|
|
202
|
-
const task=found&&found.task;
|
|
203
|
-
if(task){$('#missionTitle').textContent=String(task.prompt||'').slice(0,240)||id;$('#filesSub').textContent=`${id} · toplam değişiklik`;const d=task.delivery||{};if(task.status==='done')setNow('done',`✓ <b>Tamamlandı</b> · ${task.operatorCli||'Operatör'} · ${d.rounds||0} tur${d.verification?` · ✓ ${esc(d.verification)}`:''}`);else if(task.status==='failed')setNow('fail',`✕ <b>Başarısız</b> · ${esc(String(task.error||'').slice(0,140))}`);setRounds(d.rounds||0,0)}
|
|
204
|
-
replayed=await fetch(`/api/tasks/${id}/events?limit=1500`).then(r=>r.ok?r.json():[]);
|
|
205
|
-
for(const event of replayed){if(event.type==='log'||event.type==='activity'||event.type==='result')dispatchLiveEvent(event.type,event)}
|
|
206
|
-
const last=[...replayed].reverse().find(event=>event.type==='filechange');
|
|
207
|
-
if(last)renderFiles(last);
|
|
208
|
-
}catch{}finally{bootstrapping=false;const seen=new Set(replayed.map(event=>JSON.stringify(event))),pending=bufferedEvents;bufferedEvents=[];for(const event of pending){if(seen.has(JSON.stringify(event.data)))continue;dispatchLiveEvent(event.type,event.data)}}
|
|
209
|
-
}
|
|
210
|
-
boot();
|
|
211
|
-
</script>
|
|
212
|
-
</body>
|
|
213
|
-
</html>
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="tr">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>Canlı Kodlama · CrewCtl</title>
|
|
7
|
+
<link rel="icon" href="/favicon.ico" sizes="any">
|
|
8
|
+
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
|
9
|
+
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
|
10
|
+
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
|
11
|
+
<link rel="manifest" href="/site.webmanifest">
|
|
12
|
+
<style>
|
|
13
|
+
*{box-sizing:border-box}
|
|
14
|
+
:root{--bg:#090c12;--panel:#111826;--panel2:#182234;--line:#243149;--tx:#e9eef7;--dim:#8494ab;--acc:#5e8bff;--ok:#2fd08a;--warn:#eab24d;--err:#f2565f;--purple:#a98bff;
|
|
15
|
+
--headerbg:#0a0f1acc;--input:#0b111c;--nodebg:#0b1220;--shadow:0 14px 40px #0008;
|
|
16
|
+
--appbg:radial-gradient(1200px 620px at 50% -14%,#5e8bff16,transparent),radial-gradient(1000px 520px at 90% -6%,#a98bff12,transparent),var(--bg)}
|
|
17
|
+
[data-theme=light]{--bg:#edf1f8;--panel:#ffffff;--panel2:#f3f6fc;--line:#dce4ef;--tx:#131a27;--dim:#5a6779;--acc:#3a63f0;--ok:#0ca678;--warn:#a76a10;--err:#dc434e;--purple:#7350f0;
|
|
18
|
+
--headerbg:#ffffffe6;--input:#f4f7fd;--nodebg:#f7f9fd;--shadow:0 14px 36px #7a90b820;
|
|
19
|
+
--appbg:radial-gradient(1200px 620px at 50% -14%,#3a63f012,transparent),radial-gradient(1000px 520px at 90% -6%,#7350f00e,transparent),var(--bg)}
|
|
20
|
+
body{margin:0;background:var(--appbg);background-attachment:fixed;color:var(--tx);font:14px/1.5 Inter,Segoe UI,system-ui,sans-serif;transition:background .3s,color .3s}
|
|
21
|
+
button{cursor:pointer;color:var(--tx);background:var(--panel2);border:1px solid var(--line);border-radius:8px;padding:7px 11px;font:inherit;transition:border-color .18s,transform .1s,box-shadow .18s}
|
|
22
|
+
button:hover{border-color:var(--acc);box-shadow:0 4px 14px color-mix(in srgb,var(--acc) 16%,transparent)}button:active{transform:translateY(1px)}
|
|
23
|
+
a.btn{text-decoration:none;display:inline-block}
|
|
24
|
+
code,pre{font-family:ui-monospace,SFMono-Regular,Consolas,monospace}
|
|
25
|
+
/* Header */
|
|
26
|
+
header{height:58px;display:flex;align-items:center;gap:12px;padding:0 16px;background:var(--headerbg);backdrop-filter:blur(10px);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:20}
|
|
27
|
+
h1{font-size:15px;margin:0;letter-spacing:.2px}.brand{color:var(--acc);font-weight:800}.sep{color:var(--dim)}.grow{flex:1}
|
|
28
|
+
.pill{border:1px solid var(--line);background:var(--panel2);border-radius:999px;padding:5px 11px;color:var(--dim);font-size:12px;display:inline-flex;align-items:center;gap:6px}
|
|
29
|
+
.dot{width:8px;height:8px;border-radius:50%;background:var(--dim)}
|
|
30
|
+
.dot.on{background:var(--ok);box-shadow:0 0 10px var(--ok);animation:beat 1.6s ease-in-out infinite}
|
|
31
|
+
@keyframes beat{50%{box-shadow:0 0 3px var(--ok)}}
|
|
32
|
+
/* Banner + tiles */
|
|
33
|
+
.topbar{padding:14px 14px 0}
|
|
34
|
+
.banner{background:linear-gradient(120deg,var(--panel),var(--panel2));border:1px solid var(--line);border-radius:16px;padding:15px 18px;box-shadow:var(--shadow);display:flex;gap:16px;align-items:center;flex-wrap:wrap}
|
|
35
|
+
.banner .kick{font-size:10px;letter-spacing:1.4px;text-transform:uppercase;color:var(--acc);font-weight:800}
|
|
36
|
+
.banner .t{font-weight:800;font-size:17px;line-height:1.28;margin-top:2px;max-width:70ch}
|
|
37
|
+
.now{display:flex;align-items:center;gap:8px;color:var(--dim);font-size:12.5px;margin-top:6px}
|
|
38
|
+
.now .ndot{width:9px;height:9px;border-radius:50%;background:var(--dim);flex:none}
|
|
39
|
+
.now.live .ndot{background:var(--acc);box-shadow:0 0 10px var(--acc);animation:beat 1.3s ease-in-out infinite}
|
|
40
|
+
.now.done .ndot{background:var(--ok);box-shadow:0 0 8px var(--ok)}
|
|
41
|
+
.now.fail .ndot{background:var(--err);box-shadow:0 0 8px var(--err)}
|
|
42
|
+
.now b{color:var(--tx);font-weight:700}
|
|
43
|
+
.tiles{display:grid;grid-template-columns:repeat(4,minmax(96px,1fr));gap:10px;margin-left:auto}
|
|
44
|
+
.tile{background:color-mix(in srgb,var(--panel2) 60%,transparent);border:1px solid var(--line);border-radius:12px;padding:9px 12px;min-width:96px}
|
|
45
|
+
.tile .k{font-size:9.5px;text-transform:uppercase;letter-spacing:.6px;color:var(--dim)}
|
|
46
|
+
.tile .v{font-size:22px;font-weight:800;margin-top:1px;font-variant-numeric:tabular-nums;line-height:1.1}
|
|
47
|
+
.tile.add .v{color:var(--ok)}.tile.del .v{color:var(--err)}
|
|
48
|
+
.chips{display:flex;gap:8px;flex-wrap:wrap;margin:12px 14px 0}
|
|
49
|
+
.chip{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--line);background:var(--panel);border-radius:999px;padding:5px 11px;font-size:11.5px;color:var(--dim)}
|
|
50
|
+
.chip b{font-variant-numeric:tabular-nums}
|
|
51
|
+
.chip .c-created{color:var(--ok)}.chip .c-modified{color:var(--warn)}.chip .c-deleted{color:var(--err)}
|
|
52
|
+
/* Diff list */
|
|
53
|
+
main{padding:14px}
|
|
54
|
+
.panel{background:var(--panel);border:1px solid var(--line);border-radius:16px;box-shadow:var(--shadow);padding:14px}
|
|
55
|
+
.panel-head{display:flex;align-items:center;gap:10px;margin-bottom:10px}.panel-head h2{margin:0;font-size:13px;letter-spacing:.2px}
|
|
56
|
+
.muted{color:var(--dim);font-size:12px}
|
|
57
|
+
.legend{display:flex;gap:12px;flex-wrap:wrap;color:var(--dim);font-size:11px;margin-left:auto}
|
|
58
|
+
.legend i{font-style:normal;font-weight:700}.legend .lg-add{color:var(--ok)}.legend .lg-mod{color:var(--warn)}.legend .lg-del{color:var(--err)}
|
|
59
|
+
.empty{padding:34px 14px;text-align:center;color:var(--dim);border:1px dashed var(--line);border-radius:12px;background:color-mix(in srgb,var(--panel2) 40%,transparent)}
|
|
60
|
+
.files{display:flex;flex-direction:column;gap:8px}
|
|
61
|
+
.diff-file{border:1px solid var(--line);border-radius:10px;overflow:hidden;background:var(--panel);animation:pop .3s cubic-bezier(.2,.8,.2,1)}
|
|
62
|
+
@keyframes pop{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
|
|
63
|
+
.diff-file>summary{display:flex;align-items:center;gap:9px;padding:9px 11px;background:var(--panel2);list-style:none;font:11.5px ui-monospace;cursor:pointer}
|
|
64
|
+
.diff-file>summary::-webkit-details-marker{display:none}
|
|
65
|
+
.diff-file>summary:before{content:'›';font-size:17px;color:var(--dim);transition:.16s}.diff-file[open]>summary:before{transform:rotate(90deg)}
|
|
66
|
+
.diff-action{font-weight:800;text-transform:uppercase;font-size:9px;letter-spacing:.04em;padding:2px 6px;border-radius:5px;border:1px solid transparent}
|
|
67
|
+
.diff-action.created{color:var(--ok);background:color-mix(in srgb,var(--ok) 14%,transparent);border-color:color-mix(in srgb,var(--ok) 35%,transparent)}
|
|
68
|
+
.diff-action.modified{color:var(--warn);background:color-mix(in srgb,var(--warn) 14%,transparent);border-color:color-mix(in srgb,var(--warn) 35%,transparent)}
|
|
69
|
+
.diff-action.deleted{color:var(--err);background:color-mix(in srgb,var(--err) 14%,transparent);border-color:color-mix(in srgb,var(--err) 35%,transparent)}
|
|
70
|
+
.diff-path{color:var(--tx);font-weight:700;word-break:break-all}
|
|
71
|
+
.diff-stats{margin-left:auto;display:flex;gap:8px;white-space:nowrap;font-variant-numeric:tabular-nums}
|
|
72
|
+
.diff-add{color:var(--ok)}.diff-del{color:var(--err)}
|
|
73
|
+
.diff-code{overflow:auto;font:11px/1.5 ui-monospace,SFMono-Regular,Consolas,monospace;background:var(--input)}
|
|
74
|
+
.diff-hunk{padding:4px 10px;color:var(--acc);background:color-mix(in srgb,var(--acc) 12%,transparent);border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
|
|
75
|
+
.diff-line{display:grid;grid-template-columns:44px 44px 18px minmax(max-content,1fr);min-width:max-content;white-space:pre}
|
|
76
|
+
.diff-line .ln{padding:0 7px;text-align:right;color:var(--dim);user-select:none;border-right:1px solid var(--line);opacity:.75}
|
|
77
|
+
.diff-line .mark{padding-left:6px;user-select:none}.diff-line .code{padding:0 12px 0 5px;color:var(--tx)}
|
|
78
|
+
.diff-line.add{background:color-mix(in srgb,var(--ok) 13%,transparent)}.diff-line.add .mark{color:var(--ok)}
|
|
79
|
+
.diff-line.delete{background:color-mix(in srgb,var(--err) 13%,transparent)}.diff-line.delete .mark{color:var(--err)}
|
|
80
|
+
.diff-note{padding:9px 11px;color:var(--dim);font-size:11px}.diff-note.warn{color:var(--warn)}
|
|
81
|
+
.revert{border-color:color-mix(in srgb,var(--err) 45%,var(--line));color:var(--err);font-weight:700}
|
|
82
|
+
.revert:hover{border-color:var(--err);box-shadow:0 4px 14px color-mix(in srgb,var(--err) 20%,transparent)}
|
|
83
|
+
.revert:disabled{opacity:.45;cursor:default;box-shadow:none}
|
|
84
|
+
#toasts{position:fixed;right:16px;bottom:16px;display:flex;flex-direction:column;gap:8px;z-index:50}
|
|
85
|
+
.toast{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:10px 14px;box-shadow:var(--shadow);font-size:13px;max-width:360px;transition:opacity .3s}
|
|
86
|
+
.toast.ok{border-color:color-mix(in srgb,var(--ok) 45%,var(--line))}.toast.err{border-color:color-mix(in srgb,var(--err) 45%,var(--line))}
|
|
87
|
+
</style>
|
|
88
|
+
<link rel="stylesheet" href="app.css">
|
|
89
|
+
<script src="app.js" defer></script>
|
|
90
|
+
</head>
|
|
91
|
+
<body>
|
|
92
|
+
<header>
|
|
93
|
+
<h1><span class="brand">CrewCtl</span> <span class="sep">·</span> Canlı Kodlama</h1>
|
|
94
|
+
<span class="pill"><span id="dot" class="dot"></span><span id="engineState">durdu</span></span>
|
|
95
|
+
<span id="modePill" class="pill">boşta</span>
|
|
96
|
+
<div class="grow"></div>
|
|
97
|
+
<button id="revertBtn" class="revert" hidden onclick="revertVersion()" title="Bu görev öncesindeki koda dön"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg>Önceki sürüme dön</button>
|
|
98
|
+
<a class="btn" href="flow.html"><button>Ekip Akışı</button></a>
|
|
99
|
+
<button id="themeBtn" onclick="toggleTheme()" title="Açık / koyu tema"><svg class="i i-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg><svg class="i i-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg></button>
|
|
100
|
+
<a class="btn" href="index.html"><button>← Komuta Merkezi</button></a>
|
|
101
|
+
</header>
|
|
102
|
+
|
|
103
|
+
<div class="topbar">
|
|
104
|
+
<div class="banner">
|
|
105
|
+
<div style="min-width:0">
|
|
106
|
+
<div class="kick">Canlı Kodlama</div>
|
|
107
|
+
<div class="t" id="missionTitle">Görev bekleniyor</div>
|
|
108
|
+
<div class="now" id="nowLine"><span class="ndot"></span><span id="nowText">Bir görev başladığında burada ne yapıldığı canlı görünür.</span></div>
|
|
109
|
+
</div>
|
|
110
|
+
<div class="tiles">
|
|
111
|
+
<div class="tile"><div class="k">Değişen dosya</div><div class="v" id="tFiles">0</div></div>
|
|
112
|
+
<div class="tile add"><div class="k">Eklenen satır</div><div class="v" id="tAdd">+0</div></div>
|
|
113
|
+
<div class="tile del"><div class="k">Silinen satır</div><div class="v" id="tDel">−0</div></div>
|
|
114
|
+
<div class="tile"><div class="k">Tur · Çağrı</div><div class="v" id="tRounds" style="font-size:15px">0 · 0</div></div>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
<div class="chips">
|
|
118
|
+
<span class="chip"><span class="c-created">●</span> <b id="cCreated">0</b> oluşturuldu</span>
|
|
119
|
+
<span class="chip"><span class="c-modified">●</span> <b id="cModified">0</b> değişti</span>
|
|
120
|
+
<span class="chip"><span class="c-deleted">●</span> <b id="cDeleted">0</b> silindi</span>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<main>
|
|
125
|
+
<div class="panel">
|
|
126
|
+
<div class="panel-head">
|
|
127
|
+
<h2>Değişen dosyalar</h2>
|
|
128
|
+
<span class="muted" id="filesSub">görev başından bu yana toplam</span>
|
|
129
|
+
<span class="legend"><span class="lg-add"><i>+</i> eklenen</span><span class="lg-del"><i>−</i> silinen</span> <span><i class="lg-add">eklendi</i> · <i class="lg-mod">değişti</i> · <i class="lg-del">silindi</i></span></span>
|
|
130
|
+
</div>
|
|
131
|
+
<div id="files" class="files"><div class="empty">Henüz dosya değişikliği yok. Bir görev çalışırken oluşturulan/değişen dosyalar burada, satır satır farkıyla belirir.</div></div>
|
|
132
|
+
</div>
|
|
133
|
+
</main>
|
|
134
|
+
|
|
135
|
+
<script>
|
|
136
|
+
const $=s=>document.querySelector(s);
|
|
137
|
+
function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]))}
|
|
138
|
+
/* ---- Tema ---- */
|
|
139
|
+
function applyTheme(t){document.documentElement.setAttribute('data-theme',t);try{localStorage.setItem('cliTheme',t)}catch{}}
|
|
140
|
+
function toggleTheme(){applyTheme(document.documentElement.getAttribute('data-theme')==='light'?'dark':'light')}
|
|
141
|
+
(function(){let t='dark';try{t=localStorage.getItem('cliTheme')||'dark'}catch{}applyTheme(t)})();
|
|
142
|
+
|
|
143
|
+
/* ---- Durum: "şu an ne oluyor" ---- */
|
|
144
|
+
function setNow(kind,text){const line=$('#nowLine');line.className='now'+(kind?(' '+kind):'');$('#nowText').innerHTML=text}
|
|
145
|
+
function roleOf(stage,agent){const s=String(stage||'');if(s.startsWith('operator'))return 'Operatör';return esc(agent||'Uzman')}
|
|
146
|
+
function phaseOf(stage){const s=String(stage||'');if(s==='operator-plan')return 'planı hazırlıyor';if(s==='operator-review')return 'sonucu değerlendiriyor';if(s==='operator-chat')return 'soruyu yanıtlıyor';if(s.startsWith('operator'))return 'ekibi yönetiyor';if(s==='delegate')return 'delege edilen işi uyguluyor';return 'çalışıyor'}
|
|
147
|
+
function applyStatus(s){$('#dot').className='dot'+(s.running?' on':'');$('#engineState').textContent=s.running?'çalışıyor':'durdu';$('#modePill').textContent=s.current?`${roleOf(s.current.stage,s.current.agent)} · ${s.current.stage||''}`:(s.busy?'meşgul':'boşta');engineActive=!!s.current;if(s.current&&s.current.id)currentTaskId=s.current.id;updateRevert()}
|
|
148
|
+
/* ---- Geri dönüş: bu görev öncesindeki sürüme dön ---- */
|
|
149
|
+
let currentTaskId='',engineActive=false;
|
|
150
|
+
function setCurrentTask(id){if(id&&id!==currentTaskId)currentTaskId=id;updateRevert()}
|
|
151
|
+
function updateRevert(){const b=$('#revertBtn');if(!b)return;if(!currentTaskId){b.hidden=true;return}b.hidden=false;b.disabled=!!engineActive;b.title=engineActive?'Görev çalışırken geri alınamaz; bitmesini bekleyin':'Bu görev öncesindeki koda dön'}
|
|
152
|
+
function toast(kind,msg){let box=document.getElementById('toasts');if(!box){box=document.createElement('div');box.id='toasts';document.body.appendChild(box)}const t=document.createElement('div');t.className='toast '+(kind||'');t.textContent=msg;box.appendChild(t);setTimeout(()=>{t.style.opacity='0';setTimeout(()=>t.remove(),300)},4600)}
|
|
153
|
+
async function revertVersion(){
|
|
154
|
+
if(!currentTaskId){toast('err','Geri dönülecek görev yok.');return}
|
|
155
|
+
if(engineActive){toast('err','Görev çalışırken geri alınamaz; bitmesini bekleyin.');return}
|
|
156
|
+
if(!await appConfirm({title:'Önceki sürüme dönülsün mü?',body:'Bu görev çalışmadan önceki koda dönülür:\n• Görev sonrası oluşan dosyalar silinir\n• Değişen/silinen dosyalar eski haline döner\n\nGeri alma işleminin kendisi de bir sürüm oluşturur, yani geri alınabilir.',confirmLabel:'Önceki sürüme dön',cancelLabel:'Vazgeç',danger:true}))return;
|
|
157
|
+
const b=$('#revertBtn');if(b)b.disabled=true;
|
|
158
|
+
try{
|
|
159
|
+
const r=await fetch(`/api/tasks/${currentTaskId}/restore`,{method:'POST'}).then(async res=>{const j=await res.json().catch(()=>({}));if(!res.ok)throw new Error(j.error||('HTTP '+res.status));return j});
|
|
160
|
+
toast('ok',`↩ Önceki sürüme dönüldü · ${r.restored} dosya geri yazıldı · ${r.deleted} silindi`);
|
|
161
|
+
setNow('done',`↩ <b>Önceki sürüme dönüldü</b> · ${r.restored} dosya geri yazıldı · ${r.deleted} sonradan oluşan dosya silindi`);
|
|
162
|
+
}catch(e){toast('err','Geri alınamadı: '+e.message)}
|
|
163
|
+
finally{if(b)b.disabled=false;updateRevert()}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* ---- Toplamlar (tiles + chips) ---- */
|
|
167
|
+
function applyTotals(e){const c=e.counts||{created:0,modified:0,deleted:0},lc=e.lineCounts||{additions:0,deletions:0};const total=(c.created||0)+(c.modified||0)+(c.deleted||0);$('#tFiles').textContent=total;$('#tAdd').textContent='+'+(lc.additions||0);$('#tDel').textContent='−'+(lc.deletions||0);$('#cCreated').textContent=c.created||0;$('#cModified').textContent=c.modified||0;$('#cDeleted').textContent=c.deleted||0}
|
|
168
|
+
function resetTotals(){applyTotals({counts:{created:0,modified:0,deleted:0},lineCounts:{additions:0,deletions:0}})}
|
|
169
|
+
function setRounds(rounds,calls){$('#tRounds').textContent=`${rounds??0} · ${calls??0}`}
|
|
170
|
+
|
|
171
|
+
/* ---- Diff render ---- */
|
|
172
|
+
function diffPreviewMessage(status){return {redacted:'Hassas dosya içeriği güvenlik için gizlendi.',binary:'İkili dosya; satır önizlemesi gösterilemiyor.','too-large':'Dosya canlı satır önizleme sınırından büyük.','too-many-lines':'Dosya canlı önizleme için çok fazla satır içeriyor.','baseline-limit':'Görev başlangıcı içeriği önizleme bütçesine alınmadı.','event-limit':'Bu olayın satır önizleme sınırına ulaşıldı.',unavailable:'Dosya içeriği okunamadı.'}[status]||'Satır önizlemesi kullanılamıyor.'}
|
|
173
|
+
function renderDiffLine(line){const mark=line.type==='add'?'+':line.type==='delete'?'−':' ';return `<div class="diff-line ${esc(line.type)}"><span class="ln">${line.oldNumber??''}</span><span class="ln">${line.newNumber??''}</span><span class="mark">${mark}</span><span class="code">${esc(line.text)}</span></div>`}
|
|
174
|
+
function renderDiffFile(f,expanded){const labels={created:'eklendi',modified:'değişti',deleted:'silindi'},stats=f.additions==null?'<span class="muted">önizleme yok</span>':`<span class="diff-add">+${f.additions||0}</span><span class="diff-del">−${f.deletions||0}</span>`;let body='';if(f.previewStatus)body=`<div class="diff-note warn">${esc(diffPreviewMessage(f.previewStatus))}</div>`;else if(!(f.hunks||[]).length)body='<div class="diff-note">Satır farkı yok; yalnızca dosya metadatası değişmiş olabilir.</div>';else body=(f.hunks||[]).map(h=>`<div class="diff-hunk">@@ -${h.oldStart},${h.oldLines} +${h.newStart},${h.newLines} @@</div>${(h.lines||[]).map(renderDiffLine).join('')}`).join('')+(f.approximate?'<div class="diff-note warn">Büyük dosya: değişen bölüm yaklaşık olarak gösteriliyor.</div>':'')+(f.truncated?'<div class="diff-note warn">Diff önizlemesi kısaltıldı; dosyada daha fazla değişiklik var.</div>':'');return `<details class="diff-file"${expanded?' open':''}><summary><span class="diff-action ${esc(f.action)}">${esc(labels[f.action]||f.action)}</span><span class="diff-path">${esc(f.path)}</span><span class="diff-stats">${stats}</span></summary><div class="diff-code">${body}</div></details>`}
|
|
175
|
+
function renderFiles(e){const box=$('#files'),files=e.files||[];applyTotals(e);if(!files.length){box.innerHTML='<div class="empty">Henüz dosya değişikliği yok. Bir görev çalışırken oluşturulan/değişen dosyalar burada, satır satır farkıyla belirir.</div>';return}const expanded=files.length<=5;box.innerHTML=files.map(f=>renderDiffFile(f,expanded)).join('')}
|
|
176
|
+
function resetView(title){$('#missionTitle').textContent=title||'Görev bekleniyor';resetTotals();setRounds(0,0);$('#files').innerHTML='<div class="empty">Görev başladı; ilk dosya değişiklikleri birazdan görünecek…</div>'}
|
|
177
|
+
|
|
178
|
+
/* ---- Canlı olaylar ---- */
|
|
179
|
+
let liveCalls=0,bootstrapping=true,bufferedEvents=[];
|
|
180
|
+
function onLog(e){if(e.level==='task'){liveCalls=0;const title=String(e.msg||'').replace(/^GOREV\s+[^:]+:\s*/,'').slice(0,240)||'Yeni görev';resetView(title);setNow('live','<b>Operatör</b> hedefi analiz ediyor ve uygun uzmanları seçiyor…')}}
|
|
181
|
+
function onActivity(e){if(e.kind==='process.started'){if(String(e.stage||'').includes('operator')||e.stage==='delegate')liveCalls++;setRounds(currentRounds,liveCalls);setNow('live',`<b>${roleOf(e.stage,e.agent)}</b> ${phaseOf(e.stage)}…`)}else if(e.kind==='stdout'){setNow('live',`<b>${roleOf(e.stage,e.agent)}</b> ${phaseOf(e.stage)}…`)}else if(e.kind==='process.finished'){const sec=((e.durationMs||0)/1000).toFixed(1),failed=Number(e.exitCode)!==0;setNow(failed?'fail':'live',`<b>${roleOf(e.stage,e.agent)}</b> ${failed?'tamamlayamadı':'adımı tamamladı'} · ${sec}s`)}}
|
|
182
|
+
let currentRounds=0;
|
|
183
|
+
function onResult(r){if(r.kind==='operator-chat')return;currentRounds=r.rounds||currentRounds;setRounds(currentRounds,liveCalls);const files=(r.delivery?.files||[]).length;setNow('done',`✓ <b>Görev tamamlandı</b> · ${r.operator||'Operatör'} · ${r.rounds||0} tur · ${files} dosya${r.delivery?.verification?` · ✓ ${esc(r.delivery.verification)}`:''}`)}
|
|
184
|
+
|
|
185
|
+
/* ---- SSE ---- */
|
|
186
|
+
const es=new EventSource('/api/events');
|
|
187
|
+
function dispatchLiveEvent(type,data){if(data&&data.taskId)setCurrentTask(data.taskId);if(type==='status')applyStatus(data);else if(type==='filechange')renderFiles(data);else if(type==='activity')onActivity(data);else if(type==='log')onLog(data);else if(type==='result')onResult(data)}
|
|
188
|
+
function receiveLiveEvent(type,event){const data=JSON.parse(event.data);if(bootstrapping)bufferedEvents.push({type,data});else dispatchLiveEvent(type,data)}
|
|
189
|
+
for(const type of ['status','filechange','activity','log','result'])es.addEventListener(type,event=>receiveLiveEvent(type,event));
|
|
190
|
+
window.addEventListener('pagehide',()=>{try{es.close()}catch{}});
|
|
191
|
+
window.addEventListener('pageshow',x=>{if(x.persisted)location.reload()});
|
|
192
|
+
|
|
193
|
+
/* ---- Açılış: ?task önceliklidir; yoksa aktif görevin geçmişini otomatik yükle ---- */
|
|
194
|
+
function resolveBootTaskId(state,search=location.search){const q=state&&state.queue;const done=(q&&q.done)||[],failed=(q&&q.failed)||[];const recent=done.concat(failed).filter(t=>t&&t.kind!=='operator-chat').sort((a,b)=>a.id<b.id?1:-1)[0];return new URLSearchParams(search).get('task')||state?.status?.current?.id||(recent&&recent.id)||''}
|
|
195
|
+
async function boot(){
|
|
196
|
+
let replayed=[];
|
|
197
|
+
try{
|
|
198
|
+
const st=await fetch('/api/state').then(r=>r.ok?r.json():null);if(st&&st.status)applyStatus(st.status);
|
|
199
|
+
const id=resolveBootTaskId(st);if(!id)return;
|
|
200
|
+
setCurrentTask(id);
|
|
201
|
+
const found=await fetch(`/api/tasks/${id}`).then(r=>r.ok?r.json():null);
|
|
202
|
+
const task=found&&found.task;
|
|
203
|
+
if(task){$('#missionTitle').textContent=String(task.prompt||'').slice(0,240)||id;$('#filesSub').textContent=`${id} · toplam değişiklik`;const d=task.delivery||{};if(task.status==='done')setNow('done',`✓ <b>Tamamlandı</b> · ${task.operatorCli||'Operatör'} · ${d.rounds||0} tur${d.verification?` · ✓ ${esc(d.verification)}`:''}`);else if(task.status==='failed')setNow('fail',`✕ <b>Başarısız</b> · ${esc(String(task.error||'').slice(0,140))}`);setRounds(d.rounds||0,0)}
|
|
204
|
+
replayed=await fetch(`/api/tasks/${id}/events?limit=1500`).then(r=>r.ok?r.json():[]);
|
|
205
|
+
for(const event of replayed){if(event.type==='log'||event.type==='activity'||event.type==='result')dispatchLiveEvent(event.type,event)}
|
|
206
|
+
const last=[...replayed].reverse().find(event=>event.type==='filechange');
|
|
207
|
+
if(last)renderFiles(last);
|
|
208
|
+
}catch{}finally{bootstrapping=false;const seen=new Set(replayed.map(event=>JSON.stringify(event))),pending=bufferedEvents;bufferedEvents=[];for(const event of pending){if(seen.has(JSON.stringify(event.data)))continue;dispatchLiveEvent(event.type,event.data)}}
|
|
209
|
+
}
|
|
210
|
+
boot();
|
|
211
|
+
</script>
|
|
212
|
+
</body>
|
|
213
|
+
</html>
|