ownapi 1.0.0__py3-none-any.whl

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.
Files changed (62) hide show
  1. cli.py +1017 -0
  2. compiler.py +948 -0
  3. core/__init__.py +19 -0
  4. core/api_keys.py +523 -0
  5. core/auto_repair.py +682 -0
  6. core/base_client.py +165 -0
  7. core/consent.py +169 -0
  8. core/form_automation.py +1044 -0
  9. core/hitl_queue.py +886 -0
  10. core/onboarding.py +1457 -0
  11. core/token_store.py +223 -0
  12. healer.py +2074 -0
  13. master_mcp.py +193 -0
  14. ownapi/__init__.py +94 -0
  15. ownapi/cli.py +309 -0
  16. ownapi/sdk.py +436 -0
  17. ownapi-1.0.0.dist-info/METADATA +287 -0
  18. ownapi-1.0.0.dist-info/RECORD +62 -0
  19. ownapi-1.0.0.dist-info/WHEEL +5 -0
  20. ownapi-1.0.0.dist-info/entry_points.txt +4 -0
  21. ownapi-1.0.0.dist-info/top_level.txt +10 -0
  22. recorder.py +486 -0
  23. registry/coingecko/__init__.py +5 -0
  24. registry/coingecko/client.py +84 -0
  25. registry/coingecko/schema.json +23 -0
  26. registry/edevlet/__init__.py +5 -0
  27. registry/edevlet/client.py +54 -0
  28. registry/edevlet/schema.json +14 -0
  29. registry/enuygun/__init__.py +5 -0
  30. registry/enuygun/client.py +78 -0
  31. registry/enuygun/schema.json +13 -0
  32. registry/github_trending/__init__.py +5 -0
  33. registry/github_trending/client.py +90 -0
  34. registry/github_trending/schema.json +31 -0
  35. registry/hackernews/__init__.py +5 -0
  36. registry/hackernews/client.py +90 -0
  37. registry/hackernews/schema.json +38 -0
  38. registry/hepsiburada/__init__.py +5 -0
  39. registry/hepsiburada/client.py +32 -0
  40. registry/hepsiburada/schema.json +23 -0
  41. registry/linkedin_jobs/__init__.py +5 -0
  42. registry/linkedin_jobs/client.py +241 -0
  43. registry/linkedin_jobs/schema.json +46 -0
  44. registry/pegasus/__init__.py +5 -0
  45. registry/pegasus/client.py +141 -0
  46. registry/pegasus/schema.json +76 -0
  47. registry/thy/__init__.py +5 -0
  48. registry/thy/client.py +1796 -0
  49. registry/thy/schema.json +46 -0
  50. registry/trendyol/__init__.py +5 -0
  51. registry/trendyol/client.py +228 -0
  52. registry/trendyol/schema.json +30 -0
  53. registry/ucak_bileti/client.py +94 -0
  54. registry/ucak_bileti/schema.json +47 -0
  55. registry/wikipedia/__init__.py +5 -0
  56. registry/wikipedia/client.py +64 -0
  57. registry/wikipedia/schema.json +31 -0
  58. registry/yahoo_finance/__init__.py +5 -0
  59. registry/yahoo_finance/client.py +98 -0
  60. registry/yahoo_finance/schema.json +22 -0
  61. server.py +891 -0
  62. static/index.html +2401 -0
static/index.html ADDED
@@ -0,0 +1,2401 @@
1
+ <!DOCTYPE html>
2
+ <html lang="tr" class="dark">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>OwnAPI Enterprise — User-Delegated Browser Middleware & Live Playground</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script>
9
+ tailwind.config = {
10
+ darkMode: 'class',
11
+ theme: {
12
+ extend: {
13
+ colors: {
14
+ bgBase: '#090A0F',
15
+ bgCard: '#11131C',
16
+ bgElevated: '#171A26',
17
+ borderSubtle: '#232738',
18
+ accentIndigo: '#6366F1',
19
+ accentCyan: '#06B6D4',
20
+ accentEmerald: '#10B981',
21
+ accentAmber: '#F59E0B',
22
+ accentRose: '#F43F5E',
23
+ },
24
+ fontFamily: {
25
+ sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
26
+ mono: ['JetBrains Mono', 'Fira Code', 'Consolas', 'monospace'],
27
+ }
28
+ }
29
+ }
30
+ }
31
+ </script>
32
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
33
+ <style>
34
+ body {
35
+ background-color: #090A0F;
36
+ color: #F3F4F6;
37
+ font-family: 'Inter', sans-serif;
38
+ background-image:
39
+ radial-gradient(circle at 15% 0%, rgba(99, 102, 241, 0.12), transparent 35%),
40
+ radial-gradient(circle at 85% 0%, rgba(6, 182, 212, 0.10), transparent 35%);
41
+ }
42
+ .glass-card {
43
+ background: rgba(17, 19, 28, 0.88);
44
+ backdrop-filter: blur(12px);
45
+ border: 1px solid #232738;
46
+ }
47
+ .glass-card:hover {
48
+ border-color: #343A52;
49
+ }
50
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
51
+ ::-webkit-scrollbar-track { background: #090A0F; }
52
+ ::-webkit-scrollbar-thumb { background: #262B3D; border-radius: 3px; }
53
+ ::-webkit-scrollbar-thumb:hover { background: #3B425E; }
54
+ </style>
55
+ </head>
56
+ <body class="min-h-screen flex flex-col selection:bg-indigo-500 selection:text-white">
57
+
58
+ <!-- ÜST DURUM ÇUBUĞU (TOP NAVBAR) -->
59
+ <header class="sticky top-0 z-40 border-b border-borderSubtle bg-bgBase/90 backdrop-blur-md">
60
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between gap-4">
61
+ <div class="flex items-center gap-3">
62
+ <div class="w-9 h-9 rounded-lg bg-gradient-to-br from-indigo-500 via-indigo-600 to-cyan-500 flex items-center justify-center shadow-lg shadow-indigo-500/20 font-bold text-white text-lg">
63
+ O
64
+ </div>
65
+ <div>
66
+ <div class="flex items-center gap-2">
67
+ <span class="font-bold text-lg tracking-tight text-white">OwnAPI</span>
68
+ <span class="text-[11px] font-semibold uppercase tracking-wider px-2 py-0.5 rounded-full bg-indigo-500/15 text-indigo-400 border border-indigo-500/30">
69
+ Enterprise v1.0
70
+ </span>
71
+ </div>
72
+ <p class="text-xs text-gray-400 hidden sm:block">
73
+ Plaid-Style User-Delegated Browser Middleware & Auto-Validated API Engine
74
+ </p>
75
+ </div>
76
+ </div>
77
+
78
+ <!-- Sağ Kontroller: Canlı Kredi & Mod Göstergesi, Tenant Seçici, Mode Switcher, Yenile -->
79
+ <div class="flex items-center gap-2.5 flex-wrap justify-end">
80
+ <!-- Üst Bar Canlı Kredi & Mod Göstergesi -->
81
+ <div id="topCreditStatusBadge" onclick="switchTab('billing')"
82
+ title="Paket ve Kredi Detayları için Tıklayın"
83
+ class="cursor-pointer flex items-center gap-2 bg-bgCard hover:bg-bgElevated border border-emerald-500/40 rounded-lg px-3 py-1.5 text-xs shadow-sm transition">
84
+ <span id="topStatusDot" class="inline-block w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></span>
85
+ <span id="topStatusConnectionText" class="font-medium text-emerald-300">Bağlı - Bilgisayarın Hazır</span>
86
+ <span class="text-gray-600">|</span>
87
+ <span id="topStatusCreditsText" class="font-mono text-gray-200">Kullanılan: 0 / 1.000 Kredi</span>
88
+ <span class="text-gray-600">|</span>
89
+ <span id="topStatusSitesText" class="font-mono font-semibold text-cyan-300">Siteler: 0 / 2</span>
90
+ <span class="text-gray-600 hidden sm:inline">|</span>
91
+ <span id="topStatusPlanText" class="hidden sm:inline font-semibold text-indigo-300">Paket: Free (Local)</span>
92
+ </div>
93
+
94
+ <!-- Tenant ID -->
95
+ <div class="hidden xl:flex items-center gap-1.5 bg-bgCard border border-borderSubtle rounded-lg px-2.5 py-1.5 text-xs">
96
+ <span class="text-gray-400">Tenant:</span>
97
+ <input id="globalTenantInput" type="text" value="default"
98
+ class="bg-transparent text-cyan-400 font-mono font-medium w-20 focus:outline-none"
99
+ onchange="refreshAllData()" />
100
+ </div>
101
+
102
+ <!-- 2 Modlu Çalışma Seçici: Local Chrome vs Cloud CDP -->
103
+ <div class="flex items-center bg-bgCard border border-borderSubtle rounded-lg p-1 text-xs">
104
+ <button id="modeBtnLocal" onclick="setExecutionMode('local')"
105
+ class="px-2.5 py-1 rounded-md font-medium transition flex items-center gap-1.5 bg-indigo-600 text-white shadow">
106
+ <span>💻</span> Local Chrome
107
+ </button>
108
+ <button id="modeBtnCloud" onclick="setExecutionMode('cloud')"
109
+ class="px-2.5 py-1 rounded-md font-medium transition flex items-center gap-1.5 text-gray-400 hover:text-white">
110
+ <span>☁️</span> Cloud CDP
111
+ </button>
112
+ </div>
113
+
114
+ <button onclick="openAddSiteModal()"
115
+ class="px-3.5 py-1.5 rounded-lg bg-gradient-to-r from-indigo-600 to-cyan-600 hover:from-indigo-500 hover:to-cyan-500 text-white font-semibold text-xs shadow-lg shadow-indigo-600/25 transition flex items-center gap-1.5">
116
+ <span>➕</span> Add New Site
117
+ </button>
118
+
119
+ <button onclick="refreshAllData()" title="Verileri Yenile"
120
+ class="p-2 rounded-lg bg-bgCard border border-borderSubtle text-gray-300 hover:text-white hover:border-gray-600 transition text-xs font-medium flex items-center gap-1.5">
121
+ <span id="refreshSpinner">🔄</span>
122
+ <span class="hidden sm:inline">Yenile</span>
123
+ </button>
124
+
125
+ <a href="/docs" target="_blank"
126
+ class="px-3 py-1.5 rounded-lg bg-bgElevated border border-borderSubtle text-xs font-medium text-gray-300 hover:text-white hover:border-indigo-500/50 transition">
127
+ OpenAPI / Swagger ↗
128
+ </a>
129
+ </div>
130
+ </div>
131
+ </header>
132
+
133
+ <!-- ANA İÇERİK -->
134
+ <main class="flex-1 max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8 py-6 space-y-6">
135
+
136
+ <!-- KPI KARTLARI (5 ADET) -->
137
+ <section class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-4">
138
+ <!-- KPI 1: Aktif Servis Sayısı -->
139
+ <div class="glass-card rounded-xl p-4 flex flex-col justify-between">
140
+ <div class="flex items-center justify-between">
141
+ <span class="text-xs font-medium text-gray-400">Aktif Registry (Moat)</span>
142
+ <span class="text-indigo-400 text-sm">🗂️</span>
143
+ </div>
144
+ <div class="mt-2 flex items-baseline gap-2">
145
+ <span id="kpiTotalServices" class="text-2xl font-bold text-white">-</span>
146
+ <span class="text-xs text-emerald-400 font-medium">Hazır Servis</span>
147
+ </div>
148
+ <div id="kpiActiveSessions" class="mt-1 text-[11px] text-gray-500">TokenStore izole oturum hazır</div>
149
+ </div>
150
+
151
+ <!-- KPI 2: 3x Auto-Test Başarı Oranı -->
152
+ <div class="glass-card rounded-xl p-4 flex flex-col justify-between">
153
+ <div class="flex items-center justify-between">
154
+ <span class="text-xs font-medium text-gray-400">3x Auto-Test Doğrulama</span>
155
+ <span class="text-emerald-400 text-sm">🛡️</span>
156
+ </div>
157
+ <div class="mt-2 flex items-baseline gap-2">
158
+ <span id="kpiValidationRate" class="text-2xl font-bold text-emerald-400">%100</span>
159
+ <span id="kpiValidatedCount" class="text-xs text-gray-400 font-mono">(3/3 Pass)</span>
160
+ </div>
161
+ <div class="mt-1 text-[11px] text-gray-500">Compiler + GPT-4o Validator Loop</div>
162
+ </div>
163
+
164
+ <!-- KPI 3: Çalışma Modu -->
165
+ <div class="glass-card rounded-xl p-4 flex flex-col justify-between">
166
+ <div class="flex items-center justify-between">
167
+ <span class="text-xs font-medium text-gray-400">Browser Tünel Modu</span>
168
+ <span class="text-cyan-400 text-sm">🌐</span>
169
+ </div>
170
+ <div class="mt-2 flex items-baseline gap-2">
171
+ <span id="kpiCurrentMode" class="text-lg font-bold text-cyan-400">LOCAL CHROME</span>
172
+ </div>
173
+ <div id="kpiModeDesc" class="mt-1 text-[11px] text-gray-500">User-Delegated In-Browser Fetch</div>
174
+ </div>
175
+
176
+ <!-- KPI 4: Gece 03:00 Auto-Repair Durumu -->
177
+ <div class="glass-card rounded-xl p-4 flex flex-col justify-between cursor-pointer" onclick="switchTab('hitl')">
178
+ <div class="flex items-center justify-between">
179
+ <span class="text-xs font-medium text-gray-400">Gece 03:00 Auto-Repair</span>
180
+ <span class="text-amber-400 text-sm">🌙</span>
181
+ </div>
182
+ <div class="mt-2 flex items-baseline gap-2">
183
+ <span id="kpiAutoRepairStatus" class="text-lg font-bold text-emerald-400">AKTİF (03:00)</span>
184
+ </div>
185
+ <div id="kpiAutoRepairLast" class="mt-1 text-[11px] text-gray-500 truncate">Self-Healing 2.0 Zamanlayıcı</div>
186
+ </div>
187
+
188
+ <!-- KPI 5: Bekleyen HITL Görev Sayısı -->
189
+ <div class="glass-card rounded-xl p-4 flex flex-col justify-between cursor-pointer" onclick="switchTab('hitl')">
190
+ <div class="flex items-center justify-between">
191
+ <span class="text-xs font-medium text-gray-400">Bekleyen HITL Onayı</span>
192
+ <span class="text-rose-400 text-sm">⏳</span>
193
+ </div>
194
+ <div class="mt-2 flex items-baseline gap-2">
195
+ <span id="kpiPendingHitl" class="text-2xl font-bold text-amber-400">0</span>
196
+ <span class="text-xs text-gray-400">Görev</span>
197
+ </div>
198
+ <div class="mt-1 text-[11px] text-indigo-400 hover:underline">Kuyruğu Yönet →</div>
199
+ </div>
200
+ </section>
201
+
202
+ <!-- SEKME MENÜSÜ (6 SEKME: HIZLI BAŞLANGIÇ, API ANAHTARLARIM, BULUTA GEÇ, PLAYGROUND, HITL, TOS) -->
203
+ <div class="border-b border-borderSubtle flex flex-wrap items-center gap-1.5">
204
+ <button id="tabBtn-marketplace" onclick="switchTab('marketplace')"
205
+ class="px-3.5 py-2.5 text-sm font-medium border-b-2 border-indigo-500 text-white transition flex items-center gap-2">
206
+ <span>🚀</span> 1. Site Seç & Hızlı Başlangıç
207
+ </button>
208
+ <button id="tabBtn-apikeys" onclick="switchTab('apikeys')"
209
+ class="px-3.5 py-2.5 text-sm font-medium border-b-2 border-transparent text-gray-400 hover:text-white transition flex items-center gap-2">
210
+ <span>🔑</span> 2. API Anahtarlarım (`sk-ownapi-...`)
211
+ </button>
212
+ <button id="tabBtn-billing" onclick="switchTab('billing')"
213
+ class="px-3.5 py-2.5 text-sm font-medium border-b-2 border-transparent text-gray-400 hover:text-white transition flex items-center gap-2">
214
+ <span>☁️</span> 3. Buluta Geç ($10 / $49 Paketler)
215
+ </button>
216
+ <button id="tabBtn-playground" onclick="switchTab('playground')"
217
+ class="px-3.5 py-2.5 text-sm font-medium border-b-2 border-transparent text-gray-400 hover:text-white transition flex items-center gap-2">
218
+ <span>⚡</span> 4. Live API Playground & AI Agent
219
+ </button>
220
+ <button id="tabBtn-hitl" onclick="switchTab('hitl')"
221
+ class="px-3.5 py-2.5 text-sm font-medium border-b-2 border-transparent text-gray-400 hover:text-white transition flex items-center gap-2">
222
+ <span>🛡️</span> 5. HITL Onay Kuyruğu & Auto-Repair
223
+ <span id="tabHitlBadge" class="hidden px-1.5 py-0.5 text-[10px] rounded-full bg-rose-500/20 text-rose-400 border border-rose-500/40 font-mono">0</span>
224
+ </button>
225
+ <button id="tabBtn-consent" onclick="switchTab('consent')"
226
+ class="px-3.5 py-2.5 text-sm font-medium border-b-2 border-transparent text-gray-400 hover:text-white transition flex items-center gap-2">
227
+ <span>⚖️</span> 6. ToS Consent Audit Log
228
+ </button>
229
+ </div>
230
+
231
+ <!-- BİLDİRİM TOAST ALANI -->
232
+ <div id="statusToast" class="hidden rounded-xl p-4 border bg-bgElevated border-indigo-500/40 text-sm flex items-center justify-between">
233
+ <div id="statusToastText" class="text-gray-200"></div>
234
+ <button onclick="document.getElementById('statusToast').classList.add('hidden')" class="text-gray-400 hover:text-white text-xs">✕</button>
235
+ </div>
236
+
237
+ <!-- CANLI HITL ONAY BANDI (.sessions/hitl_state.json — 2sn Polling ile F5 Sonrası Anında Görünür) -->
238
+ <div id="liveHitlBanner" class="hidden rounded-xl p-4 border border-amber-500/50 bg-amber-950/30 space-y-3 shadow-lg shadow-amber-950/20">
239
+ <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
240
+ <div class="flex items-center gap-2.5">
241
+ <span class="inline-flex h-3 w-3 rounded-full bg-amber-400 animate-ping"></span>
242
+ <span class="text-xs font-bold uppercase tracking-wider text-amber-300">
243
+ ⏸️ Bekleyen HITL Ajan Onayları (.sessions/hitl_state.json)
244
+ </span>
245
+ <span id="liveHitlBannerCount" class="px-2 py-0.5 text-[11px] font-mono rounded-full bg-amber-500/20 text-amber-300 border border-amber-500/40">0 PAUSED</span>
246
+ </div>
247
+ <button onclick="switchTab('hitl')" class="text-xs text-amber-300 hover:text-white underline font-medium text-left sm:text-right">
248
+ HITL Onay Kuyruğu Sekmesine Git →
249
+ </button>
250
+ </div>
251
+ <div id="liveHitlBannerList" class="space-y-2"></div>
252
+ </div>
253
+
254
+ <!-- ========================================================= -->
255
+ <!-- SEKME 1: SİTE SEÇ & HIZLI BAŞLANGIÇ + REGISTRY MARKETPLACE -->
256
+ <!-- ========================================================= -->
257
+ <section id="tabContent-marketplace" class="space-y-6">
258
+
259
+ <!-- HIZLI BAŞLANGIÇ (SİTE SEÇ -> SOLDA KOPYALANABİLİR KOD / SAĞDA CANLI TEST ET) -->
260
+ <div class="glass-card rounded-2xl p-5 border border-indigo-500/30 space-y-5">
261
+ <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-2 border-b border-borderSubtle pb-3">
262
+ <div>
263
+ <h2 class="text-base font-bold text-white flex items-center gap-2">
264
+ <span>⚡</span> Site Seç & 30 Saniyede Hızlı Başlangıç (Quickstart)
265
+ </h2>
266
+ <p class="text-xs text-gray-400">
267
+ Bir hazır konektör seçin: Sol taraftan Python kodunu kopyalayın, sağ taraftan <b>[Test Et]</b> ile anında canlı çalıştırın.
268
+ </p>
269
+ </div>
270
+ <div class="flex items-center gap-2">
271
+ <span class="text-[11px] font-mono px-2.5 py-1 rounded-lg bg-indigo-500/15 text-indigo-300 border border-indigo-500/30">
272
+ pip install -e . &nbsp;|&nbsp; ownapi init
273
+ </span>
274
+ </div>
275
+ </div>
276
+
277
+ <!-- Popüler Hazır Site Seçim Kartları -->
278
+ <div id="quickstartSiteCards" class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-2.5">
279
+ <button onclick="selectQuickstartSite('thy')" id="qsCard-thy"
280
+ class="qs-site-card text-left p-3 rounded-xl border border-indigo-500 bg-indigo-500/15 transition hover:border-indigo-400">
281
+ <div class="flex items-center justify-between">
282
+ <span class="text-base">✈️</span>
283
+ <span class="text-[10px] font-mono px-1.5 py-0.5 rounded bg-emerald-500/20 text-emerald-300">Hazır</span>
284
+ </div>
285
+ <div class="mt-1.5 font-bold text-xs text-white">THY (Türk Hava Yolları)</div>
286
+ <div class="text-[10px] font-mono text-indigo-300 truncate">thy.search_flights()</div>
287
+ </button>
288
+
289
+ <button onclick="selectQuickstartSite('trendyol')" id="qsCard-trendyol"
290
+ class="qs-site-card text-left p-3 rounded-xl border border-borderSubtle bg-bgBase/70 transition hover:border-indigo-400">
291
+ <div class="flex items-center justify-between">
292
+ <span class="text-base">🛍️</span>
293
+ <span class="text-[10px] font-mono px-1.5 py-0.5 rounded bg-emerald-500/20 text-emerald-300">Hazır</span>
294
+ </div>
295
+ <div class="mt-1.5 font-bold text-xs text-white">Trendyol</div>
296
+ <div class="text-[10px] font-mono text-indigo-300 truncate">trendyol.search()</div>
297
+ </button>
298
+
299
+ <button onclick="selectQuickstartSite('pegasus')" id="qsCard-pegasus"
300
+ class="qs-site-card text-left p-3 rounded-xl border border-borderSubtle bg-bgBase/70 transition hover:border-indigo-400">
301
+ <div class="flex items-center justify-between">
302
+ <span class="text-base">🛫</span>
303
+ <span class="text-[10px] font-mono px-1.5 py-0.5 rounded bg-emerald-500/20 text-emerald-300">Hazır</span>
304
+ </div>
305
+ <div class="mt-1.5 font-bold text-xs text-white">Pegasus</div>
306
+ <div class="text-[10px] font-mono text-indigo-300 truncate">pegasus.get_flight_availability()</div>
307
+ </button>
308
+
309
+ <button onclick="selectQuickstartSite('enuygun')" id="qsCard-enuygun"
310
+ class="qs-site-card text-left p-3 rounded-xl border border-borderSubtle bg-bgBase/70 transition hover:border-indigo-400">
311
+ <div class="flex items-center justify-between">
312
+ <span class="text-base">🎫</span>
313
+ <span class="text-[10px] font-mono px-1.5 py-0.5 rounded bg-emerald-500/20 text-emerald-300">Hazır</span>
314
+ </div>
315
+ <div class="mt-1.5 font-bold text-xs text-white">Enuygun</div>
316
+ <div class="text-[10px] font-mono text-indigo-300 truncate">enuygun.get_popular_flights()</div>
317
+ </button>
318
+
319
+ <button onclick="selectQuickstartSite('linkedin_jobs')" id="qsCard-linkedin_jobs"
320
+ class="qs-site-card text-left p-3 rounded-xl border border-borderSubtle bg-bgBase/70 transition hover:border-indigo-400">
321
+ <div class="flex items-center justify-between">
322
+ <span class="text-base">💼</span>
323
+ <span class="text-[10px] font-mono px-1.5 py-0.5 rounded bg-emerald-500/20 text-emerald-300">Hazır</span>
324
+ </div>
325
+ <div class="mt-1.5 font-bold text-xs text-white">LinkedIn Jobs</div>
326
+ <div class="text-[10px] font-mono text-indigo-300 truncate">linkedin_jobs.search_jobs()</div>
327
+ </button>
328
+
329
+ <button onclick="selectQuickstartSite('wikipedia')" id="qsCard-wikipedia"
330
+ class="qs-site-card text-left p-3 rounded-xl border border-borderSubtle bg-bgBase/70 transition hover:border-indigo-400">
331
+ <div class="flex items-center justify-between">
332
+ <span class="text-base">📚</span>
333
+ <span class="text-[10px] font-mono px-1.5 py-0.5 rounded bg-emerald-500/20 text-emerald-300">Hazır</span>
334
+ </div>
335
+ <div class="mt-1.5 font-bold text-xs text-white">Wikipedia</div>
336
+ <div class="text-[10px] font-mono text-indigo-300 truncate">wikipedia.search()</div>
337
+ </button>
338
+ </div>
339
+
340
+ <!-- Sol: Kopyalanabilir Python Kodu | Sağ: Canlı [Test Et] & Yanıt -->
341
+ <div class="grid grid-cols-1 lg:grid-cols-12 gap-5 pt-1">
342
+ <!-- Sol: Kopyalanabilir Python SDK Kodu -->
343
+ <div class="lg:col-span-6 bg-bgBase border border-borderSubtle rounded-xl p-4 flex flex-col justify-between space-y-3">
344
+ <div class="flex items-center justify-between">
345
+ <div class="flex items-center gap-2">
346
+ <span class="text-xs font-bold text-white">🐍 Kopyalanabilir Python Kodu</span>
347
+ <span id="qsSelectedServiceBadge" class="text-[11px] font-mono px-2 py-0.5 rounded bg-indigo-500/20 text-indigo-300">thy</span>
348
+ </div>
349
+ <button onclick="copyQuickstartCode()"
350
+ class="px-3 py-1 rounded-lg bg-indigo-600/20 hover:bg-indigo-600/40 border border-indigo-500/40 text-indigo-300 text-xs font-medium transition">
351
+ 📋 Kodu Kopyala
352
+ </button>
353
+ </div>
354
+ <pre id="qsPythonCodeBlock" class="text-xs font-mono text-indigo-200 overflow-x-auto leading-relaxed p-3 rounded-lg bg-bgCard border border-borderSubtle"></pre>
355
+ </div>
356
+
357
+ <!-- Sağ: Canlı [Test Et] Butonu ve Sonuç Ekranı -->
358
+ <div class="lg:col-span-6 bg-bgBase border border-borderSubtle rounded-xl p-4 flex flex-col justify-between space-y-3">
359
+ <div class="flex items-center justify-between">
360
+ <div>
361
+ <span class="text-xs font-bold text-white">⚡ Canlı Konektör Testi</span>
362
+ <span id="qsTestTimerBadge" class="ml-2 text-[11px] font-mono text-cyan-400">Hazır</span>
363
+ </div>
364
+ <div class="flex items-center gap-2">
365
+ <button id="qsRunTestBtn" onclick="runQuickstartTest()"
366
+ class="px-4 py-1.5 rounded-lg bg-gradient-to-r from-emerald-600 to-cyan-600 hover:from-emerald-500 hover:to-cyan-500 text-white font-bold text-xs shadow-lg shadow-emerald-600/25 transition flex items-center gap-1.5">
367
+ <span>▶️</span> Test Et
368
+ </button>
369
+ <button onclick="openInPlayground(selectedQuickstartService)"
370
+ class="px-2.5 py-1.5 rounded-lg bg-bgCard border border-borderSubtle text-gray-300 hover:text-white text-xs transition">
371
+ Playground ↗
372
+ </button>
373
+ </div>
374
+ </div>
375
+ <pre id="qsTestResultViewer" class="h-44 bg-bgCard border border-borderSubtle rounded-lg p-3 text-xs font-mono text-emerald-300 overflow-auto leading-relaxed">{
376
+ "durum": "Hazır — Sağ üstteki [▶️ Test Et] butonuna basarak seçili siteyi canlı test edin."
377
+ }</pre>
378
+ </div>
379
+ </div>
380
+ </div>
381
+ <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
382
+ <div>
383
+ <h2 class="text-lg font-semibold text-white">Hazır Kurumsal API Kütüphanesi (Moat Registry)</h2>
384
+ <p class="text-xs text-gray-400">
385
+ Tüm servisler <code class="text-indigo-400 font-mono">BaseOwnClient</code> tarayıcı içi fetch tüneli, çok kiracılı <code class="text-cyan-400 font-mono">TokenStore</code> ve 3x Auto-Test güvencesiyle çalışır.
386
+ </p>
387
+ </div>
388
+ <div class="flex items-center gap-2">
389
+ <input id="serviceSearchInput" type="text" placeholder="Servis veya uç nokta ara..."
390
+ oninput="renderMarketplaceCards()"
391
+ class="bg-bgCard border border-borderSubtle rounded-lg px-3 py-1.5 text-xs text-white placeholder-gray-500 focus:outline-none focus:border-indigo-500 w-60" />
392
+ <button onclick="openAddSiteModal()"
393
+ class="px-3.5 py-1.5 rounded-lg bg-gradient-to-r from-indigo-600 to-cyan-600 hover:from-indigo-500 hover:to-cyan-500 text-white font-semibold text-xs shadow-lg shadow-indigo-600/25 transition flex items-center gap-1.5 whitespace-nowrap">
394
+ <span>➕</span> Add New Site
395
+ </button>
396
+ </div>
397
+ </div>
398
+
399
+ <div id="marketplaceGrid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
400
+ <!-- JS ile doldurulur -->
401
+ </div>
402
+ </section>
403
+
404
+ <!-- ========================================================= -->
405
+ <!-- SEKME 2: API ANAHTARLARIM (sk-ownapi-...) -->
406
+ <!-- ========================================================= -->
407
+ <section id="tabContent-apikeys" class="hidden space-y-6">
408
+ <div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
409
+ <!-- Sol: Aktif API Anahtarı & Kredi Kartı -->
410
+ <div class="lg:col-span-7 glass-card rounded-2xl p-6 space-y-5 border border-indigo-500/30">
411
+ <div class="flex items-center justify-between border-b border-borderSubtle pb-4">
412
+ <div>
413
+ <h2 class="text-base font-bold text-white flex items-center gap-2">
414
+ <span>🔑</span> API Anahtarlarım (`sk-ownapi-...`)
415
+ </h2>
416
+ <p class="text-xs text-gray-400 mt-0.5">
417
+ Python SDK (<code class="text-indigo-400 font-mono">OwnAPI(api_key=...)</code>) ve MCP sunucunuz için kimlik doğrulama anahtarı.
418
+ </p>
419
+ </div>
420
+ <span id="apiKeyPlanBadge" class="px-3 py-1 rounded-full text-xs font-semibold bg-indigo-500/20 text-indigo-300 border border-indigo-500/30">
421
+ Free (Local)
422
+ </span>
423
+ </div>
424
+
425
+ <!-- API Anahtarı Göster / Gizle / Kopyala Kutusu -->
426
+ <div class="space-y-2">
427
+ <label class="block text-xs font-medium text-gray-300">Aktif Gizli Anahtar (Secret Key)</label>
428
+ <div class="flex flex-col sm:flex-row items-stretch sm:items-center gap-2">
429
+ <div class="flex-1 bg-bgBase border border-borderSubtle rounded-xl px-4 py-3 flex items-center justify-between gap-2">
430
+ <code id="apiKeyDisplayValue" class="text-xs sm:text-sm font-mono text-amber-300 select-all truncate">
431
+ sk-ownapi-••••••••••••••••••••••
432
+ </code>
433
+ <button id="toggleKeyBtn" onclick="toggleApiKeyVisibility()"
434
+ class="text-xs text-gray-400 hover:text-white px-2 py-1 rounded bg-bgElevated border border-borderSubtle whitespace-nowrap">
435
+ 👁️ Göster
436
+ </button>
437
+ </div>
438
+ <button onclick="copyCurrentApiKey()"
439
+ class="px-4 py-3 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-semibold text-xs transition shadow flex items-center justify-center gap-1.5 whitespace-nowrap">
440
+ <span>📋</span> Anahtarı Kopyala
441
+ </button>
442
+ <button id="btnGenerateNewKey" onclick="generateNewApiKey()"
443
+ class="px-4 py-3 rounded-xl bg-bgElevated hover:bg-gray-800 border border-borderSubtle text-gray-200 font-semibold text-xs transition flex items-center justify-center gap-1.5 whitespace-nowrap">
444
+ <span>🔄</span> Yeni Anahtar Üret
445
+ </button>
446
+ </div>
447
+ </div>
448
+
449
+ <!-- Kredi Durumu İlerleme Çubuğu -->
450
+ <div class="bg-bgBase/80 border border-borderSubtle rounded-xl p-4 space-y-2.5">
451
+ <div class="flex items-center justify-between text-xs">
452
+ <span class="text-gray-300 font-medium">Kalan Kredi Bakiyesi</span>
453
+ <span id="apiKeyCreditSummary" class="font-mono font-bold text-emerald-400">1.000 / 1.000 Kredi</span>
454
+ </div>
455
+ <div class="w-full h-2.5 bg-gray-800 rounded-full overflow-hidden">
456
+ <div id="apiKeyCreditBar" class="h-full bg-gradient-to-r from-indigo-500 to-emerald-400 rounded-full transition-all duration-300" style="width: 100%"></div>
457
+ </div>
458
+ <div class="flex items-center justify-between text-[11px] text-gray-400">
459
+ <span id="apiKeyModeInfo">Çalışma Modu: <b class="text-cyan-300">local (Kendi Bilgisayarın)</b></span>
460
+ <button onclick="switchTab('billing')" class="text-indigo-400 hover:underline font-medium">
461
+ ☁️ Bulut Paketine Yükselt ($10 / 10.000 Kredi) →
462
+ </button>
463
+ </div>
464
+ </div>
465
+ </div>
466
+
467
+ <!-- Sağ: Hızlı SDK ve CLI Kullanım Rehberi -->
468
+ <div class="lg:col-span-5 glass-card rounded-2xl p-6 space-y-4">
469
+ <h3 class="text-sm font-bold text-white flex items-center gap-2 border-b border-borderSubtle pb-3">
470
+ <span>💻</span> Bu Anahtarı Nasıl Kullanırım?
471
+ </h3>
472
+ <div class="space-y-3 text-xs">
473
+ <div>
474
+ <div class="text-gray-300 font-semibold mb-1">1. Python SDK ile Kullanım:</div>
475
+ <pre id="apiKeySdkSnippet" class="bg-bgBase border border-borderSubtle rounded-xl p-3 font-mono text-[11px] text-indigo-200 overflow-x-auto leading-relaxed">from ownapi import OwnAPI
476
+
477
+ api = OwnAPI(api_key="sk-ownapi-...")
478
+ flights = api.thy.search_flights(
479
+ origin_airport_code="IST",
480
+ destination_airport_code="ESB"
481
+ )
482
+ print(flights)</pre>
483
+ </div>
484
+ <div>
485
+ <div class="text-gray-300 font-semibold mb-1">2. Tek Komutla Claude Desktop & CLI:</div>
486
+ <pre class="bg-bgBase border border-borderSubtle rounded-xl p-3 font-mono text-[11px] text-cyan-300 overflow-x-auto leading-relaxed"># Claude Desktop'a otomatik bağla
487
+ ownapi init
488
+
489
+ # Kalan kredi ve paket durumunu gör
490
+ ownapi status</pre>
491
+ </div>
492
+ </div>
493
+ </div>
494
+ </div>
495
+ </section>
496
+
497
+ <!-- ========================================================= -->
498
+ <!-- SEKME 3: BULUTA GEÇ ($10 / $49 PAKETLER) -->
499
+ <!-- ========================================================= -->
500
+ <section id="tabContent-billing" class="hidden space-y-6">
501
+ <div class="text-center max-w-2xl mx-auto space-y-2">
502
+ <span class="px-3 py-1 rounded-full text-[11px] font-bold uppercase tracking-wider bg-cyan-500/15 text-cyan-300 border border-cyan-500/30">
503
+ ☁️ OwnAPI Cloud & Local SaaS Paketleri
504
+ </span>
505
+ <h2 class="text-xl sm:text-2xl font-bold text-white">
506
+ Bilgisayarınız Kapalıyken Bile 7/24 Bulutta Çalıştırın
507
+ </h2>
508
+ <p class="text-xs sm:text-sm text-gray-400">
509
+ Bedava yerel modla kendi bilgisayarınızda hemen başlayın; hazır olduğunuzda tek tıkla <b>Bulut Tarayıcı Kümesine (`mode="cloud"`)</b> geçin.
510
+ </p>
511
+ </div>
512
+
513
+ <!-- 3'lü Paket Karşılaştırma Kartları -->
514
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
515
+ <!-- Paket 1: Bedava (Free Local) -->
516
+ <div class="glass-card rounded-2xl p-6 flex flex-col justify-between border border-borderSubtle">
517
+ <div class="space-y-4">
518
+ <div class="flex items-center justify-between">
519
+ <span class="text-sm font-bold text-gray-200">💻 Bedava (Free)</span>
520
+ <span id="planActiveBadge-free" class="px-2.5 py-0.5 rounded-full text-[10px] font-mono bg-emerald-500/20 text-emerald-300 border border-emerald-500/30">
521
+ Mevcut Paket
522
+ </span>
523
+ </div>
524
+ <div class="flex items-baseline gap-1">
525
+ <span class="text-3xl font-extrabold text-white">$0</span>
526
+ <span class="text-xs text-gray-400">/ ay</span>
527
+ </div>
528
+ <p class="text-xs text-gray-400">Kendi bilgisayarınız açıkken yerel Chrome oturumu üzerinden çalışır.</p>
529
+ <ul class="space-y-2 text-xs text-gray-300 pt-2 border-t border-borderSubtle">
530
+ <li class="flex items-center gap-2"><span class="text-emerald-400">✔</span> <b>1.000 istek / ay</b> ücretsiz kredi</li>
531
+ <li class="flex items-center gap-2"><span class="text-emerald-400">✔</span> <b>En fazla 2 farklı site / konektör</b> (<code class="font-mono text-cyan-300">max_sites = 2</code>)</li>
532
+ <li class="flex items-center gap-2"><span class="text-emerald-400">✔</span> Kendi bilgisayarın (<code class="font-mono text-indigo-300">mode="local"</code>)</li>
533
+ <li class="flex items-center gap-2"><span class="text-emerald-400">✔</span> Hazır konektörler (THY, Trendyol, Pegasus vb.)</li>
534
+ <li class="flex items-center gap-2"><span class="text-emerald-400">✔</span> <code class="font-mono text-cyan-300">ownapi init</code> ile Claude MCP bağlantısı</li>
535
+ </ul>
536
+ </div>
537
+ <div class="pt-6">
538
+ <button onclick="selectFreePlan()"
539
+ class="w-full py-2.5 px-4 rounded-xl bg-bgElevated hover:bg-gray-800 border border-borderSubtle text-gray-200 font-semibold text-xs transition">
540
+ Ücretsiz Yerel Modu Kullan ($0)
541
+ </button>
542
+ </div>
543
+ </div>
544
+
545
+ <!-- Paket 2: Başlangıç (Starter Cloud - $10/ay) -->
546
+ <div class="glass-card rounded-2xl p-6 flex flex-col justify-between border-2 border-indigo-500 shadow-2xl shadow-indigo-950/50 relative">
547
+ <div class="absolute -top-3 right-5 px-3 py-0.5 rounded-full bg-gradient-to-r from-indigo-600 to-cyan-600 text-white text-[10px] font-bold uppercase tracking-wider shadow">
548
+ 🔥 En Popüler
549
+ </div>
550
+ <div class="space-y-4">
551
+ <div class="flex items-center justify-between">
552
+ <span class="text-sm font-bold text-indigo-300">☁️ Başlangıç (Starter Cloud)</span>
553
+ <span id="planActiveBadge-starter_cloud" class="hidden px-2.5 py-0.5 rounded-full text-[10px] font-mono bg-emerald-500/20 text-emerald-300 border border-emerald-500/30">
554
+ Aktif Paket
555
+ </span>
556
+ </div>
557
+ <div class="flex items-baseline gap-1">
558
+ <span class="text-3xl font-extrabold text-white">$10</span>
559
+ <span class="text-xs text-gray-400">/ ay</span>
560
+ </div>
561
+ <p class="text-xs text-gray-300">Bilgisayarınız kapalıyken bile 7/24 bulut tarayıcı kümesinde kesintisiz çalışır.</p>
562
+ <ul class="space-y-2 text-xs text-gray-200 pt-2 border-t border-borderSubtle">
563
+ <li class="flex items-center gap-2"><span class="text-cyan-400">✔</span> <b>10.000 istek / ay</b> bulut kredisi</li>
564
+ <li class="flex items-center gap-2"><span class="text-cyan-400">✔</span> <b>En fazla 5 site / konektör</b> (<code class="font-mono text-cyan-300">max_sites = 5</code>)</li>
565
+ <li class="flex items-center gap-2"><span class="text-cyan-400">✔</span> 7/24 Bulut Tarayıcı (<code class="font-mono text-cyan-300">mode="cloud"</code>)</li>
566
+ <li class="flex items-center gap-2"><span class="text-cyan-400">✔</span> Özel <code class="font-mono text-amber-300">sk-ownapi-...</code> bulut anahtarı</li>
567
+ <li class="flex items-center gap-2"><span class="text-cyan-400">✔</span> Gece 03:00 Auto-Repair & HITL bildirimleri</li>
568
+ </ul>
569
+ </div>
570
+ <div class="pt-6">
571
+ <button onclick="openCheckoutModal('starter_cloud', 'Başlangıç Bulut Paketi (Starter Cloud)', 10, '10.000')"
572
+ class="w-full py-2.5 px-4 rounded-xl bg-gradient-to-r from-indigo-600 to-cyan-600 hover:from-indigo-500 hover:to-cyan-500 text-white font-bold text-xs shadow-lg shadow-indigo-600/30 transition">
573
+ 🚀 Seç - $10
574
+ </button>
575
+ </div>
576
+ </div>
577
+
578
+ <!-- Paket 3: Büyüme (Growth Cloud - $49/ay) -->
579
+ <div class="glass-card rounded-2xl p-6 flex flex-col justify-between border border-cyan-500/40">
580
+ <div class="space-y-4">
581
+ <div class="flex items-center justify-between">
582
+ <span class="text-sm font-bold text-cyan-300">⚡ Büyüme (Growth Cloud)</span>
583
+ <span id="planActiveBadge-growth_cloud" class="hidden px-2.5 py-0.5 rounded-full text-[10px] font-mono bg-emerald-500/20 text-emerald-300 border border-emerald-500/30">
584
+ Aktif Paket
585
+ </span>
586
+ </div>
587
+ <div class="flex items-baseline gap-1">
588
+ <span class="text-3xl font-extrabold text-white">$49</span>
589
+ <span class="text-xs text-gray-400">/ ay</span>
590
+ </div>
591
+ <p class="text-xs text-gray-400">Yüksek hacimli üretim ortamları ve çoklu AI ajan sürüleri için izole bulut kümesi.</p>
592
+ <ul class="space-y-2 text-xs text-gray-300 pt-2 border-t border-borderSubtle">
593
+ <li class="flex items-center gap-2"><span class="text-emerald-400">✔</span> <b>100.000 istek / ay</b> yüksek hacimli kredi</li>
594
+ <li class="flex items-center gap-2"><span class="text-emerald-400">✔</span> <b>En fazla 50 site / konektör</b> (<code class="font-mono text-cyan-300">max_sites = 50</code>)</li>
595
+ <li class="flex items-center gap-2"><span class="text-emerald-400">✔</span> Özel Bulut Kümesi (<code class="font-mono text-cyan-300">mode="cloud"</code>)</li>
596
+ <li class="flex items-center gap-2"><span class="text-emerald-400">✔</span> Öncelikli GPT-4o Self-Healing 2.0 onarımı</li>
597
+ <li class="flex items-center gap-2"><span class="text-emerald-400">✔</span> Çoklu Kiracı (Multi-Tenant) & SLA Desteği</li>
598
+ </ul>
599
+ </div>
600
+ <div class="pt-6">
601
+ <button onclick="openCheckoutModal('growth_cloud', 'Büyüme Bulut Paketi (Growth Cloud)', 49, '100.000')"
602
+ class="w-full py-2.5 px-4 rounded-xl bg-cyan-600 hover:bg-cyan-500 text-white font-bold text-xs shadow-lg shadow-cyan-600/25 transition">
603
+ ⚡ Seç - $49
604
+ </button>
605
+ </div>
606
+ </div>
607
+ </div>
608
+ </section>
609
+
610
+ <!-- ========================================================= -->
611
+ <!-- SEKME 4: LIVE API PLAYGROUND & AI AGENT -->
612
+ <!-- ========================================================= -->
613
+ <section id="tabContent-playground" class="hidden">
614
+ <div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
615
+
616
+ <!-- SOL SÜTUN: Servis Parametreleri + Doğal Dil AI Agent -->
617
+ <div class="lg:col-span-5 space-y-6">
618
+
619
+ <!-- Doğrudan Servis Çağrısı Kartı -->
620
+ <div class="glass-card rounded-xl p-5 space-y-4">
621
+ <div class="flex items-center justify-between border-b border-borderSubtle pb-3">
622
+ <div>
623
+ <h3 class="text-sm font-semibold text-white flex items-center gap-2">
624
+ <span>⚡</span> Doğrudan Servis Çağrısı (In-Browser Fetch)
625
+ </h3>
626
+ <p class="text-[11px] text-gray-400">Seçilen servisi gerçek tarayıcı oturumuyla çalıştırır</p>
627
+ </div>
628
+ <span id="pgMethodBadge" class="text-[11px] font-mono font-bold px-2 py-0.5 rounded bg-indigo-500/20 text-indigo-400 border border-indigo-500/30">
629
+ GET
630
+ </span>
631
+ </div>
632
+
633
+ <!-- Servis Seçimi -->
634
+ <div>
635
+ <label class="block text-xs font-medium text-gray-300 mb-1">Kayıtlı Servis Seçin</label>
636
+ <select id="pgServiceSelect" onchange="onPlaygroundServiceChange()"
637
+ class="w-full bg-bgBase border border-borderSubtle rounded-lg px-3 py-2 text-sm text-white focus:outline-none focus:border-indigo-500">
638
+ </select>
639
+ <div id="pgEndpointUrl" class="mt-1 text-[11px] font-mono text-gray-400 truncate"></div>
640
+ </div>
641
+
642
+ <!-- Dinamik Parametre Inputları -->
643
+ <div id="pgDynamicParamsContainer" class="space-y-3">
644
+ <!-- JS ile seçilen servise göre oluşturulur -->
645
+ </div>
646
+
647
+ <!-- Non-Interactive HITL Toggle -->
648
+ <div class="flex items-center justify-between pt-2 border-t border-borderSubtle text-xs">
649
+ <label for="pgNonInteractive" class="text-gray-300 flex items-center gap-2 cursor-pointer">
650
+ <input id="pgNonInteractive" type="checkbox" onchange="updateCodeSnippets()"
651
+ class="rounded bg-bgBase border-borderSubtle text-indigo-600 focus:ring-0" />
652
+ <span>Non-Interactive (Captcha çıkarsa HITL kuyruğuna at)</span>
653
+ </label>
654
+ </div>
655
+
656
+ <button id="pgExecuteBtn" onclick="executePlaygroundCall()"
657
+ class="w-full py-2.5 px-4 rounded-lg bg-gradient-to-r from-indigo-600 to-cyan-600 hover:from-indigo-500 hover:to-cyan-500 text-white font-semibold text-sm shadow-lg shadow-indigo-600/25 transition flex items-center justify-center gap-2">
658
+ <span>🚀</span> Şimdi Çalıştır (Execute In-Browser)
659
+ </button>
660
+ </div>
661
+
662
+ <!-- Doğal Dil AI Agent (Ask AI) Kartı -->
663
+ <div class="glass-card rounded-xl p-5 space-y-4">
664
+ <div class="border-b border-borderSubtle pb-3">
665
+ <h3 class="text-sm font-semibold text-white flex items-center gap-2">
666
+ <span>🤖</span> OwnAPI AI Agent (Function Calling)
667
+ </h3>
668
+ <p class="text-[11px] text-gray-400">
669
+ Doğal dilde soru sorun; ajan kayıtlı servisleri otomatik seçip yanıtlasın.
670
+ </p>
671
+ </div>
672
+
673
+ <div class="flex flex-wrap gap-1.5">
674
+ <button onclick="setAgentPrompt('Wikipedia üzerinde Peter Thiel hakkında arama yap ve özetle')"
675
+ class="text-[11px] px-2.5 py-1 rounded-full bg-bgElevated border border-borderSubtle text-gray-300 hover:text-white hover:border-indigo-500/40">
676
+ Wikipedia: Peter Thiel
677
+ </button>
678
+ <button onclick="setAgentPrompt('Enuygun popüler uçuş rotalarını getir ve listele')"
679
+ class="text-[11px] px-2.5 py-1 rounded-full bg-bgElevated border border-borderSubtle text-gray-300 hover:text-white hover:border-indigo-500/40">
680
+ Enuygun Popüler Uçuşlar
681
+ </button>
682
+ <button onclick="setAgentPrompt('Trendyol üzerinde laptop araması yap ve ilk 3 ürünü fiyatlarıyla listele')"
683
+ class="text-[11px] px-2.5 py-1 rounded-full bg-bgElevated border border-borderSubtle text-gray-300 hover:text-white hover:border-indigo-500/40">
684
+ Trendyol Laptop Fiyatları
685
+ </button>
686
+ </div>
687
+
688
+ <textarea id="pgAgentPrompt" rows="3"
689
+ placeholder="Örn: Trendyol'da kablosuz kulaklık araması yap ve en uygun 3 modeli özetle..."
690
+ class="w-full bg-bgBase border border-borderSubtle rounded-lg p-3 text-xs text-white placeholder-gray-500 focus:outline-none focus:border-indigo-500"></textarea>
691
+
692
+ <button id="pgAskBtn" onclick="executeAgentAsk()"
693
+ class="w-full py-2 px-4 rounded-lg bg-emerald-600 hover:bg-emerald-500 text-white font-semibold text-xs shadow-lg shadow-emerald-600/20 transition flex items-center justify-center gap-2">
694
+ <span>✨</span> AI Ajanına Sor (`POST /api/v1/ask`)
695
+ </button>
696
+ </div>
697
+ </div>
698
+
699
+ <!-- SAĞ SÜTUN: Canlı JSON Yanıtı + Milisaniye Sayacı + SDK / cURL Kod Örnekleri -->
700
+ <div class="lg:col-span-7 space-y-6">
701
+
702
+ <!-- Canlı JSON Yanıt Kutusu -->
703
+ <div class="glass-card rounded-xl p-5 flex flex-col h-[420px]">
704
+ <div class="flex items-center justify-between border-b border-borderSubtle pb-3 mb-3">
705
+ <div class="flex items-center gap-2">
706
+ <span class="text-sm font-semibold text-white">📡 Canlı Yanıt (Live Response)</span>
707
+ <span id="pgStatusBadge" class="text-[11px] font-mono px-2 py-0.5 rounded bg-gray-800 text-gray-400">
708
+ HAZIR
709
+ </span>
710
+ </div>
711
+ <div class="flex items-center gap-3">
712
+ <span id="pgTimerBadge" class="text-xs font-mono text-cyan-400 bg-cyan-500/10 border border-cyan-500/30 px-2.5 py-0.5 rounded">
713
+ ⏱️ 0 ms
714
+ </span>
715
+ <button onclick="copyResponseJson()" class="text-xs text-gray-400 hover:text-white">
716
+ 📋 Kopyala
717
+ </button>
718
+ </div>
719
+ </div>
720
+
721
+ <pre id="pgResponseViewer" class="flex-1 bg-bgBase border border-borderSubtle rounded-lg p-4 text-xs font-mono text-emerald-300 overflow-auto leading-relaxed">{
722
+ "info": "Sol panelden bir servis seçip 'Şimdi Çalıştır' butonuna basın veya Marketplace'ten '⚡ Playground'da Dene' seçin."
723
+ }</pre>
724
+ </div>
725
+
726
+ <!-- Hazır Python SDK & cURL Kod Örnekleri -->
727
+ <div class="glass-card rounded-xl p-5 space-y-3">
728
+ <div class="flex items-center justify-between border-b border-borderSubtle pb-2">
729
+ <div class="flex items-center gap-2">
730
+ <button id="codeTabPython" onclick="switchCodeTab('python')"
731
+ class="text-xs font-semibold px-3 py-1 rounded-md bg-indigo-600 text-white">
732
+ 🐍 Python SDK (OwnAPIClient)
733
+ </button>
734
+ <button id="codeTabCurl" onclick="switchCodeTab('curl')"
735
+ class="text-xs font-semibold px-3 py-1 rounded-md text-gray-400 hover:text-white">
736
+ 💻 REST cURL
737
+ </button>
738
+ </div>
739
+ <button onclick="copyCodeSnippet()" class="text-xs text-gray-400 hover:text-white">
740
+ 📋 Kodu Kopyala
741
+ </button>
742
+ </div>
743
+ <pre id="pgCodeSnippet" class="bg-bgBase border border-borderSubtle rounded-lg p-3.5 text-xs font-mono text-indigo-300 overflow-x-auto leading-relaxed"></pre>
744
+ </div>
745
+
746
+ </div>
747
+ </div>
748
+ </section>
749
+
750
+ <!-- ========================================================= -->
751
+ <!-- SEKME 3: HITL ONAY KUYRUĞU & AUTO-REPAIR MERKEZİ -->
752
+ <!-- ========================================================= -->
753
+ <section id="tabContent-hitl" class="hidden space-y-6">
754
+
755
+ <!-- Üst Aksiyon Paneli: Auto-Repair Tetikleyici -->
756
+ <div class="glass-card rounded-xl p-5 flex flex-col md:flex-row md:items-center justify-between gap-4">
757
+ <div>
758
+ <div class="flex items-center gap-2">
759
+ <h2 class="text-base font-semibold text-white">🛠️ Self-Healing 2.0 & Gece Otomatik Tamir Merkezi</h2>
760
+ <span class="text-[11px] font-mono px-2 py-0.5 rounded bg-emerald-500/15 text-emerald-400 border border-emerald-500/30">
761
+ Her Gece 03:00 Daemon Hazır
762
+ </span>
763
+ </div>
764
+ <p class="text-xs text-gray-400 mt-1">
765
+ Bozulan servisleri tespit eder, otomatik headless tarayıcıyla güncel trafiği yakalar ve GPT-4o ile <code class="text-indigo-400 font-mono">client.py</code> dosyasını onarıp 3x doğrulamadan geçirir.
766
+ </p>
767
+ </div>
768
+ <div class="flex items-center gap-3">
769
+ <button id="btnRunAutoRepair" onclick="triggerAutoRepair()"
770
+ class="px-4 py-2.5 rounded-lg bg-gradient-to-r from-amber-500 to-orange-600 hover:from-amber-400 hover:to-orange-500 text-white font-semibold text-xs shadow-lg shadow-amber-500/20 transition flex items-center gap-2 whitespace-nowrap">
771
+ <span>🔧</span> Tüm Servisleri Şimdi Tara & Onar (Auto-Repair)
772
+ </button>
773
+ </div>
774
+ </div>
775
+
776
+ <!-- Bulut (Cloud) HITL Uzaktan Çözüm Önizleme Kartları -->
777
+ <div id="hitlCloudPreviewSection" class="glass-card rounded-xl p-5 border border-cyan-500/30 space-y-4">
778
+ <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-2 border-b border-borderSubtle pb-3">
779
+ <div>
780
+ <h3 class="text-sm font-semibold text-white flex items-center gap-2">
781
+ <span>☁️ Uzak Bulut Tarayıcı Ekranı (Canlı Önizleme)</span>
782
+ <span class="px-2 py-0.5 text-[10px] font-mono rounded-full bg-cyan-500/15 text-cyan-300 border border-cyan-500/30">
783
+ Cloud HITL Remote Resolver
784
+ </span>
785
+ </h3>
786
+ <p class="text-xs text-gray-400 mt-0.5">
787
+ Bulut modunda (<code class="text-cyan-300 font-mono">execution_mode="cloud"</code>) veya önizlemesi mevcut görevlerde uzak tarayıcı ekranını görüntüleyin ve doğrudan Web Dashboard üzerinden doğrulamayı tamamlayın.
788
+ </p>
789
+ </div>
790
+ <button onclick="loadHitlQueue()" class="text-xs text-cyan-400 hover:underline self-start sm:self-center">
791
+ 🔄 Canlı Önizlemeyi Yenile
792
+ </button>
793
+ </div>
794
+
795
+ <div id="hitlCloudPreviewCards" class="grid grid-cols-1 gap-4">
796
+ <div class="rounded-xl bg-bgBase/80 border border-borderSubtle p-4 text-xs text-gray-400 space-y-3">
797
+ <div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
798
+ <div>
799
+ <div class="font-semibold text-gray-200">☁️ Uzak Bulut Tarayıcı Ekranı (Canlı Önizleme) Hazır</div>
800
+ <div class="text-[11px] text-gray-500 mt-0.5">Bekleyen bir Cloud HITL görevi oluştuğunda canlı ekran görüntüsü (`GET /api/hitl/preview/{task_id}`) ve uzaktan OTP giriş alanı otomatik olarak burada açılır.</div>
801
+ </div>
802
+ <span class="px-3 py-1.5 rounded-lg bg-emerald-600/20 text-emerald-300 border border-emerald-500/30 font-semibold text-[11px]">
803
+ Doğrulamayı Tamamla & Devam Et (Resume)
804
+ </span>
805
+ </div>
806
+ <div class="pt-2 border-t border-borderSubtle/60 flex flex-wrap items-center gap-2 text-[11px] text-gray-400">
807
+ <span>🔑 SMS / E-Posta Onay Kodu (Varsa):</span>
808
+ <span class="px-2.5 py-1 rounded bg-bgCard border border-borderSubtle font-mono text-amber-300">[ 123456 ]</span>
809
+ <span class="text-gray-500">(E-Devlet veya 2FA isteyen siteler için uzaktan kod gönderimi desteklenir)</span>
810
+ </div>
811
+ </div>
812
+ </div>
813
+ </div>
814
+
815
+ <!-- HITL Kuyruğu Tablosu -->
816
+ <div class="glass-card rounded-xl p-5 space-y-4">
817
+ <div class="flex items-center justify-between">
818
+ <div>
819
+ <h3 class="text-sm font-semibold text-white flex items-center gap-2">
820
+ <span>🛡️</span> Human-in-the-Loop (HITL) Asenkron Onay Kuyruğu
821
+ </h3>
822
+ <p class="text-xs text-gray-400">
823
+ Gece veya arka plan modunda (<code class="text-amber-400 font-mono">non_interactive=True</code>) Captcha / PerimeterX / 2FA doğrulamasına takılan görevler.
824
+ </p>
825
+ </div>
826
+ <button onclick="loadHitlQueue()" class="text-xs text-indigo-400 hover:underline">
827
+ 🔄 Kuyruğu Yenile
828
+ </button>
829
+ </div>
830
+
831
+ <div class="overflow-x-auto">
832
+ <table class="w-full text-left border-collapse text-xs">
833
+ <thead>
834
+ <tr class="border-b border-borderSubtle text-gray-400 uppercase text-[11px]">
835
+ <th class="py-2.5 px-3">Task ID</th>
836
+ <th class="py-2.5 px-3">Servis / Tenant</th>
837
+ <th class="py-2.5 px-3">Metot & Uç Nokta</th>
838
+ <th class="py-2.5 px-3">Bekleme Nedeni</th>
839
+ <th class="py-2.5 px-3">Durum</th>
840
+ <th class="py-2.5 px-3">Zaman (UTC)</th>
841
+ <th class="py-2.5 px-3 text-right">Aksiyon</th>
842
+ </tr>
843
+ </thead>
844
+ <tbody id="hitlTableBody" class="divide-y divide-borderSubtle">
845
+ <!-- JS ile doldurulur -->
846
+ </tbody>
847
+ </table>
848
+ </div>
849
+ </div>
850
+
851
+ <!-- Son Auto-Repair Raporu Tablosu -->
852
+ <div class="glass-card rounded-xl p-5 space-y-4">
853
+ <div class="flex items-center justify-between">
854
+ <div>
855
+ <h3 class="text-sm font-semibold text-white">📊 Son Self-Healing 2.0 Tarama & Onarım Raporu (`.sessions/auto_repair_report.json`)</h3>
856
+ <p id="autoRepairTimestamp" class="text-xs text-gray-400">Henüz rapor yüklenmedi</p>
857
+ </div>
858
+ </div>
859
+ <div class="overflow-x-auto">
860
+ <table class="w-full text-left border-collapse text-xs">
861
+ <thead>
862
+ <tr class="border-b border-borderSubtle text-gray-400 uppercase text-[11px]">
863
+ <th class="py-2 px-3">Servis</th>
864
+ <th class="py-2 px-3">İlk Durum</th>
865
+ <th class="py-2 px-3">Son Durum</th>
866
+ <th class="py-2 px-3">Otomatik Tamir</th>
867
+ <th class="py-2 px-3">Süre</th>
868
+ <th class="py-2 px-3">Detay</th>
869
+ </tr>
870
+ </thead>
871
+ <tbody id="autoRepairTableBody" class="divide-y divide-borderSubtle">
872
+ <!-- JS ile doldurulur -->
873
+ </tbody>
874
+ </table>
875
+ </div>
876
+ </div>
877
+ </section>
878
+
879
+ <!-- ========================================================= -->
880
+ <!-- SEKME 4: ToS CONSENT AUDIT LOG -->
881
+ <!-- ========================================================= -->
882
+ <section id="tabContent-consent" class="hidden space-y-6">
883
+ <!-- Yasal Kalkan Beyanı -->
884
+ <div class="glass-card rounded-xl p-5 border-l-4 border-l-amber-500 space-y-2">
885
+ <div class="flex items-center justify-between">
886
+ <h2 class="text-sm font-bold text-amber-400 uppercase tracking-wider flex items-center gap-2">
887
+ <span>⚖️</span> OwnAPI Legal Shield — Terms of Service & User Delegation Disclaimer
888
+ </h2>
889
+ <span id="tosVersionBadge" class="text-xs font-mono px-2.5 py-0.5 rounded bg-amber-500/15 text-amber-300 border border-amber-500/30">
890
+ v1.0-Enterprise
891
+ </span>
892
+ </div>
893
+ <pre id="tosDisclaimerText" class="text-xs text-gray-300 whitespace-pre-wrap font-sans leading-relaxed"></pre>
894
+ </div>
895
+
896
+ <!-- Denetim Tablosu -->
897
+ <div class="glass-card rounded-xl p-5 space-y-4">
898
+ <div class="flex items-center justify-between">
899
+ <div>
900
+ <h3 class="text-sm font-semibold text-white">📜 SHA-256 İmzalı Kullanıcı Delegasyon Denetim Günlüğü (`consent_audit.json`)</h3>
901
+ <p class="text-xs text-gray-400">Her yeni site ekleme ve ilk servis çağrısında otomatik kaydedilen yasal onay izleri.</p>
902
+ </div>
903
+ <button onclick="loadConsentAudit()" class="text-xs text-indigo-400 hover:underline">
904
+ 🔄 Kayıtları Yenile
905
+ </button>
906
+ </div>
907
+
908
+ <div class="overflow-x-auto">
909
+ <table class="w-full text-left border-collapse text-xs">
910
+ <thead>
911
+ <tr class="border-b border-borderSubtle text-gray-400 uppercase text-[11px]">
912
+ <th class="py-2.5 px-3">#</th>
913
+ <th class="py-2.5 px-3">Servis</th>
914
+ <th class="py-2.5 px-3">İşlem Türü</th>
915
+ <th class="py-2.5 px-3">Çalışma Modu</th>
916
+ <th class="py-2.5 px-3">Yetkilendiren (User@Host)</th>
917
+ <th class="py-2.5 px-3">SHA-256 İmza Özeti</th>
918
+ <th class="py-2.5 px-3">Onay Zamanı (UTC)</th>
919
+ </tr>
920
+ </thead>
921
+ <tbody id="consentTableBody" class="divide-y divide-borderSubtle">
922
+ <!-- JS ile doldurulur -->
923
+ </tbody>
924
+ </table>
925
+ </div>
926
+ </div>
927
+ </section>
928
+
929
+ </main>
930
+
931
+ <!-- ALT BİLGİ (FOOTER) -->
932
+ <footer class="border-t border-borderSubtle py-4 mt-10 text-center text-xs text-gray-500">
933
+ OwnAPI Enterprise Middleware (`own-api v1.0.0`) • Plaid-Style User-Delegated Browser Fetch • Multi-Tenant TokenStore & Self-Healing 2.0
934
+ </footer>
935
+
936
+ <!-- ========================================================= -->
937
+ <!-- MODAL: ADD NEW SITE (ZERO-CODE ONBOARDING & 3X VALIDATOR) -->
938
+ <!-- ========================================================= -->
939
+ <div id="addSiteModal" class="fixed inset-0 z-50 hidden flex items-center justify-center bg-black/75 backdrop-blur-sm p-4">
940
+ <div class="glass-card rounded-2xl w-full max-w-lg border border-indigo-500/40 shadow-2xl shadow-indigo-950/60 overflow-hidden">
941
+ <!-- Modal Başlık -->
942
+ <div class="px-6 py-4 border-b border-borderSubtle bg-bgElevated/80 flex items-center justify-between">
943
+ <div class="flex items-center gap-2.5">
944
+ <div class="w-8 h-8 rounded-lg bg-gradient-to-br from-indigo-500 to-cyan-500 flex items-center justify-center text-white font-bold text-sm shadow">
945
+ ➕
946
+ </div>
947
+ <div>
948
+ <h3 class="text-sm font-bold text-white">Add New Site (Zero-Code API Onboarding)</h3>
949
+ <p class="text-[11px] text-gray-400">Record XHR/Fetch • Compile via GPT-4o • 3x Auto-Test Loop</p>
950
+ </div>
951
+ </div>
952
+ <button onclick="closeAddSiteModal()" class="text-gray-400 hover:text-white text-sm px-2 py-1 rounded-lg hover:bg-white/5 transition">
953
+ ✕
954
+ </button>
955
+ </div>
956
+
957
+ <!-- Modal Form Gövdesi -->
958
+ <div class="p-6 space-y-4">
959
+ <!-- Site Adı (service_name) -->
960
+ <div>
961
+ <label class="block text-xs font-semibold text-gray-300 mb-1">
962
+ Site Adı (<code class="text-indigo-400 font-mono">service_name</code>) *
963
+ </label>
964
+ <input id="onboardServiceName" type="text" placeholder="Örn: biletix, stripe_docs, obilet"
965
+ class="w-full bg-bgBase border border-borderSubtle rounded-lg px-3.5 py-2 text-xs text-white font-mono placeholder-gray-500 focus:outline-none focus:border-indigo-500" />
966
+ </div>
967
+
968
+ <!-- Hedef URL (target_url) -->
969
+ <div>
970
+ <label class="block text-xs font-semibold text-gray-300 mb-1">
971
+ Hedef URL (<code class="text-cyan-400 font-mono">target_url</code>) *
972
+ </label>
973
+ <input id="onboardTargetUrl" type="url" placeholder="Örn: https://www.biletix.com"
974
+ class="w-full bg-bgBase border border-borderSubtle rounded-lg px-3.5 py-2 text-xs text-white font-mono placeholder-gray-500 focus:outline-none focus:border-indigo-500" />
975
+ </div>
976
+
977
+ <!-- Açıklama (description) -->
978
+ <div>
979
+ <label class="block text-xs font-semibold text-gray-300 mb-1">
980
+ Açıklama / Hedef İşlem (<code class="text-emerald-400 font-mono">description</code>)
981
+ </label>
982
+ <textarea id="onboardDescription" rows="2"
983
+ placeholder="Örn: Etkinlik ve konser biletlerini ara ve fiyatları getir..."
984
+ class="w-full bg-bgBase border border-borderSubtle rounded-lg px-3.5 py-2 text-xs text-white placeholder-gray-500 focus:outline-none focus:border-indigo-500"></textarea>
985
+ </div>
986
+
987
+ <!-- Kayıt Süresi (wait_seconds) -->
988
+ <div class="flex items-center justify-between gap-4 bg-bgBase/70 border border-borderSubtle rounded-lg px-3.5 py-2">
989
+ <div>
990
+ <span class="text-xs font-medium text-gray-300">Tarayıcı Dinleme Süresi</span>
991
+ <p class="text-[10px] text-gray-500">Açılan Chrome penceresindeki '✅ Kaydı Bitir' butonuyla erken de tamamlayabilirsiniz.</p>
992
+ </div>
993
+ <div class="flex items-center gap-1.5">
994
+ <input id="onboardWaitSeconds" type="number" value="20" min="5" max="120"
995
+ class="w-16 bg-bgCard border border-borderSubtle rounded px-2 py-1 text-xs font-mono text-cyan-400 text-center focus:outline-none focus:border-indigo-500" />
996
+ <span class="text-xs text-gray-400">sn</span>
997
+ </div>
998
+ </div>
999
+
1000
+ <!-- Plaid Tarzı Yasal Onay Checkbox'ı -->
1001
+ <div class="p-3.5 rounded-xl bg-indigo-500/10 border border-indigo-500/30 space-y-1.5">
1002
+ <label for="onboardConsentCheck" class="flex items-start gap-2.5 cursor-pointer select-none">
1003
+ <input id="onboardConsentCheck" type="checkbox" checked
1004
+ class="mt-0.5 rounded bg-bgBase border-indigo-500/50 text-indigo-600 focus:ring-0" />
1005
+ <span class="text-xs text-gray-200 leading-relaxed font-medium">
1006
+ "Bu siteye kendi hesabım ve tarayıcımla vekaleten eriştiğimi onaylıyorum."
1007
+ </span>
1008
+ </label>
1009
+ <div class="pl-6 flex items-center justify-between text-[10px] text-indigo-300/80 font-mono">
1010
+ <span>🛡️ Plaid-Style User Delegation Shield</span>
1011
+ <span>SHA-256 Audit Logged</span>
1012
+ </div>
1013
+ </div>
1014
+
1015
+ <!-- Durum Bilgisi Kutusu -->
1016
+ <div id="onboardStatusBox" class="hidden rounded-lg p-3 bg-bgBase border border-borderSubtle text-xs text-gray-300 font-mono leading-relaxed">
1017
+ </div>
1018
+ </div>
1019
+
1020
+ <!-- Modal Alt Aksiyon Butonları -->
1021
+ <div class="px-6 py-4 border-t border-borderSubtle bg-bgElevated/60 flex items-center justify-end gap-2.5">
1022
+ <button onclick="closeAddSiteModal()"
1023
+ class="px-4 py-2 rounded-lg bg-bgCard border border-borderSubtle text-xs font-medium text-gray-300 hover:text-white transition">
1024
+ İptal
1025
+ </button>
1026
+ <button id="onboardSubmitBtn" onclick="submitOnboardSite()"
1027
+ class="px-5 py-2 rounded-lg bg-gradient-to-r from-indigo-600 to-cyan-600 hover:from-indigo-500 hover:to-cyan-500 text-white font-semibold text-xs shadow-lg shadow-indigo-600/30 transition flex items-center gap-2">
1028
+ <span>🔴</span> Start Recording & Compile
1029
+ </button>
1030
+ </div>
1031
+ </div>
1032
+ </div>
1033
+
1034
+ <!-- ========================================================= -->
1035
+ <!-- MODAL: STRIPE / KART ÖDEME SİMÜLASYONU & BULUTA GEÇİŞ -->
1036
+ <!-- ========================================================= -->
1037
+ <div id="stripeCheckoutModal" class="fixed inset-0 z-50 hidden flex items-center justify-center bg-black/80 backdrop-blur-sm p-4">
1038
+ <div class="glass-card rounded-2xl w-full max-w-md border border-indigo-500/40 shadow-2xl shadow-indigo-950/70 overflow-hidden">
1039
+ <!-- Başlık -->
1040
+ <div class="px-6 py-4 border-b border-borderSubtle bg-bgElevated/90 flex items-center justify-between">
1041
+ <div class="flex items-center gap-2.5">
1042
+ <div class="w-8 h-8 rounded-lg bg-gradient-to-br from-indigo-500 to-cyan-500 flex items-center justify-center text-white font-bold text-sm shadow">
1043
+ 💳
1044
+ </div>
1045
+ <div>
1046
+ <h3 id="checkoutModalTitle" class="text-sm font-bold text-white">OwnAPI Cloud — Hızlı Kart Ödemesi</h3>
1047
+ <p class="text-[11px] text-gray-400">256-bit SSL • Stripe Checkout Simülasyonu</p>
1048
+ </div>
1049
+ </div>
1050
+ <button onclick="closeCheckoutModal()" class="text-gray-400 hover:text-white text-sm px-2 py-1 rounded-lg hover:bg-white/5 transition">
1051
+ ✕
1052
+ </button>
1053
+ </div>
1054
+
1055
+ <!-- Adım 1: Kart Bilgileri Formu -->
1056
+ <div id="checkoutFormView" class="p-6 space-y-4">
1057
+ <div class="p-3.5 rounded-xl bg-indigo-500/10 border border-indigo-500/30 flex items-center justify-between">
1058
+ <div>
1059
+ <div id="checkoutPlanName" class="text-xs font-bold text-white">Başlangıç Bulut Paketi (Starter Cloud)</div>
1060
+ <div id="checkoutPlanCredits" class="text-[11px] text-cyan-300 font-mono">10.000 Bulut Kredisi • mode="cloud"</div>
1061
+ </div>
1062
+ <div id="checkoutPlanPrice" class="text-xl font-extrabold text-white">$10</div>
1063
+ </div>
1064
+
1065
+ <div class="space-y-3 text-xs">
1066
+ <div>
1067
+ <label class="block text-gray-300 font-medium mb-1">Kart Üzerindeki İsim</label>
1068
+ <input id="cardHolderInput" type="text" value="Mert Yılmaz"
1069
+ class="w-full bg-bgBase border border-borderSubtle rounded-lg px-3 py-2 text-white focus:outline-none focus:border-indigo-500" />
1070
+ </div>
1071
+ <div>
1072
+ <label class="block text-gray-300 font-medium mb-1">Kart Numarası (Test Kartı)</label>
1073
+ <input id="cardNumberInput" type="text" value="4242 •••• •••• 4242"
1074
+ class="w-full bg-bgBase border border-borderSubtle rounded-lg px-3 py-2 text-cyan-300 font-mono focus:outline-none focus:border-indigo-500" />
1075
+ </div>
1076
+ <div class="grid grid-cols-2 gap-3">
1077
+ <div>
1078
+ <label class="block text-gray-300 font-medium mb-1">Son Kullanma</label>
1079
+ <input type="text" value="12/28"
1080
+ class="w-full bg-bgBase border border-borderSubtle rounded-lg px-3 py-2 text-white font-mono focus:outline-none focus:border-indigo-500" />
1081
+ </div>
1082
+ <div>
1083
+ <label class="block text-gray-300 font-medium mb-1">CVC</label>
1084
+ <input type="text" value="424"
1085
+ class="w-full bg-bgBase border border-borderSubtle rounded-lg px-3 py-2 text-white font-mono focus:outline-none focus:border-indigo-500" />
1086
+ </div>
1087
+ </div>
1088
+ </div>
1089
+
1090
+ <button id="checkoutPayBtn" onclick="submitBillingUpgrade()"
1091
+ class="w-full py-3 px-4 rounded-xl bg-gradient-to-r from-emerald-600 to-cyan-600 hover:from-emerald-500 hover:to-cyan-500 text-white font-bold text-sm shadow-lg shadow-emerald-600/30 transition flex items-center justify-center gap-2">
1092
+ <span>🔒</span> <span id="checkoutPayBtnText">Öde - $10</span>
1093
+ </button>
1094
+ </div>
1095
+
1096
+ <!-- Adım 2: Ödeme Başarılı — "Tebrikler! Artık buluttasın" Ekranı -->
1097
+ <div id="checkoutSuccessView" class="hidden p-6 space-y-4 text-center">
1098
+ <div class="w-12 h-12 rounded-full bg-emerald-500/20 border border-emerald-500/40 text-emerald-400 flex items-center justify-center text-2xl mx-auto">
1099
+ 🎉
1100
+ </div>
1101
+ <div>
1102
+ <h4 class="text-base font-extrabold text-white">Tebrikler! Artık buluttasın</h4>
1103
+ <p id="checkoutSuccessSubtitle" class="text-xs text-gray-300 mt-1">
1104
+ 10.000 bulut krediniz ve <code class="text-cyan-300 font-mono">mode="cloud"</code> erişiminiz anında tanımlandı.
1105
+ </p>
1106
+ </div>
1107
+
1108
+ <div class="bg-bgBase border border-emerald-500/40 rounded-xl p-3.5 text-left space-y-2">
1109
+ <div class="flex items-center justify-between text-[11px] text-gray-400">
1110
+ <span>Yeni Bulut API Anahtarınız:</span>
1111
+ <button onclick="copyUpgradedKey()" class="text-emerald-400 hover:underline font-semibold">📋 Kopyala</button>
1112
+ </div>
1113
+ <code id="checkoutNewApiKey" class="block text-xs font-mono text-amber-300 break-all select-all">
1114
+ sk-ownapi-...
1115
+ </code>
1116
+ </div>
1117
+
1118
+ <div class="bg-bgBase border border-borderSubtle rounded-xl p-3 text-left">
1119
+ <div class="text-[11px] text-gray-400 mb-1">Bulut Modu Python Kodu:</div>
1120
+ <pre id="checkoutCloudSnippet" class="text-[11px] font-mono text-indigo-200 overflow-x-auto leading-relaxed"></pre>
1121
+ </div>
1122
+
1123
+ <button onclick="closeCheckoutModal()"
1124
+ class="w-full py-2.5 px-4 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-semibold text-xs transition">
1125
+ Harika, Panelime Dön →
1126
+ </button>
1127
+ </div>
1128
+ </div>
1129
+ </div>
1130
+
1131
+ <!-- JAVASCRIPT UYGULAMA MANTIĞI -->
1132
+ <script>
1133
+ let allServices = [];
1134
+ let currentMode = 'local';
1135
+ let currentCodeTab = 'python';
1136
+
1137
+ function showToast(htmlMessage, isError = false) {
1138
+ const toast = document.getElementById('statusToast');
1139
+ const text = document.getElementById('statusToastText');
1140
+ toast.classList.remove('hidden', 'border-indigo-500/40', 'border-rose-500/50');
1141
+ toast.classList.add(isError ? 'border-rose-500/50' : 'border-indigo-500/40');
1142
+ text.innerHTML = htmlMessage;
1143
+ }
1144
+
1145
+ function getTenant() {
1146
+ return (document.getElementById('globalTenantInput').value || 'default').trim();
1147
+ }
1148
+
1149
+ function setExecutionMode(mode) {
1150
+ currentMode = mode;
1151
+ const btnLocal = document.getElementById('modeBtnLocal');
1152
+ const btnCloud = document.getElementById('modeBtnCloud');
1153
+ const kpiMode = document.getElementById('kpiCurrentMode');
1154
+ const kpiDesc = document.getElementById('kpiModeDesc');
1155
+
1156
+ if (mode === 'local') {
1157
+ btnLocal.className = 'px-2.5 py-1 rounded-md font-medium transition flex items-center gap-1.5 bg-indigo-600 text-white shadow';
1158
+ btnCloud.className = 'px-2.5 py-1 rounded-md font-medium transition flex items-center gap-1.5 text-gray-400 hover:text-white';
1159
+ kpiMode.textContent = 'LOCAL CHROME';
1160
+ kpiMode.className = 'text-lg font-bold text-cyan-400';
1161
+ kpiDesc.textContent = 'User-Delegated In-Browser Fetch';
1162
+ } else {
1163
+ btnCloud.className = 'px-2.5 py-1 rounded-md font-medium transition flex items-center gap-1.5 bg-cyan-600 text-white shadow';
1164
+ btnLocal.className = 'px-2.5 py-1 rounded-md font-medium transition flex items-center gap-1.5 text-gray-400 hover:text-white';
1165
+ kpiMode.textContent = 'CLOUD CDP';
1166
+ kpiMode.className = 'text-lg font-bold text-indigo-400';
1167
+ kpiDesc.textContent = 'Remote Docker / Browserless Container';
1168
+ }
1169
+ updateCodeSnippets();
1170
+ }
1171
+
1172
+ function switchTab(tabName) {
1173
+ ['marketplace', 'apikeys', 'billing', 'playground', 'hitl', 'consent'].forEach(t => {
1174
+ const content = document.getElementById(`tabContent-${t}`);
1175
+ const btn = document.getElementById(`tabBtn-${t}`);
1176
+ if (!content || !btn) return;
1177
+ if (t === tabName) {
1178
+ content.classList.remove('hidden');
1179
+ btn.className = 'px-3.5 py-2.5 text-sm font-medium border-b-2 border-indigo-500 text-white transition flex items-center gap-2';
1180
+ } else {
1181
+ content.classList.add('hidden');
1182
+ btn.className = 'px-3.5 py-2.5 text-sm font-medium border-b-2 border-transparent text-gray-400 hover:text-white transition flex items-center gap-2';
1183
+ }
1184
+ });
1185
+ }
1186
+
1187
+ async function loadServices() {
1188
+ const tenant = getTenant();
1189
+ const res = await fetch(`/api/v1/services?tenant_id=${encodeURIComponent(tenant)}`);
1190
+ const data = await res.json();
1191
+ allServices = data.services || [];
1192
+
1193
+ document.getElementById('kpiTotalServices').textContent = data.total_services || 0;
1194
+ document.getElementById('kpiActiveSessions').textContent = `${data.active_sessions_count || 0} aktif TokenStore oturumu`;
1195
+ document.getElementById('kpiValidationRate').textContent = `%${data.validation_rate ?? 100}`;
1196
+ document.getElementById('kpiValidatedCount').textContent = `(${data.validated_count}/${data.total_services} 3x Pass)`;
1197
+ document.getElementById('kpiPendingHitl').textContent = data.pending_hitl_count || 0;
1198
+
1199
+ const hitlBadge = document.getElementById('tabHitlBadge');
1200
+ if ((data.pending_hitl_count || 0) > 0) {
1201
+ hitlBadge.textContent = data.pending_hitl_count;
1202
+ hitlBadge.classList.remove('hidden');
1203
+ } else {
1204
+ hitlBadge.classList.add('hidden');
1205
+ }
1206
+
1207
+ if (data.latest_auto_repair) {
1208
+ const rep = data.latest_auto_repair;
1209
+ document.getElementById('kpiAutoRepairStatus').textContent = `${rep.healthy} Sağlıklı / ${rep.repaired} Onarıldı`;
1210
+ document.getElementById('kpiAutoRepairLast').textContent = `Son: ${(rep.timestamp || '').slice(0, 19).replace('T', ' ')}`;
1211
+ }
1212
+
1213
+ renderMarketplaceCards();
1214
+ populatePlaygroundSelect();
1215
+ }
1216
+
1217
+ function renderMarketplaceCards() {
1218
+ const grid = document.getElementById('marketplaceGrid');
1219
+ const q = (document.getElementById('serviceSearchInput').value || '').toLowerCase().trim();
1220
+ const filtered = allServices.filter(s =>
1221
+ !q ||
1222
+ s.service_name.toLowerCase().includes(q) ||
1223
+ s.function_name.toLowerCase().includes(q) ||
1224
+ (s.explanation || '').toLowerCase().includes(q) ||
1225
+ (s.endpoint_url || '').toLowerCase().includes(q)
1226
+ );
1227
+
1228
+ if (!filtered.length) {
1229
+ grid.innerHTML = `<div class="col-span-full glass-card rounded-xl p-8 text-center text-gray-400 text-sm">Eşleşen servis bulunamadı.</div>`;
1230
+ return;
1231
+ }
1232
+
1233
+ grid.innerHTML = filtered.map(svc => {
1234
+ const isOk = svc.validation_status === 'BAŞARILI';
1235
+ const valBadge = isOk
1236
+ ? `<span class="px-2 py-0.5 text-[10px] font-mono rounded-full bg-emerald-500/15 text-emerald-400 border border-emerald-500/30">✔ 3/3 Auto-Test</span>`
1237
+ : `<span class="px-2 py-0.5 text-[10px] font-mono rounded-full bg-amber-500/15 text-amber-400 border border-amber-500/30">⚠ ${svc.validation_status}</span>`;
1238
+
1239
+ const sessBadge = svc.has_active_session
1240
+ ? `<span class="px-2 py-0.5 text-[10px] rounded-full bg-cyan-500/15 text-cyan-300 border border-cyan-500/30">🍪 Aktif Oturum</span>`
1241
+ : `<span class="px-2 py-0.5 text-[10px] rounded-full bg-gray-800 text-gray-400 border border-gray-700">🍪 Hazır</span>`;
1242
+
1243
+ const tosBadge = svc.has_tos_consent
1244
+ ? `<span class="px-2 py-0.5 text-[10px] rounded-full bg-indigo-500/15 text-indigo-300 border border-indigo-500/30">⚖️ ToS Onaylı</span>`
1245
+ : `<span class="px-2 py-0.5 text-[10px] rounded-full bg-gray-800 text-gray-400">⚖️ İlk Çağrıda Onay</span>`;
1246
+
1247
+ const methodColor = svc.method === 'POST'
1248
+ ? 'bg-amber-500/20 text-amber-300 border-amber-500/30'
1249
+ : 'bg-cyan-500/20 text-cyan-300 border-cyan-500/30';
1250
+
1251
+ const paramsList = (svc.parameters || []).map(p =>
1252
+ `<span class="px-2 py-0.5 rounded bg-bgBase border border-borderSubtle text-[11px] font-mono text-gray-300">${p.name}: <span class="text-indigo-400">${p.type}</span></span>`
1253
+ ).join(' ') || '<span class="text-[11px] text-gray-500 italic">Parametresiz (Statik)</span>';
1254
+
1255
+ return `
1256
+ <div class="glass-card rounded-xl p-5 flex flex-col justify-between space-y-4 transition">
1257
+ <div class="space-y-2.5">
1258
+ <div class="flex items-start justify-between gap-2">
1259
+ <div>
1260
+ <h3 class="text-base font-bold text-white flex items-center gap-2">
1261
+ <span>${svc.service_name}</span>
1262
+ </h3>
1263
+ <p class="text-xs font-mono text-indigo-400">${svc.function_name}()</p>
1264
+ </div>
1265
+ <span class="text-[10px] font-mono font-bold px-2 py-0.5 rounded border ${methodColor}">
1266
+ ${svc.method}
1267
+ </span>
1268
+ </div>
1269
+
1270
+ <div class="flex flex-wrap gap-1.5">
1271
+ ${valBadge}
1272
+ ${sessBadge}
1273
+ ${tosBadge}
1274
+ </div>
1275
+
1276
+ <p class="text-xs text-gray-300 line-clamp-2 leading-relaxed">
1277
+ ${svc.explanation || svc.goal || ''}
1278
+ </p>
1279
+
1280
+ <div class="text-[11px] font-mono text-gray-400 bg-bgBase px-2.5 py-1.5 rounded border border-borderSubtle truncate" title="${svc.endpoint_url}">
1281
+ ${svc.endpoint_url}
1282
+ </div>
1283
+
1284
+ <div class="flex flex-wrap gap-1 pt-1">
1285
+ ${paramsList}
1286
+ </div>
1287
+ </div>
1288
+
1289
+ <div class="grid grid-cols-2 gap-2 pt-3 border-t border-borderSubtle">
1290
+ <button onclick="openInPlayground('${svc.service_name}')"
1291
+ class="py-2 px-3 rounded-lg bg-indigo-600 hover:bg-indigo-500 text-white font-medium text-xs transition flex items-center justify-center gap-1.5">
1292
+ <span>⚡</span> Playground'da Dene
1293
+ </button>
1294
+ <button id="valBtn-${svc.service_name}" onclick="validateService('${svc.service_name}')"
1295
+ class="py-2 px-3 rounded-lg bg-bgElevated hover:bg-gray-800 border border-borderSubtle text-gray-200 font-medium text-xs transition flex items-center justify-center gap-1.5">
1296
+ <span>🛡️</span> 3x Validate
1297
+ </button>
1298
+ </div>
1299
+ </div>
1300
+ `;
1301
+ }).join('');
1302
+ }
1303
+
1304
+ function populatePlaygroundSelect() {
1305
+ const sel = document.getElementById('pgServiceSelect');
1306
+ const currentVal = sel.value;
1307
+ sel.innerHTML = allServices.map(s =>
1308
+ `<option value="${s.service_name}">${s.service_name} — ${s.function_name}() [${s.method}]</option>`
1309
+ ).join('');
1310
+ if (currentVal && allServices.some(s => s.service_name === currentVal)) {
1311
+ sel.value = currentVal;
1312
+ }
1313
+ onPlaygroundServiceChange();
1314
+ }
1315
+
1316
+ function openInPlayground(serviceName) {
1317
+ switchTab('playground');
1318
+ const sel = document.getElementById('pgServiceSelect');
1319
+ sel.value = serviceName;
1320
+ onPlaygroundServiceChange();
1321
+ }
1322
+
1323
+ function onPlaygroundServiceChange() {
1324
+ const sel = document.getElementById('pgServiceSelect');
1325
+ const svc = allServices.find(s => s.service_name === sel.value) || allServices[0];
1326
+ if (!svc) return;
1327
+
1328
+ document.getElementById('pgMethodBadge').textContent = svc.method;
1329
+ document.getElementById('pgEndpointUrl').textContent = `${svc.method} ${svc.endpoint_url}`;
1330
+
1331
+ const container = document.getElementById('pgDynamicParamsContainer');
1332
+ const params = svc.parameters || [];
1333
+ if (!params.length) {
1334
+ container.innerHTML = `<div class="text-xs text-gray-400 bg-bgBase border border-borderSubtle rounded-lg p-3">Bu servis ek dinamik parametre gerektirmiyor (doğrudan çalıştırılabilir).</div>`;
1335
+ } else {
1336
+ container.innerHTML = params.map(p => {
1337
+ const defVal = p.default !== null && p.default !== undefined ? p.default : '';
1338
+ return `
1339
+ <div>
1340
+ <div class="flex items-center justify-between text-xs mb-1">
1341
+ <label class="font-mono font-medium text-indigo-300">${p.name}</label>
1342
+ <span class="text-[10px] font-mono text-gray-500">${p.type} (${p.location})</span>
1343
+ </div>
1344
+ <input type="text" data-param-name="${p.name}" data-param-type="${p.type}" value="${defVal}"
1345
+ oninput="updateCodeSnippets()"
1346
+ placeholder="${p.description || p.name}"
1347
+ class="pg-param-input w-full bg-bgBase border border-borderSubtle rounded-lg px-3 py-1.5 text-xs text-white font-mono focus:outline-none focus:border-indigo-500" />
1348
+ </div>
1349
+ `;
1350
+ }).join('');
1351
+ }
1352
+ updateCodeSnippets();
1353
+ }
1354
+
1355
+ function collectPlaygroundParams() {
1356
+ const inputs = document.querySelectorAll('.pg-param-input');
1357
+ const params = {};
1358
+ inputs.forEach(inp => {
1359
+ const name = inp.getAttribute('data-param-name');
1360
+ const ptype = inp.getAttribute('data-param-type');
1361
+ const raw = inp.value;
1362
+ if (raw === '') return;
1363
+ if (ptype === 'int') {
1364
+ const parsed = parseInt(raw, 10);
1365
+ params[name] = isNaN(parsed) ? raw : parsed;
1366
+ } else if (ptype === 'float') {
1367
+ const parsed = parseFloat(raw);
1368
+ params[name] = isNaN(parsed) ? raw : parsed;
1369
+ } else if (ptype === 'bool') {
1370
+ params[name] = raw.toLowerCase() === 'true';
1371
+ } else {
1372
+ params[name] = raw;
1373
+ }
1374
+ });
1375
+ return params;
1376
+ }
1377
+
1378
+ function switchCodeTab(tab) {
1379
+ currentCodeTab = tab;
1380
+ document.getElementById('codeTabPython').className = tab === 'python'
1381
+ ? 'text-xs font-semibold px-3 py-1 rounded-md bg-indigo-600 text-white'
1382
+ : 'text-xs font-semibold px-3 py-1 rounded-md text-gray-400 hover:text-white';
1383
+ document.getElementById('codeTabCurl').className = tab === 'curl'
1384
+ ? 'text-xs font-semibold px-3 py-1 rounded-md bg-indigo-600 text-white'
1385
+ : 'text-xs font-semibold px-3 py-1 rounded-md text-gray-400 hover:text-white';
1386
+ updateCodeSnippets();
1387
+ }
1388
+
1389
+ function updateCodeSnippets() {
1390
+ const svcName = document.getElementById('pgServiceSelect').value || 'wikipedia';
1391
+ const tenant = getTenant();
1392
+ const nonInteractive = document.getElementById('pgNonInteractive').checked;
1393
+ const params = collectPlaygroundParams();
1394
+ const snippetEl = document.getElementById('pgCodeSnippet');
1395
+
1396
+ if (currentCodeTab === 'python') {
1397
+ const kwParts = Object.entries(params).map(([k, v]) =>
1398
+ `${k}=${typeof v === 'string' ? JSON.stringify(v) : v}`
1399
+ );
1400
+ const kwStr = kwParts.length ? ', ' + kwParts.join(', ') : '';
1401
+ const niStr = nonInteractive ? ', non_interactive=True' : '';
1402
+ snippetEl.textContent =
1403
+ `from own_api import OwnAPIClient
1404
+
1405
+ client = OwnAPIClient(tenant_id="${tenant}", mode="${currentMode}"${niStr})
1406
+ response = client.call("${svcName}"${kwStr})
1407
+ print(response)`;
1408
+ } else {
1409
+ const bodyObj = {
1410
+ service: svcName,
1411
+ params: params,
1412
+ tenant_id: tenant,
1413
+ mode: currentMode,
1414
+ non_interactive: nonInteractive
1415
+ };
1416
+ snippetEl.textContent =
1417
+ `curl -X POST http://localhost:8000/api/v1/call \\
1418
+ -H "Content-Type: application/json" \\
1419
+ -d '${JSON.stringify(bodyObj, null, 2)}'`;
1420
+ }
1421
+ }
1422
+
1423
+ async function executePlaygroundCall() {
1424
+ const svcName = document.getElementById('pgServiceSelect').value;
1425
+ const params = collectPlaygroundParams();
1426
+ const tenant = getTenant();
1427
+ const nonInteractive = document.getElementById('pgNonInteractive').checked;
1428
+
1429
+ const btn = document.getElementById('pgExecuteBtn');
1430
+ const viewer = document.getElementById('pgResponseViewer');
1431
+ const statusBadge = document.getElementById('pgStatusBadge');
1432
+ const timerBadge = document.getElementById('pgTimerBadge');
1433
+
1434
+ btn.disabled = true;
1435
+ btn.innerHTML = `<span>⏳</span> Tarayıcı Tüneli Çalışıyor (${currentMode.toUpperCase()})...`;
1436
+ statusBadge.textContent = 'ÇALIŞIYOR...';
1437
+ statusBadge.className = 'text-[11px] font-mono px-2 py-0.5 rounded bg-amber-500/20 text-amber-300';
1438
+
1439
+ const t0 = performance.now();
1440
+ try {
1441
+ const res = await fetch('/api/v1/call', {
1442
+ method: 'POST',
1443
+ headers: { 'Content-Type': 'application/json' },
1444
+ body: JSON.stringify({
1445
+ service: svcName,
1446
+ params: params,
1447
+ tenant_id: tenant,
1448
+ mode: currentMode,
1449
+ non_interactive: nonInteractive
1450
+ })
1451
+ });
1452
+ const data = await res.json();
1453
+ const elapsed = data.elapsed_ms || Math.round(performance.now() - t0);
1454
+ timerBadge.textContent = `⏱️ ${elapsed} ms`;
1455
+ viewer.textContent = JSON.stringify(data, null, 2);
1456
+
1457
+ if (data.success) {
1458
+ statusBadge.textContent = '200 OK (BAŞARILI)';
1459
+ statusBadge.className = 'text-[11px] font-mono px-2 py-0.5 rounded bg-emerald-500/20 text-emerald-400';
1460
+ } else {
1461
+ statusBadge.textContent = 'HATA / LİMİT';
1462
+ statusBadge.className = 'text-[11px] font-mono px-2 py-0.5 rounded bg-rose-500/20 text-rose-400';
1463
+ if (data.error || data.detail) {
1464
+ showToast(`⚠️ ${data.error || data.detail}`, true);
1465
+ }
1466
+ }
1467
+ await refreshAllData();
1468
+ } catch (err) {
1469
+ statusBadge.textContent = 'BAĞLANTI HATASI';
1470
+ statusBadge.className = 'text-[11px] font-mono px-2 py-0.5 rounded bg-rose-500/20 text-rose-400';
1471
+ viewer.textContent = JSON.stringify({ error: String(err) }, null, 2);
1472
+ } finally {
1473
+ btn.disabled = false;
1474
+ btn.innerHTML = `<span>🚀</span> Şimdi Çalıştır (Execute In-Browser)`;
1475
+ }
1476
+ }
1477
+
1478
+ function setAgentPrompt(text) {
1479
+ document.getElementById('pgAgentPrompt').value = text;
1480
+ }
1481
+
1482
+ async function executeAgentAsk() {
1483
+ const prompt = document.getElementById('pgAgentPrompt').value.trim();
1484
+ if (!prompt) return;
1485
+
1486
+ const btn = document.getElementById('pgAskBtn');
1487
+ const viewer = document.getElementById('pgResponseViewer');
1488
+ const statusBadge = document.getElementById('pgStatusBadge');
1489
+ const timerBadge = document.getElementById('pgTimerBadge');
1490
+
1491
+ btn.disabled = true;
1492
+ btn.innerHTML = `<span>⏳</span> AI Agent Araçları Çalıştırıyor...`;
1493
+ statusBadge.textContent = 'AI AGENT ÇALIŞIYOR...';
1494
+ statusBadge.className = 'text-[11px] font-mono px-2 py-0.5 rounded bg-amber-500/20 text-amber-300';
1495
+
1496
+ try {
1497
+ const res = await fetch('/api/v1/ask', {
1498
+ method: 'POST',
1499
+ headers: { 'Content-Type': 'application/json' },
1500
+ body: JSON.stringify({
1501
+ prompt: prompt,
1502
+ tenant_id: getTenant(),
1503
+ mode: currentMode
1504
+ })
1505
+ });
1506
+ const data = await res.json();
1507
+ timerBadge.textContent = `⏱️ ${data.elapsed_ms || 0} ms`;
1508
+ viewer.textContent = JSON.stringify(data, null, 2);
1509
+ statusBadge.textContent = data.success ? 'AI YANITI HAZIR' : 'AJAN HATASI';
1510
+ statusBadge.className = data.success
1511
+ ? 'text-[11px] font-mono px-2 py-0.5 rounded bg-emerald-500/20 text-emerald-400'
1512
+ : 'text-[11px] font-mono px-2 py-0.5 rounded bg-rose-500/20 text-rose-400';
1513
+ if (!data.success && (data.error || data.detail)) {
1514
+ showToast(`⚠️ ${data.error || data.detail}`, true);
1515
+ }
1516
+ await loadAccountInfo();
1517
+ } catch (err) {
1518
+ viewer.textContent = JSON.stringify({ error: String(err) }, null, 2);
1519
+ } finally {
1520
+ btn.disabled = false;
1521
+ btn.innerHTML = `<span>✨</span> AI Ajanına Sor (\`POST /api/v1/ask\`)`;
1522
+ }
1523
+ }
1524
+
1525
+ async function validateService(serviceName) {
1526
+ const btn = document.getElementById(`valBtn-${serviceName}`);
1527
+ if (btn) {
1528
+ btn.disabled = true;
1529
+ btn.innerHTML = `<span>⏳</span> 3x Test...`;
1530
+ }
1531
+ showToast(`🧪 <b>${serviceName}</b> servisi için 3x Auto-Test (Compiler + Validator Loop) başlatıldı...`);
1532
+
1533
+ try {
1534
+ const res = await fetch(`/api/v1/validate/${encodeURIComponent(serviceName)}`, {
1535
+ method: 'POST',
1536
+ headers: { 'Content-Type': 'application/json' },
1537
+ body: JSON.stringify({ runs: 3, tenant_id: getTenant(), mode: currentMode })
1538
+ });
1539
+ const data = await res.json();
1540
+ if (data.validation_status === 'BAŞARILI') {
1541
+ showToast(`✅ <b>${serviceName}</b> 3/3 Auto-Test doğrulamasından başarıyla geçti!`);
1542
+ } else {
1543
+ showToast(`⚠️ <b>${serviceName}</b> doğrulama durumu: ${data.validation_status}`, true);
1544
+ }
1545
+ await refreshAllData();
1546
+ } catch (err) {
1547
+ showToast(`✖ Doğrulama hatası: ${err}`, true);
1548
+ } finally {
1549
+ if (btn) {
1550
+ btn.disabled = false;
1551
+ btn.innerHTML = `<span>🛡️</span> 3x Validate`;
1552
+ }
1553
+ }
1554
+ }
1555
+
1556
+ async function triggerAutoRepair() {
1557
+ const btn = document.getElementById('btnRunAutoRepair');
1558
+ btn.disabled = true;
1559
+ btn.innerHTML = `<span>⏳</span> Tüm Servisler Taranıyor & Onarılıyor...`;
1560
+ showToast(`🛠️ <b>Self-Healing 2.0</b> tüm servisleri tarıyor ve gerekirse otomatik onarıyor...`);
1561
+
1562
+ try {
1563
+ const res = await fetch('/api/v1/auto-repair', {
1564
+ method: 'POST',
1565
+ headers: { 'Content-Type': 'application/json' },
1566
+ body: JSON.stringify({
1567
+ runs: 1,
1568
+ auto_heal: true,
1569
+ tenant_id: getTenant(),
1570
+ mode: currentMode
1571
+ })
1572
+ });
1573
+ const data = await res.json();
1574
+ showToast(`✅ Self-Healing 2.0 Tamamlandı! Sağlıklı: <b>${data.healthy}</b> | Onarılan: <b>${data.repaired}</b> | HITL Bekleyen: <b>${data.pending_hitl}</b>`);
1575
+ await refreshAllData();
1576
+ } catch (err) {
1577
+ showToast(`✖ Auto-Repair hatası: ${err}`, true);
1578
+ } finally {
1579
+ btn.disabled = false;
1580
+ btn.innerHTML = `<span>🔧</span> Tüm Servisleri Şimdi Tara & Onar (Auto-Repair)`;
1581
+ }
1582
+ }
1583
+
1584
+ const PENDING_HITL_SET = new Set(['PAUSED', 'PENDING_USER_ACTION', 'PENDING_USER_APPROVAL', 'APPROVED_TO_RESUME', 'RESUMING']);
1585
+ let lastHitlSignature = '';
1586
+
1587
+ function renderHitlTasksUI(data) {
1588
+ const tasks = data.tasks || [];
1589
+ const pendingTasks = data.pending_tasks || tasks.filter(t => PENDING_HITL_SET.has(String(t.status || '').toUpperCase()));
1590
+ const pendingCount = data.pending_count ?? pendingTasks.length;
1591
+
1592
+ const kpiEl = document.getElementById('kpiPendingHitl');
1593
+ if (kpiEl) kpiEl.textContent = pendingCount;
1594
+
1595
+ const hitlBadge = document.getElementById('tabHitlBadge');
1596
+ if (hitlBadge) {
1597
+ if (pendingCount > 0) {
1598
+ hitlBadge.textContent = pendingCount;
1599
+ hitlBadge.classList.remove('hidden');
1600
+ } else {
1601
+ hitlBadge.classList.add('hidden');
1602
+ }
1603
+ }
1604
+
1605
+ // Üst canlı HITL onay bandını güncelle (F5 sonrasında hangi sekmede olunursa olunsun anında görünür)
1606
+ const banner = document.getElementById('liveHitlBanner');
1607
+ const bannerCount = document.getElementById('liveHitlBannerCount');
1608
+ const bannerList = document.getElementById('liveHitlBannerList');
1609
+ if (banner && bannerList) {
1610
+ if (pendingTasks.length > 0) {
1611
+ banner.classList.remove('hidden');
1612
+ if (bannerCount) bannerCount.textContent = `${pendingTasks.length} PAUSED`;
1613
+ bannerList.innerHTML = pendingTasks.map(t => `
1614
+ <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-2 bg-bgBase/90 border border-amber-500/30 rounded-lg px-3.5 py-2.5 text-xs">
1615
+ <div class="space-y-0.5">
1616
+ <div class="flex items-center gap-2 flex-wrap">
1617
+ <span class="font-mono font-bold text-cyan-400">${t.task_id}</span>
1618
+ <span class="px-2 py-0.5 rounded bg-amber-500/20 text-amber-300 font-mono text-[10px]">⏸️ ${t.status || 'PAUSED'}</span>
1619
+ <span class="font-semibold text-white">${t.service}</span>
1620
+ <span class="text-gray-400">(${t.tenant_id || 'default'})</span>
1621
+ </div>
1622
+ <div class="text-amber-200/90 text-[11px]">${t.reason || 'WAF / Captcha doğrulaması bekleniyor'} — <span class="font-mono text-gray-400">${t.method} ${t.url}</span></div>
1623
+ </div>
1624
+ <button onclick="approveHitlTask('${t.task_id}')"
1625
+ class="px-3.5 py-1.5 rounded-lg bg-emerald-600 hover:bg-emerald-500 text-white font-semibold text-xs transition shadow whitespace-nowrap flex items-center gap-1.5">
1626
+ <span>▶️</span> Onayla & Kaldığı Yerden Devam Et (Resume)
1627
+ </button>
1628
+ </div>
1629
+ `).join('');
1630
+ } else {
1631
+ banner.classList.add('hidden');
1632
+ bannerList.innerHTML = '';
1633
+ }
1634
+ }
1635
+
1636
+ const tbody = document.getElementById('hitlTableBody');
1637
+ if (!tbody) return;
1638
+
1639
+ // Bulut (Cloud) HITL Uzaktan Çözüm Önizleme Kartlarını Güncelle
1640
+ const previewContainer = document.getElementById('hitlCloudPreviewCards');
1641
+ if (previewContainer) {
1642
+ const previewCandidates = pendingTasks.filter(t => {
1643
+ const em = String(t.execution_mode || t.mode || '').toLowerCase();
1644
+ return em === 'cloud' || Boolean(t.screenshot_b64) || Boolean(t.cloud_preview_url) || pendingTasks.length > 0;
1645
+ });
1646
+
1647
+ if (!previewCandidates.length) {
1648
+ previewContainer.innerHTML = `
1649
+ <div class="rounded-xl bg-bgBase/80 border border-borderSubtle p-4 text-xs text-gray-400 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
1650
+ <div>
1651
+ <div class="font-semibold text-gray-200">☁️ Uzak Bulut Tarayıcı Ekranı (Canlı Önizleme) — Bekleyen Görev Yok</div>
1652
+ <div class="text-[11px] text-gray-500 mt-0.5">Yeni bir HITL görevi duraklatıldığında uzak tarayıcı önizlemesi otomatik olarak burada görüntülenir.</div>
1653
+ </div>
1654
+ <span class="px-3 py-1.5 rounded-lg bg-gray-800 text-gray-400 border border-gray-700 font-mono text-[11px]">
1655
+ 0 Aktif Önizleme
1656
+ </span>
1657
+ </div>
1658
+ `;
1659
+ } else {
1660
+ previewContainer.innerHTML = previewCandidates.map(t => {
1661
+ const execMode = String(t.execution_mode || t.mode || 'local').toUpperCase();
1662
+ const isCloudTask = execMode === 'CLOUD';
1663
+ const modeBadge = isCloudTask
1664
+ ? `<span class="px-2 py-0.5 rounded bg-cyan-500/20 text-cyan-300 border border-cyan-500/40 font-mono text-[10px]">☁️ CLOUD MODE</span>`
1665
+ : `<span class="px-2 py-0.5 rounded bg-indigo-500/20 text-indigo-300 border border-indigo-500/40 font-mono text-[10px]">💻 ${execMode} MODE</span>`;
1666
+ const targetLink = t.target_url || t.url || '';
1667
+ const previewEndpoint = t.cloud_preview_url || `/api/hitl/preview/${encodeURIComponent(t.task_id)}`;
1668
+
1669
+ return `
1670
+ <div id="hitlPreviewCard-${t.task_id}" class="rounded-xl bg-bgBase/95 border border-cyan-500/40 p-4 space-y-3 shadow-lg shadow-cyan-950/20">
1671
+ <div class="flex flex-col md:flex-row md:items-center justify-between gap-2 border-b border-borderSubtle pb-3">
1672
+ <div class="space-y-1">
1673
+ <div class="flex items-center gap-2 flex-wrap">
1674
+ <span class="text-xs font-bold text-cyan-300">☁️ Uzak Bulut Tarayıcı Ekranı (Canlı Önizleme)</span>
1675
+ <span class="font-mono font-bold text-white text-xs">${t.task_id}</span>
1676
+ ${modeBadge}
1677
+ <span class="px-2 py-0.5 rounded bg-amber-500/20 text-amber-300 font-mono text-[10px]">⏸️ ${t.status || 'PAUSED'}</span>
1678
+ </div>
1679
+ <div class="text-[11px] text-gray-300">
1680
+ Servis: <b class="text-white">${t.service}</b> (${t.tenant_id || 'default'}) • Neden: <span class="text-amber-300">${t.reason || 'Captcha / WAF doğrulaması'}</span>
1681
+ </div>
1682
+ </div>
1683
+ <div class="flex items-center gap-2">
1684
+ <button onclick="refreshSingleHitlPreview('${t.task_id}')"
1685
+ class="px-3 py-1.5 rounded-lg bg-bgElevated hover:bg-gray-800 border border-borderSubtle text-cyan-300 text-xs font-medium transition">
1686
+ 🔄 Ekranı Yenile
1687
+ </button>
1688
+ <button onclick="approveHitlTask('${t.task_id}')"
1689
+ class="px-4 py-2 rounded-lg bg-gradient-to-r from-emerald-600 to-cyan-600 hover:from-emerald-500 hover:to-cyan-500 text-white font-bold text-xs shadow-lg shadow-emerald-600/25 transition flex items-center gap-1.5">
1690
+ <span>✅</span> Doğrulamayı Tamamla & Devam Et (Resume)
1691
+ </button>
1692
+ </div>
1693
+ </div>
1694
+
1695
+ <!-- Uzak Tarayıcı Adres Çubuğu & Canlı Ekran Çerçevesi -->
1696
+ <div class="rounded-lg border border-borderSubtle bg-bgCard overflow-hidden">
1697
+ <div class="px-3 py-1.5 bg-bgElevated border-b border-borderSubtle flex items-center justify-between gap-2 text-[11px] font-mono">
1698
+ <div class="flex items-center gap-2 truncate">
1699
+ <span class="inline-block w-2 h-2 rounded-full bg-emerald-400"></span>
1700
+ <span class="text-gray-400">URL:</span>
1701
+ <a href="${targetLink}" target="_blank" class="text-cyan-400 hover:underline truncate">${targetLink}</a>
1702
+ </div>
1703
+ <span class="text-gray-500 shrink-0">${previewEndpoint}</span>
1704
+ </div>
1705
+ <div class="p-3 flex flex-col items-center justify-center bg-black/40 min-h-[180px]">
1706
+ <img id="hitlPreviewImg-${t.task_id}"
1707
+ alt="Uzak Bulut Tarayıcı Ekranı (${t.task_id})"
1708
+ class="max-h-64 w-auto rounded-lg border border-borderSubtle shadow-md object-contain" />
1709
+ <div id="hitlPreviewMeta-${t.task_id}" class="mt-2 text-[11px] font-mono text-gray-400">
1710
+ Canlı bulut ekran görüntüsü yükleniyor...
1711
+ </div>
1712
+ </div>
1713
+
1714
+ <!-- Opsiyonel Uzaktan SMS / E-Posta (OTP) Doğrulama Kodu Giriş Alanı -->
1715
+ <div class="px-4 py-3 bg-bgElevated/90 border-t border-borderSubtle flex flex-col sm:flex-row sm:items-center justify-between gap-3">
1716
+ <label for="hitlOtpInput-${t.task_id}" class="text-xs font-semibold text-amber-300 flex items-center gap-2">
1717
+ <span>🔑 SMS / E-Posta Onay Kodu (Varsa):</span>
1718
+ </label>
1719
+ <div class="flex items-center gap-2 flex-1 sm:max-w-md justify-end">
1720
+ <input id="hitlOtpInput-${t.task_id}" type="text" maxlength="12"
1721
+ placeholder="123456"
1722
+ value="${t.otp_code || ''}"
1723
+ class="w-36 sm:w-44 bg-bgBase border border-amber-500/40 rounded-lg px-3 py-1.5 text-xs font-mono font-bold text-amber-200 tracking-widest placeholder-gray-500 focus:outline-none focus:border-amber-400 text-center" />
1724
+ <button onclick="approveHitlTask('${t.task_id}')"
1725
+ class="px-3.5 py-1.5 rounded-lg bg-gradient-to-r from-emerald-600 to-cyan-600 hover:from-emerald-500 hover:to-cyan-500 text-white font-bold text-xs shadow transition whitespace-nowrap">
1726
+ Doğrulamayı Tamamla & Devam Et
1727
+ </button>
1728
+ </div>
1729
+ </div>
1730
+ </div>
1731
+ </div>
1732
+ `;
1733
+ }).join('');
1734
+
1735
+ previewCandidates.forEach(t => {
1736
+ refreshSingleHitlPreview(t.task_id);
1737
+ });
1738
+ }
1739
+ }
1740
+
1741
+ if (!tasks.length) {
1742
+ tbody.innerHTML = `<tr><td colspan="7" class="py-6 text-center text-gray-500">HITL kuyruğunda (.sessions/hitl_state.json) bekleyen veya tamamlanan görev bulunmuyor.</td></tr>`;
1743
+ return;
1744
+ }
1745
+
1746
+ tbody.innerHTML = tasks.map(t => {
1747
+ const st = String(t.status || 'PAUSED').toUpperCase();
1748
+ const isPending = PENDING_HITL_SET.has(st);
1749
+ const execMode = String(t.execution_mode || t.mode || 'local').toUpperCase();
1750
+ const badge = isPending
1751
+ ? `<span class="px-2 py-0.5 rounded-full bg-amber-500/20 text-amber-300 border border-amber-500/40 font-mono text-[10px]">⏸️ ${st} (${execMode})</span>`
1752
+ : `<span class="px-2 py-0.5 rounded-full bg-emerald-500/20 text-emerald-400 border border-emerald-500/30 font-mono text-[10px]">✔ COMPLETED</span>`;
1753
+
1754
+ const actionBtn = isPending
1755
+ ? `<div class="flex items-center justify-end gap-1.5">
1756
+ <button onclick="refreshSingleHitlPreview('${t.task_id}')" class="px-2.5 py-1.5 rounded-lg bg-cyan-500/15 hover:bg-cyan-500/25 border border-cyan-500/30 text-cyan-300 font-medium text-[11px] transition">👁️ Önizleme</button>
1757
+ <button onclick="approveHitlTask('${t.task_id}')" class="px-3 py-1.5 rounded-lg bg-emerald-600 hover:bg-emerald-500 text-white font-semibold text-xs transition shadow">▶️ Doğrulamayı Tamamla & Devam Et (Resume)</button>
1758
+ </div>`
1759
+ : `<span class="text-emerald-400/80 font-mono text-[11px]">✔ Kaldığı Yerden Tamamlandı</span>`;
1760
+
1761
+ return `
1762
+ <tr class="hover:bg-bgElevated/50 transition">
1763
+ <td class="py-3 px-3 font-mono font-semibold text-cyan-400">${t.task_id}</td>
1764
+ <td class="py-3 px-3"><span class="font-semibold text-white">${t.service}</span> <span class="text-gray-400">(${t.tenant_id})</span></td>
1765
+ <td class="py-3 px-3 font-mono text-[11px] text-gray-300 max-w-xs truncate" title="${t.url}">${t.method} ${t.url}</td>
1766
+ <td class="py-3 px-3 text-amber-300">${t.reason || '-'}</td>
1767
+ <td class="py-3 px-3">${badge}</td>
1768
+ <td class="py-3 px-3 text-gray-400 font-mono text-[11px]">${(t.created_at || '').slice(0, 19).replace('T', ' ')}</td>
1769
+ <td class="py-3 px-3 text-right">${actionBtn}</td>
1770
+ </tr>
1771
+ `;
1772
+ }).join('');
1773
+ }
1774
+
1775
+ async function refreshSingleHitlPreview(taskId) {
1776
+ try {
1777
+ const res = await fetch(`/api/hitl/preview/${encodeURIComponent(taskId)}`, { cache: 'no-store' });
1778
+ if (!res.ok) return;
1779
+ const data = await res.json();
1780
+ const imgEl = document.getElementById(`hitlPreviewImg-${taskId}`);
1781
+ const metaEl = document.getElementById(`hitlPreviewMeta-${taskId}`);
1782
+ if (imgEl && data.screenshot_data_url) {
1783
+ imgEl.src = data.screenshot_data_url;
1784
+ }
1785
+ if (metaEl) {
1786
+ metaEl.textContent = `Mod: ${String(data.execution_mode || 'local').toUpperCase()} | Durum: ${data.status} | Son Güncelleme: ${(data.updated_at || '').slice(0, 19).replace('T', ' ')} UTC`;
1787
+ }
1788
+ } catch (err) {
1789
+ console.warn('HITL önizleme yüklenemedi:', err);
1790
+ }
1791
+ }
1792
+
1793
+ async function loadHitlQueue() {
1794
+ try {
1795
+ const res = await fetch('/api/hitl/pending', { cache: 'no-store' });
1796
+ const data = await res.json();
1797
+ renderHitlTasksUI(data);
1798
+ } catch (err) {
1799
+ console.warn('HITL state okuma uyarısı:', err);
1800
+ }
1801
+ }
1802
+
1803
+ async function pollHitlPending() {
1804
+ try {
1805
+ const res = await fetch('/api/hitl/pending', { cache: 'no-store' });
1806
+ if (!res.ok) return;
1807
+ const data = await res.json();
1808
+ const sig = JSON.stringify((data.tasks || []).map(t => `${t.task_id}:${t.status}:${t.updated_at}`));
1809
+ if (sig !== lastHitlSignature) {
1810
+ lastHitlSignature = sig;
1811
+ renderHitlTasksUI(data);
1812
+ }
1813
+ } catch (_) {}
1814
+ }
1815
+
1816
+ async function approveHitlTask(taskId) {
1817
+ const otpInputEl = document.getElementById(`hitlOtpInput-${taskId}`);
1818
+ const otpCode = otpInputEl ? (otpInputEl.value || '').trim() : '';
1819
+ const otpNotice = otpCode ? ` (OTP Kodu: <b>${otpCode}</b>)` : '';
1820
+ showToast(`▶️ <b>${taskId}</b> görevi için onay sinyali gönderildi${otpNotice}. Duraklatılan ajan kaldığı yerden ('resume') devam ettiriliyor...`);
1821
+ try {
1822
+ const res = await fetch(`/api/hitl/resolve/${encodeURIComponent(taskId)}`, {
1823
+ method: 'POST',
1824
+ headers: { 'Content-Type': 'application/json' },
1825
+ body: JSON.stringify({
1826
+ otp_code: otpCode || null,
1827
+ tenant_id: getTenant()
1828
+ })
1829
+ });
1830
+ const data = await res.json();
1831
+ if (res.ok && data.success) {
1832
+ showToast(`✅ Görev <b>${taskId}</b>${otpNotice} başarıyla onaylandı ve kaldığı yerden tamamlandı (COMPLETED)!`);
1833
+ } else {
1834
+ showToast(`✖ Görev onaylama hatası: ${data.detail || data.error}`, true);
1835
+ }
1836
+ lastHitlSignature = '';
1837
+ await refreshAllData();
1838
+ } catch (err) {
1839
+ showToast(`✖ Hata: ${err}`, true);
1840
+ }
1841
+ }
1842
+
1843
+ async function loadAutoRepairReport() {
1844
+ const res = await fetch('/api/v1/auto-repair/report');
1845
+ const data = await res.json();
1846
+ const tbody = document.getElementById('autoRepairTableBody');
1847
+ const history = data.history || [];
1848
+
1849
+ if (!history.length) {
1850
+ tbody.innerHTML = `<tr><td colspan="6" class="py-6 text-center text-gray-500">Henüz kayıtlı bir Self-Healing 2.0 raporu bulunmuyor.</td></tr>`;
1851
+ return;
1852
+ }
1853
+
1854
+ const latest = history[history.length - 1];
1855
+ document.getElementById('autoRepairTimestamp').textContent =
1856
+ `Son Çalışma: ${(latest.timestamp || '').slice(0, 19).replace('T', ' ')} UTC | Toplam Tarama Geçmişi: ${data.total_runs} döngü`;
1857
+
1858
+ const results = latest.results || [];
1859
+ tbody.innerHTML = results.map(r => {
1860
+ const initBadge = r.initial_status === 'BAŞARILI'
1861
+ ? `<span class="text-emerald-400 font-semibold">🟢 BAŞARILI</span>`
1862
+ : `<span class="text-rose-400 font-semibold">🔴 ${r.initial_status}</span>`;
1863
+ const finBadge = r.final_status === 'BAŞARILI'
1864
+ ? `<span class="text-emerald-400 font-semibold">🟢 BAŞARILI</span>`
1865
+ : `<span class="text-amber-400 font-semibold">⏳ ${r.final_status}</span>`;
1866
+ const healedStr = r.auto_healed
1867
+ ? `<span class="px-2 py-0.5 rounded bg-cyan-500/20 text-cyan-300 font-mono text-[10px]">🛠️ Onarıldı (3/3)</span>`
1868
+ : `<span class="text-gray-500">-</span>`;
1869
+
1870
+ return `
1871
+ <tr class="hover:bg-bgElevated/50">
1872
+ <td class="py-2.5 px-3 font-semibold text-white">${r.service_name}</td>
1873
+ <td class="py-2.5 px-3">${initBadge}</td>
1874
+ <td class="py-2.5 px-3">${finBadge}</td>
1875
+ <td class="py-2.5 px-3">${healedStr}</td>
1876
+ <td class="py-2.5 px-3 font-mono text-cyan-400">${r.elapsed_ms || 0} ms</td>
1877
+ <td class="py-2.5 px-3 text-gray-300 max-w-md truncate">${r.detail || '-'}</td>
1878
+ </tr>
1879
+ `;
1880
+ }).join('');
1881
+ }
1882
+
1883
+ async function loadConsentAudit() {
1884
+ const tenant = getTenant();
1885
+ const res = await fetch(`/api/v1/consent?tenant_id=${encodeURIComponent(tenant)}`);
1886
+ const data = await res.json();
1887
+
1888
+ document.getElementById('tosVersionBadge').textContent = data.tos_version || 'v1.0-Enterprise';
1889
+ document.getElementById('tosDisclaimerText').textContent = data.disclaimer || '';
1890
+
1891
+ const tbody = document.getElementById('consentTableBody');
1892
+ const consents = data.consents || [];
1893
+ if (!consents.length) {
1894
+ tbody.innerHTML = `<tr><td colspan="7" class="py-6 text-center text-gray-500">Bu kiracı için henüz kaydedilmiş bir ToS onay kaydı bulunmuyor.</td></tr>`;
1895
+ return;
1896
+ }
1897
+
1898
+ tbody.innerHTML = consents.map((c, idx) => `
1899
+ <tr class="hover:bg-bgElevated/50">
1900
+ <td class="py-2.5 px-3 text-gray-500 font-mono">${idx + 1}</td>
1901
+ <td class="py-2.5 px-3 font-semibold text-indigo-400">${c.service}</td>
1902
+ <td class="py-2.5 px-3 text-amber-300 font-mono text-[11px]">${c.action}</td>
1903
+ <td class="py-2.5 px-3"><span class="px-2 py-0.5 rounded bg-cyan-500/15 text-cyan-300 font-mono text-[10px]">${(c.execution_mode || 'local').toUpperCase()}</span></td>
1904
+ <td class="py-2.5 px-3 text-gray-200 font-mono">${c.delegated_by || '-'}</td>
1905
+ <td class="py-2.5 px-3 font-mono text-emerald-400">${c.disclaimer_sha256}</td>
1906
+ <td class="py-2.5 px-3 text-gray-400 font-mono text-[11px]">${(c.accepted_at || '').slice(0, 19).replace('T', ' ')}</td>
1907
+ </tr>
1908
+ `).join('');
1909
+ }
1910
+
1911
+ function copyResponseJson() {
1912
+ const text = document.getElementById('pgResponseViewer').textContent;
1913
+ navigator.clipboard.writeText(text);
1914
+ showToast('📋 Canlı JSON yanıtı panoya kopyalandı.');
1915
+ }
1916
+
1917
+ function copyCodeSnippet() {
1918
+ const text = document.getElementById('pgCodeSnippet').textContent;
1919
+ navigator.clipboard.writeText(text);
1920
+ showToast('📋 Kod örneği panoya kopyalandı.');
1921
+ }
1922
+
1923
+ function openAddSiteModal() {
1924
+ const modal = document.getElementById('addSiteModal');
1925
+ const statusBox = document.getElementById('onboardStatusBox');
1926
+ statusBox.classList.add('hidden');
1927
+ statusBox.innerHTML = '';
1928
+ modal.classList.remove('hidden');
1929
+ setTimeout(() => document.getElementById('onboardServiceName').focus(), 80);
1930
+ }
1931
+
1932
+ function closeAddSiteModal() {
1933
+ document.getElementById('addSiteModal').classList.add('hidden');
1934
+ }
1935
+
1936
+ async function submitOnboardSite() {
1937
+ const serviceName = (document.getElementById('onboardServiceName').value || '').trim();
1938
+ const targetUrl = (document.getElementById('onboardTargetUrl').value || '').trim();
1939
+ const description = (document.getElementById('onboardDescription').value || '').trim();
1940
+ const waitSeconds = parseInt(document.getElementById('onboardWaitSeconds').value || '20', 10) || 20;
1941
+ const consentAccepted = document.getElementById('onboardConsentCheck').checked;
1942
+ const statusBox = document.getElementById('onboardStatusBox');
1943
+ const btn = document.getElementById('onboardSubmitBtn');
1944
+
1945
+ if (!serviceName || !targetUrl) {
1946
+ statusBox.classList.remove('hidden');
1947
+ statusBox.innerHTML = `<span class="text-rose-400">✖ Lütfen Site Adı (service_name) ve Hedef URL (target_url) alanlarını doldurun.</span>`;
1948
+ return;
1949
+ }
1950
+
1951
+ if (!consentAccepted) {
1952
+ statusBox.classList.remove('hidden');
1953
+ statusBox.innerHTML = `<span class="text-amber-400">⚠️ Devam etmek için Plaid tarzı Yasal Onay kutucuğunu işaretlemelisiniz.</span>`;
1954
+ return;
1955
+ }
1956
+
1957
+ btn.disabled = true;
1958
+ btn.innerHTML = `<span class="animate-spin">⏳</span> Recording & Compiling (GPT-4o + 3x Test)...`;
1959
+ statusBox.classList.remove('hidden');
1960
+ statusBox.innerHTML = `
1961
+ <div class="text-cyan-400 font-semibold">🚀 1/3 Tarayıcı açılıyor: ${targetUrl}</div>
1962
+ <div class="text-gray-400 mt-1">• Açılan Chrome penceresinde örnek işlemi yapın ve sağ üstteki <b>"✅ Kaydı Bitir & Derle"</b> butonuna basın (veya ${waitSeconds} sn bekleyin).</div>
1963
+ <div class="text-gray-400">• Ardından GPT-4o <code class="text-indigo-400">client.py</code> dosyasını üretecek ve 3x Auto-Test doğrulamasından geçirecektir.</div>
1964
+ `;
1965
+
1966
+ try {
1967
+ const res = await fetch('/api/v1/onboard', {
1968
+ method: 'POST',
1969
+ headers: { 'Content-Type': 'application/json' },
1970
+ body: JSON.stringify({
1971
+ service_name: serviceName,
1972
+ target_url: targetUrl,
1973
+ description: description,
1974
+ consent_accepted: consentAccepted,
1975
+ tenant_id: getTenant(),
1976
+ mode: currentMode,
1977
+ wait_seconds: waitSeconds,
1978
+ validation_runs: 3
1979
+ })
1980
+ });
1981
+ const data = await res.json();
1982
+
1983
+ if (res.ok && data.success) {
1984
+ statusBox.innerHTML = `
1985
+ <div class="text-emerald-400 font-bold">✅ Servis Başarıyla Eklendi ve Doğrulandı!</div>
1986
+ <div class="text-gray-300 mt-1">• Servis: <b>${data.service_name}</b> | Fonksiyon: <b>${data.function_name}()</b></div>
1987
+ <div class="text-gray-300">• Endpoint: ${data.method} ${data.endpoint_url}</div>
1988
+ <div class="text-emerald-300">• 3x Auto-Test Durumu: <b>${data.validation_status}</b> (${data.elapsed_ms} ms)</div>
1989
+ `;
1990
+ showToast(`🎉 <b>${data.service_name}</b> başarıyla derlendi, 3x Auto-Test'ten geçti ve Registry Marketplace'e eklendi!`);
1991
+ await refreshAllData();
1992
+ setTimeout(() => {
1993
+ closeAddSiteModal();
1994
+ openInPlayground(data.service_name);
1995
+ }, 1400);
1996
+ } else {
1997
+ const errMsg = data.detail || data.error || 'Bilinmeyen derleme hatası';
1998
+ statusBox.innerHTML = `<span class="text-rose-400">✖ Onboarding Hatası: ${errMsg}</span>`;
1999
+ showToast(`✖ Onboarding başarısız: ${errMsg}`, true);
2000
+ }
2001
+ } catch (err) {
2002
+ statusBox.innerHTML = `<span class="text-rose-400">✖ Sunucu bağlantı hatası: ${err}</span>`;
2003
+ showToast(`✖ Hata: ${err}`, true);
2004
+ } finally {
2005
+ btn.disabled = false;
2006
+ btn.innerHTML = `<span>🔴</span> Start Recording & Compile`;
2007
+ }
2008
+ }
2009
+
2010
+ let currentAccount = null;
2011
+ let isApiKeyVisible = false;
2012
+ let selectedQuickstartService = 'thy';
2013
+ let selectedUpgradePlan = 'starter_cloud';
2014
+ let selectedUpgradePrice = 10;
2015
+
2016
+ const QUICKSTART_TEMPLATES = {
2017
+ thy: {
2018
+ fnCall: `thy.search_flights(origin_airport_code="IST", destination_airport_code="ESB", departure_date="02-03-2027", return_date="09-03-2027")`,
2019
+ cloudCall: `api.thy.search_flights(origin_airport_code="IST", destination_airport_code="ESB", departure_date="02-03-2027", return_date="09-03-2027")`,
2020
+ params: { origin_airport_code: "IST", destination_airport_code: "ESB", departure_date: "02-03-2027", return_date: "09-03-2027" }
2021
+ },
2022
+ trendyol: {
2023
+ fnCall: `trendyol.search(query="laptop", max_results=5)`,
2024
+ cloudCall: `api.trendyol.search(query="laptop", max_results=5)`,
2025
+ params: { query: "laptop", max_results: 5 }
2026
+ },
2027
+ pegasus: {
2028
+ fnCall: `pegasus.get_flight_availability(departure_port="SAW", arrival_port="ESB", departure_date="2027-03-03", return_date="2027-03-08")`,
2029
+ cloudCall: `api.pegasus.get_flight_availability(departure_port="SAW", arrival_port="ESB", departure_date="2027-03-03", return_date="2027-03-08")`,
2030
+ params: { departure_port: "SAW", arrival_port: "ESB", departure_date: "2027-03-03", return_date: "2027-03-08" }
2031
+ },
2032
+ enuygun: {
2033
+ fnCall: `enuygun.get_popular_flights()`,
2034
+ cloudCall: `api.enuygun.get_popular_flights()`,
2035
+ params: {}
2036
+ },
2037
+ linkedin_jobs: {
2038
+ fnCall: `linkedin_jobs.search_jobs(keywords="AI Engineer", location="Turkey", limit=5)`,
2039
+ cloudCall: `api.linkedin_jobs.search_jobs(keywords="AI Engineer", location="Turkey", limit=5)`,
2040
+ params: { keywords: "AI Engineer", location: "Turkey", limit: 5 }
2041
+ },
2042
+ wikipedia: {
2043
+ fnCall: `wikipedia.search(query="Artificial intelligence")`,
2044
+ cloudCall: `api.wikipedia.search(query="Artificial intelligence")`,
2045
+ params: { query: "Artificial intelligence" }
2046
+ }
2047
+ };
2048
+
2049
+ function formatNumberTr(n) {
2050
+ return Number(n || 0).toLocaleString('tr-TR');
2051
+ }
2052
+
2053
+ function selectQuickstartSite(serviceName) {
2054
+ selectedQuickstartService = serviceName;
2055
+ document.querySelectorAll('.qs-site-card').forEach(el => {
2056
+ el.className = 'qs-site-card text-left p-3 rounded-xl border border-borderSubtle bg-bgBase/70 transition hover:border-indigo-400';
2057
+ });
2058
+ const activeCard = document.getElementById(`qsCard-${serviceName}`);
2059
+ if (activeCard) {
2060
+ activeCard.className = 'qs-site-card text-left p-3 rounded-xl border border-indigo-500 bg-indigo-500/15 transition hover:border-indigo-400';
2061
+ }
2062
+
2063
+ const badge = document.getElementById('qsSelectedServiceBadge');
2064
+ if (badge) badge.textContent = serviceName;
2065
+
2066
+ const tpl = QUICKSTART_TEMPLATES[serviceName] || {
2067
+ fnCall: `${serviceName}.search()`,
2068
+ cloudCall: `api.${serviceName}.search()`,
2069
+ params: {}
2070
+ };
2071
+ const keyPreview = (currentAccount && currentAccount.api_key) ? currentAccount.api_key : 'sk-ownapi-...';
2072
+ const codeEl = document.getElementById('qsPythonCodeBlock');
2073
+ if (codeEl) {
2074
+ codeEl.textContent =
2075
+ `# 1. Bedava Yerel Mod (Kendi bilgisayarın - 1.000 kredi/ay)
2076
+ from ownapi import ${serviceName}
2077
+ res = ${tpl.fnCall}
2078
+ print(res)
2079
+
2080
+ # 2. Bulut Modu ($10 Starter Cloud - 7/24 Bulut Tarayıcı)
2081
+ from ownapi import OwnAPI
2082
+ api = OwnAPI(api_key="${keyPreview}")
2083
+ cloud_res = ${tpl.cloudCall}
2084
+ print(cloud_res)`;
2085
+ }
2086
+ }
2087
+
2088
+ function copyQuickstartCode() {
2089
+ const codeEl = document.getElementById('qsPythonCodeBlock');
2090
+ if (!codeEl) return;
2091
+ navigator.clipboard.writeText(codeEl.textContent);
2092
+ showToast(`📋 <b>${selectedQuickstartService}</b> Python SDK kodu panoya kopyalandı!`);
2093
+ }
2094
+
2095
+ async function runQuickstartTest() {
2096
+ const svc = selectedQuickstartService || 'thy';
2097
+ const tpl = QUICKSTART_TEMPLATES[svc] || { params: {} };
2098
+ const btn = document.getElementById('qsRunTestBtn');
2099
+ const viewer = document.getElementById('qsTestResultViewer');
2100
+ const timer = document.getElementById('qsTestTimerBadge');
2101
+
2102
+ btn.disabled = true;
2103
+ btn.innerHTML = `<span class="animate-spin">⏳</span> Çalışıyor...`;
2104
+ timer.textContent = `Çalıştırılıyor (${currentMode.toUpperCase()})...`;
2105
+
2106
+ const t0 = performance.now();
2107
+ try {
2108
+ const res = await fetch('/api/v1/call', {
2109
+ method: 'POST',
2110
+ headers: { 'Content-Type': 'application/json' },
2111
+ body: JSON.stringify({
2112
+ service: svc,
2113
+ params: tpl.params,
2114
+ tenant_id: getTenant(),
2115
+ mode: currentMode,
2116
+ non_interactive: false
2117
+ })
2118
+ });
2119
+ const data = await res.json();
2120
+ const elapsed = data.elapsed_ms || Math.round(performance.now() - t0);
2121
+ timer.textContent = `⏱️ ${elapsed} ms`;
2122
+ viewer.textContent = JSON.stringify(data, null, 2);
2123
+ if (!data.success && (data.error || data.detail)) {
2124
+ showToast(`⚠️ ${data.error || data.detail}`, true);
2125
+ }
2126
+ await loadAccountInfo();
2127
+ } catch (err) {
2128
+ timer.textContent = 'Hata';
2129
+ viewer.textContent = JSON.stringify({ error: String(err) }, null, 2);
2130
+ } finally {
2131
+ btn.disabled = false;
2132
+ btn.innerHTML = `<span>▶️</span> Test Et`;
2133
+ }
2134
+ }
2135
+
2136
+ async function loadAccountInfo() {
2137
+ try {
2138
+ const tenant = getTenant();
2139
+ const res = await fetch(`/api/v1/account?tenant_id=${encodeURIComponent(tenant)}`, { cache: 'no-store' });
2140
+ if (!res.ok) return;
2141
+ const acc = await res.json();
2142
+ currentAccount = acc;
2143
+
2144
+ const isCloud = acc.mode === 'cloud';
2145
+ const used = acc.used_credits ?? 0;
2146
+ const maxC = acc.max_credits || 1000;
2147
+ const remC = acc.remaining_credits ?? acc.credits ?? 1000;
2148
+ const maxSites = acc.max_sites ?? (acc.plan === 'growth_cloud' ? 50 : (acc.plan === 'starter_cloud' ? 5 : 2));
2149
+ const activeSitesCount = acc.active_sites_count ?? (Array.isArray(acc.active_sites) ? acc.active_sites.length : 0);
2150
+
2151
+ // Üst bar canlı Kredi, Site Limiti & Mod göstergesi
2152
+ const topBadge = document.getElementById('topCreditStatusBadge');
2153
+ const topDot = document.getElementById('topStatusDot');
2154
+ const topConn = document.getElementById('topStatusConnectionText');
2155
+ const topCred = document.getElementById('topStatusCreditsText');
2156
+ const topSites = document.getElementById('topStatusSitesText');
2157
+ const topPlan = document.getElementById('topStatusPlanText');
2158
+
2159
+ if (isCloud) {
2160
+ topBadge.className = 'cursor-pointer flex items-center gap-2 bg-bgCard hover:bg-bgElevated border border-cyan-500/50 rounded-lg px-3 py-1.5 text-xs shadow-sm transition';
2161
+ topDot.className = 'inline-block w-2 h-2 rounded-full bg-cyan-400 animate-pulse';
2162
+ topConn.innerHTML = '☁️ Bulut Aktif (Cloud)';
2163
+ topConn.className = 'font-semibold text-cyan-300';
2164
+ topPlan.textContent = `Paket: ${acc.plan === 'growth_cloud' ? 'Growth Cloud ($49)' : 'Starter Cloud ($10)'}`;
2165
+ topPlan.className = 'hidden sm:inline font-semibold text-cyan-300';
2166
+ } else {
2167
+ topBadge.className = 'cursor-pointer flex items-center gap-2 bg-bgCard hover:bg-bgElevated border border-emerald-500/40 rounded-lg px-3 py-1.5 text-xs shadow-sm transition';
2168
+ topDot.className = 'inline-block w-2 h-2 rounded-full bg-emerald-400 animate-pulse';
2169
+ topConn.innerHTML = 'Bağlı - Bilgisayarın Hazır';
2170
+ topConn.className = 'font-medium text-emerald-300';
2171
+ topPlan.textContent = 'Paket: Free (Local)';
2172
+ topPlan.className = 'hidden sm:inline font-semibold text-indigo-300';
2173
+ }
2174
+ topCred.textContent = `Kullanılan: ${formatNumberTr(used)} / ${formatNumberTr(maxC)} Kredi (Kalan: ${formatNumberTr(remC)})`;
2175
+ if (topSites) {
2176
+ topSites.textContent = `Siteler: ${activeSitesCount} / ${maxSites}`;
2177
+ topSites.title = (Array.isArray(acc.active_sites) && acc.active_sites.length)
2178
+ ? `Aktif Siteler: ${acc.active_sites.join(', ')}`
2179
+ : `Maksimum ${maxSites} farklı site kullanılabilir`;
2180
+ }
2181
+
2182
+ // Sekme 2: API Anahtarlarım güncellemesi
2183
+ renderApiKeySection();
2184
+
2185
+ // Sekme 3: Paket aktif rozetleri
2186
+ ['free', 'starter_cloud', 'growth_cloud'].forEach(p => {
2187
+ const b = document.getElementById(`planActiveBadge-${p}`);
2188
+ if (b) {
2189
+ if (acc.plan === p) b.classList.remove('hidden');
2190
+ else b.classList.add('hidden');
2191
+ }
2192
+ });
2193
+
2194
+ selectQuickstartSite(selectedQuickstartService);
2195
+ } catch (err) {
2196
+ console.warn('Hesap bilgisi okunamadı:', err);
2197
+ }
2198
+ }
2199
+
2200
+ function renderApiKeySection() {
2201
+ if (!currentAccount) return;
2202
+ const keyEl = document.getElementById('apiKeyDisplayValue');
2203
+ const toggleBtn = document.getElementById('toggleKeyBtn');
2204
+ const planBadge = document.getElementById('apiKeyPlanBadge');
2205
+ const credSum = document.getElementById('apiKeyCreditSummary');
2206
+ const credBar = document.getElementById('apiKeyCreditBar');
2207
+ const modeInfo = document.getElementById('apiKeyModeInfo');
2208
+ const sdkSnippet = document.getElementById('apiKeySdkSnippet');
2209
+
2210
+ if (keyEl) {
2211
+ keyEl.textContent = isApiKeyVisible ? currentAccount.api_key : currentAccount.masked_api_key;
2212
+ }
2213
+ if (toggleBtn) {
2214
+ toggleBtn.textContent = isApiKeyVisible ? '🙈 Gizle' : '👁️ Göster';
2215
+ }
2216
+ if (planBadge) {
2217
+ planBadge.textContent = `${currentAccount.plan_label || currentAccount.plan} (${currentAccount.mode.toUpperCase()})`;
2218
+ }
2219
+ const rem = currentAccount.remaining_credits ?? 0;
2220
+ const max = currentAccount.max_credits || 1000;
2221
+ const pct = Math.max(0, Math.min(100, Math.round((rem / max) * 100)));
2222
+ if (credSum) {
2223
+ credSum.textContent = `${formatNumberTr(rem)} / ${formatNumberTr(max)} Kredi Kaldı`;
2224
+ }
2225
+ if (credBar) {
2226
+ credBar.style.width = `${pct}%`;
2227
+ }
2228
+ if (modeInfo) {
2229
+ modeInfo.innerHTML = currentAccount.mode === 'cloud'
2230
+ ? `Çalışma Modu: <b class="text-cyan-300">☁️ cloud (7/24 Bulut Tarayıcı Kümesi)</b>`
2231
+ : `Çalışma Modu: <b class="text-emerald-300">💻 local (Kendi Bilgisayarın)</b>`;
2232
+ }
2233
+ if (sdkSnippet) {
2234
+ sdkSnippet.textContent =
2235
+ `from ownapi import OwnAPI
2236
+
2237
+ api = OwnAPI(api_key="${currentAccount.api_key}")
2238
+ flights = api.thy.search_flights(
2239
+ origin_airport_code="IST",
2240
+ destination_airport_code="ESB"
2241
+ )
2242
+ print(flights)`;
2243
+ }
2244
+ }
2245
+
2246
+ function toggleApiKeyVisibility() {
2247
+ isApiKeyVisible = !isApiKeyVisible;
2248
+ renderApiKeySection();
2249
+ }
2250
+
2251
+ function copyCurrentApiKey() {
2252
+ if (!currentAccount || !currentAccount.api_key) return;
2253
+ navigator.clipboard.writeText(currentAccount.api_key);
2254
+ showToast(`📋 API anahtarınız (<code>${currentAccount.masked_api_key}</code>) panoya kopyalandı!`);
2255
+ }
2256
+
2257
+ async function generateNewApiKey() {
2258
+ const btn = document.getElementById('btnGenerateNewKey');
2259
+ if (btn) btn.disabled = true;
2260
+ try {
2261
+ const res = await fetch('/api/v1/keys/generate', {
2262
+ method: 'POST',
2263
+ headers: { 'Content-Type': 'application/json' },
2264
+ body: JSON.stringify({ tenant_id: getTenant() })
2265
+ });
2266
+ const data = await res.json();
2267
+ if (res.ok && data.success) {
2268
+ currentAccount = data;
2269
+ isApiKeyVisible = true;
2270
+ renderApiKeySection();
2271
+ await loadAccountInfo();
2272
+ showToast(`🔑 Yeni API anahtarınız üretildi: <code>${data.api_key}</code>`);
2273
+ } else {
2274
+ showToast(`✖ Anahtar üretilemedi: ${data.detail || data.error}`, true);
2275
+ }
2276
+ } catch (err) {
2277
+ showToast(`✖ Hata: ${err}`, true);
2278
+ } finally {
2279
+ if (btn) btn.disabled = false;
2280
+ }
2281
+ }
2282
+
2283
+ function openCheckoutModal(planKey, planTitle, priceUsd, creditsLabel) {
2284
+ selectedUpgradePlan = planKey;
2285
+ selectedUpgradePrice = priceUsd;
2286
+ document.getElementById('checkoutPlanName').textContent = planTitle;
2287
+ document.getElementById('checkoutPlanCredits').textContent = `${creditsLabel} Bulut Kredisi • mode="cloud"`;
2288
+ document.getElementById('checkoutPlanPrice').textContent = `$${priceUsd}`;
2289
+ document.getElementById('checkoutPayBtnText').textContent = `Öde - $${priceUsd}`;
2290
+ document.getElementById('checkoutFormView').classList.remove('hidden');
2291
+ document.getElementById('checkoutSuccessView').classList.add('hidden');
2292
+ document.getElementById('stripeCheckoutModal').classList.remove('hidden');
2293
+ }
2294
+
2295
+ function closeCheckoutModal() {
2296
+ document.getElementById('stripeCheckoutModal').classList.add('hidden');
2297
+ }
2298
+
2299
+ async function submitBillingUpgrade() {
2300
+ const btn = document.getElementById('checkoutPayBtn');
2301
+ btn.disabled = true;
2302
+ const configuredStripeLink =
2303
+ (currentAccount && currentAccount.stripe_links && currentAccount.stripe_links[selectedUpgradePlan]) ||
2304
+ (selectedUpgradePlan === 'starter_cloud' && currentAccount && currentAccount.stripe_starter_link) ||
2305
+ (selectedUpgradePlan === 'growth_cloud' && currentAccount && currentAccount.stripe_growth_link) ||
2306
+ null;
2307
+
2308
+ btn.innerHTML = configuredStripeLink
2309
+ ? `<span class="animate-spin">⏳</span> Stripe Ödeme Sayfasına Yönlendiriliyor...`
2310
+ : `<span class="animate-spin">⏳</span> Ödeme Doğrulanıyor & Bulut Kümesi Açılıyor...`;
2311
+
2312
+ try {
2313
+ const res = await fetch('/api/v1/billing/upgrade', {
2314
+ method: 'POST',
2315
+ headers: { 'Content-Type': 'application/json' },
2316
+ body: JSON.stringify({
2317
+ tenant_id: getTenant(),
2318
+ plan: selectedUpgradePlan
2319
+ })
2320
+ });
2321
+ const data = await res.json();
2322
+ if (res.ok && data.success) {
2323
+ currentAccount = data;
2324
+ setExecutionMode('cloud');
2325
+ await loadAccountInfo();
2326
+
2327
+ const stripeCheckoutUrl = data.stripe_checkout_url || configuredStripeLink;
2328
+ if (stripeCheckoutUrl) {
2329
+ showToast(`🔒 Gerçek Stripe ödeme bağlantısına yönlendiriliyorsunuz: <code>${stripeCheckoutUrl}</code>`);
2330
+ window.location.href = stripeCheckoutUrl;
2331
+ return;
2332
+ }
2333
+
2334
+ document.getElementById('checkoutFormView').classList.add('hidden');
2335
+ document.getElementById('checkoutSuccessView').classList.remove('hidden');
2336
+ document.getElementById('checkoutSuccessSubtitle').innerHTML =
2337
+ `<b>${formatNumberTr(data.credits)}</b> bulut krediniz ve <code class="text-cyan-300 font-mono">mode="${data.mode}"</code> erişiminiz anında tanımlandı.`;
2338
+ document.getElementById('checkoutNewApiKey').textContent = data.api_key;
2339
+ document.getElementById('checkoutCloudSnippet').textContent =
2340
+ `from ownapi import OwnAPI
2341
+
2342
+ api = OwnAPI(api_key="${data.api_key}")
2343
+ res = api.thy.search_flights(origin_airport_code="IST", destination_airport_code="ESB")
2344
+ print(res)`;
2345
+ showToast(`🎉 <b>Tebrikler! Artık buluttasın.</b> Yeni bulut anahtarın: <code>${data.api_key}</code>`);
2346
+ } else {
2347
+ showToast(`✖ Paket yükseltme hatası: ${data.detail || data.error}`, true);
2348
+ }
2349
+ } catch (err) {
2350
+ showToast(`✖ Ödeme bağlantı hatası: ${err}`, true);
2351
+ } finally {
2352
+ btn.disabled = false;
2353
+ btn.innerHTML = `<span>🔒</span> <span id="checkoutPayBtnText">Öde - $${selectedUpgradePrice}</span>`;
2354
+ }
2355
+ }
2356
+
2357
+ function copyUpgradedKey() {
2358
+ const keyTxt = (document.getElementById('checkoutNewApiKey').textContent || '').trim();
2359
+ if (!keyTxt) return;
2360
+ navigator.clipboard.writeText(keyTxt);
2361
+ showToast('📋 Yeni bulut API anahtarınız panoya kopyalandı!');
2362
+ }
2363
+
2364
+ async function selectFreePlan() {
2365
+ try {
2366
+ const res = await fetch('/api/v1/billing/upgrade', {
2367
+ method: 'POST',
2368
+ headers: { 'Content-Type': 'application/json' },
2369
+ body: JSON.stringify({ tenant_id: getTenant(), plan: 'free' })
2370
+ });
2371
+ const data = await res.json();
2372
+ if (res.ok && data.success) {
2373
+ setExecutionMode('local');
2374
+ await loadAccountInfo();
2375
+ showToast(`💻 <b>Free (Local)</b> pakete geçildi (1.000 kredi, mode="local").`);
2376
+ }
2377
+ } catch (err) {
2378
+ showToast(`✖ Hata: ${err}`, true);
2379
+ }
2380
+ }
2381
+
2382
+ async function refreshAllData() {
2383
+ await Promise.all([
2384
+ loadAccountInfo(),
2385
+ loadServices(),
2386
+ loadHitlQueue(),
2387
+ loadAutoRepairReport(),
2388
+ loadConsentAudit()
2389
+ ]);
2390
+ }
2391
+
2392
+ window.addEventListener('DOMContentLoaded', () => {
2393
+ selectQuickstartSite('thy');
2394
+ pollHitlPending();
2395
+ refreshAllData();
2396
+ // Her 2 saniyede bir .sessions/hitl_state.json üzerinden bekleyen HITL onaylarını sorgula
2397
+ setInterval(pollHitlPending, 2000);
2398
+ });
2399
+ </script>
2400
+ </body>
2401
+ </html>