Cnass commited on
Commit
e26c216
·
verified ·
1 Parent(s): 6967bb5

Upload 4 files

Browse files
Files changed (4) hide show
  1. test-ui/index.html +1 -1
  2. test-ui/rpgm.html +93 -0
  3. test-ui/rpgm.js +271 -0
  4. test-ui/style.css +94 -0
test-ui/index.html CHANGED
@@ -10,7 +10,7 @@
10
  <div class="wrap">
11
  <header>
12
  <h1>Hy-MT2 Translation API <span class="badge">Test Arayüzü</span></h1>
13
- <p class="sub">Tek istek ve toplu (batch) isteklerin yanıt sürelerini karşılaştırın.</p>
14
  </header>
15
 
16
  <section class="panel" id="config-panel">
 
10
  <div class="wrap">
11
  <header>
12
  <h1>Hy-MT2 Translation API <span class="badge">Test Arayüzü</span></h1>
13
+ <p class="sub">Tek istek ve toplu (batch) isteklerin yanıt sürelerini karşılaştırın. <a href="rpgm.html">→ RPG Maker proje çevirisi</a></p>
14
  </header>
15
 
16
  <section class="panel" id="config-panel">
test-ui/rpgm.html ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="tr">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>RPG Maker Çeviri</title>
7
+ <link rel="stylesheet" href="style.css" />
8
+ </head>
9
+ <body>
10
+ <div class="wrap">
11
+ <header>
12
+ <h1>RPG Maker Çeviri <span class="badge">MV / MZ</span></h1>
13
+ <p class="sub">
14
+ Oyunun <code>data</code> (MZ) veya <code>www/data</code> (MV) klasörünü zip olarak yükleyin;
15
+ diyaloglar otomatik çıkarılır, çevrilir ve aynı yapıda geri paketlenir.
16
+ <a href="index.html">→ Hız testi arayüzü</a>
17
+ </p>
18
+ </header>
19
+
20
+ <section class="panel">
21
+ <h2>1 · Proje Yükle</h2>
22
+ <div class="grid">
23
+ <label>
24
+ API Base URL
25
+ <input type="text" id="apiBase" value="http://localhost:7860" />
26
+ </label>
27
+ <label>
28
+ API Key <span class="hint">(opsiyonel)</span>
29
+ <input type="text" id="apiKey" placeholder="X-API-Key" />
30
+ </label>
31
+ <label>
32
+ Hedef Dil
33
+ <select id="targetLang"></select>
34
+ </label>
35
+ </div>
36
+
37
+ <div id="dropZone" class="dropzone">
38
+ <input type="file" id="fileInput" accept=".zip" hidden />
39
+ <strong>Zip dosyasını buraya sürükleyin</strong>
40
+ <span class="hint">veya tıklayıp seçin — yalnızca data klasörü, tüm oyun değil</span>
41
+ </div>
42
+
43
+ <div class="actions">
44
+ <button id="btnUpload" class="primary" disabled>Yükle ve Analiz Et</button>
45
+ <span id="uploadInfo" class="hint"></span>
46
+ </div>
47
+ </section>
48
+
49
+ <section class="panel" id="projectPanel" hidden>
50
+ <h2>2 · Çeviri</h2>
51
+ <div class="stats" id="projectStats"></div>
52
+ <div class="actions">
53
+ <button id="btnStart" class="accent">Çeviriyi Başlat</button>
54
+ <button id="btnCancel" hidden>Durdur</button>
55
+ <button id="btnDownload">Çeviriyi İndir</button>
56
+ <button id="btnDelete">Projeyi Sil</button>
57
+ </div>
58
+
59
+ <div class="progress-block">
60
+ <div class="progress-head">
61
+ <span>Genel İlerleme</span>
62
+ <span id="overallPct">0%</span>
63
+ </div>
64
+ <div class="progress-track"><div class="progress-fill" id="overallBar"></div></div>
65
+ <p class="hint" id="overallNote"></p>
66
+ </div>
67
+
68
+ <h3 class="subhead">Dosya Bazında</h3>
69
+ <div id="fileList" class="file-list"></div>
70
+ </section>
71
+
72
+ <section class="panel" id="reviewPanel" hidden>
73
+ <h2>Gözden Geçirilmesi Gerekenler</h2>
74
+ <p class="hint">
75
+ Çevirisi kontrol kodlarını (<code>\C[2]</code>, <code>\N[1]</code> gibi) koruyamadığı için
76
+ <strong>oyuna yazılmayan</strong> satırlar — bunlar kaynak dilde bırakıldı, oyun bozulmaz.
77
+ </p>
78
+ <div class="table-wrap">
79
+ <table id="reviewTable">
80
+ <thead><tr><th>Kaynak</th><th>Yazılan</th></tr></thead>
81
+ <tbody></tbody>
82
+ </table>
83
+ </div>
84
+ </section>
85
+
86
+ <section class="panel">
87
+ <h2>Log</h2>
88
+ <pre id="log"></pre>
89
+ </section>
90
+ </div>
91
+ <script src="rpgm.js"></script>
92
+ </body>
93
+ </html>
test-ui/rpgm.js ADDED
@@ -0,0 +1,271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const $ = (id) => document.getElementById(id);
2
+ const logEl = $("log");
3
+
4
+ let selectedFile = null;
5
+ let projectId = null;
6
+ let pollTimer = null;
7
+
8
+ function log(msg) {
9
+ logEl.textContent += `[${new Date().toLocaleTimeString()}] ${msg}\n`;
10
+ logEl.scrollTop = logEl.scrollHeight;
11
+ }
12
+
13
+ function apiBase() {
14
+ return $("apiBase").value.replace(/\/+$/, "");
15
+ }
16
+
17
+ function headers(extra) {
18
+ const h = Object.assign({}, extra || {});
19
+ const key = $("apiKey").value.trim();
20
+ if (key) h["X-API-Key"] = key;
21
+ return h;
22
+ }
23
+
24
+ async function api(path, options) {
25
+ const res = await fetch(apiBase() + path, options || {});
26
+ const text = await res.text();
27
+ let data = null;
28
+ try { data = text ? JSON.parse(text) : null; } catch (e) { /* not json */ }
29
+ if (!res.ok) throw new Error((data && data.detail) || text || res.status);
30
+ return data;
31
+ }
32
+
33
+ // ---------------------------------------------------------------- languages
34
+
35
+ const FALLBACK_LANGS = { tr: "Turkish", en: "English", ja: "Japanese", de: "German", es: "Spanish" };
36
+
37
+ function fillLanguages(map) {
38
+ const sel = $("targetLang");
39
+ sel.innerHTML = "";
40
+ for (const [code, name] of Object.entries(map)) {
41
+ sel.add(new Option(`${name} (${code})`, code));
42
+ }
43
+ sel.value = "tr";
44
+ }
45
+
46
+ async function loadLanguages() {
47
+ try {
48
+ fillLanguages(await api("/languages", { headers: headers() }));
49
+ } catch (e) {
50
+ fillLanguages(FALLBACK_LANGS);
51
+ }
52
+ }
53
+
54
+ // ------------------------------------------------------------------- upload
55
+
56
+ function setFile(file) {
57
+ if (!file) return;
58
+ if (!file.name.toLowerCase().endsWith(".zip")) {
59
+ log("Yalnızca .zip dosyası yükleyebilirsiniz.");
60
+ return;
61
+ }
62
+ selectedFile = file;
63
+ $("uploadInfo").textContent = `${file.name} — ${(file.size / 1048576).toFixed(1)} MB`;
64
+ $("btnUpload").disabled = false;
65
+ }
66
+
67
+ $("dropZone").addEventListener("click", () => $("fileInput").click());
68
+ $("fileInput").addEventListener("change", (e) => setFile(e.target.files[0]));
69
+ ["dragenter", "dragover"].forEach((ev) =>
70
+ $("dropZone").addEventListener(ev, (e) => {
71
+ e.preventDefault();
72
+ $("dropZone").classList.add("dragging");
73
+ })
74
+ );
75
+ ["dragleave", "drop"].forEach((ev) =>
76
+ $("dropZone").addEventListener(ev, (e) => {
77
+ e.preventDefault();
78
+ $("dropZone").classList.remove("dragging");
79
+ })
80
+ );
81
+ $("dropZone").addEventListener("drop", (e) => setFile(e.dataTransfer.files[0]));
82
+
83
+ $("btnUpload").addEventListener("click", async () => {
84
+ if (!selectedFile) return;
85
+ const body = new FormData();
86
+ body.append("file", selectedFile);
87
+ $("btnUpload").disabled = true;
88
+ log(`Yükleniyor: ${selectedFile.name} ...`);
89
+ try {
90
+ const st = await api("/project/upload", { method: "POST", headers: headers(), body });
91
+ projectId = st.id;
92
+ log(`Analiz tamam — ${st.engine.toUpperCase()}, ${st.total_units} benzersiz metin, ${st.total_slots} konum.`);
93
+ if (st.unreadable_files && st.unreadable_files.length) {
94
+ log(`Okunamayan dosyalar atlandı: ${st.unreadable_files.join(", ")}`);
95
+ }
96
+ render(st);
97
+ } catch (e) {
98
+ log("Yükleme hatası: " + e.message);
99
+ } finally {
100
+ $("btnUpload").disabled = false;
101
+ }
102
+ });
103
+
104
+ // -------------------------------------------------------------------- render
105
+
106
+ function stat(num, label) {
107
+ return `<div class="stat"><div class="num">${num}</div><div class="label">${label}</div></div>`;
108
+ }
109
+
110
+ const STATUS_TR = {
111
+ ready: "hazır", translating: "çevriliyor", done: "tamamlandı",
112
+ cancelled: "durduruldu", failed: "hata", extracting: "çıkarılıyor",
113
+ };
114
+
115
+ function render(st) {
116
+ $("projectPanel").hidden = false;
117
+
118
+ $("projectStats").innerHTML = [
119
+ stat(st.engine ? st.engine.toUpperCase() : "-", "Motor"),
120
+ stat(st.total_units, "Benzersiz Metin"),
121
+ stat(st.total_slots, "Toplam Konum"),
122
+ stat(STATUS_TR[st.status] || st.status, "Durum"),
123
+ stat(st.review_count || 0, "İncelenecek"),
124
+ ].join("");
125
+
126
+ $("overallBar").style.width = st.percent + "%";
127
+ $("overallPct").textContent = st.percent + "%";
128
+ $("overallNote").textContent =
129
+ `${st.translated_units} / ${st.total_units} metin çevrildi` +
130
+ (st.target_lang ? ` — hedef: ${st.target_lang}` : "");
131
+
132
+ $("fileList").innerHTML = st.files.map((f) => `
133
+ <div class="file-row">
134
+ <div class="file-name" title="${f.file}">${f.file}</div>
135
+ <div class="progress-track small"><div class="progress-fill" style="width:${f.percent}%"></div></div>
136
+ <div class="file-count">${f.done}/${f.total}</div>
137
+ </div>`).join("");
138
+
139
+ const running = st.running || st.status === "translating";
140
+ $("btnStart").hidden = running;
141
+ $("btnStart").textContent = st.translated_units > 0 && st.status !== "done"
142
+ ? "Kaldığı Yerden Devam Et" : "Çeviriyi Başlat";
143
+ $("btnCancel").hidden = !running;
144
+
145
+ const samples = st.review_samples || [];
146
+ $("reviewPanel").hidden = samples.length === 0;
147
+ document.querySelector("#reviewTable tbody").innerHTML = samples
148
+ .map((s) => `<tr><td>${escapeHtml(s.source)}</td><td>${escapeHtml(s.translation)}</td></tr>`)
149
+ .join("");
150
+
151
+ if (running) startPolling(); else stopPolling();
152
+ }
153
+
154
+ function escapeHtml(s) {
155
+ return String(s).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c]));
156
+ }
157
+
158
+ // ------------------------------------------------------------------ polling
159
+
160
+ function startPolling() {
161
+ if (pollTimer) return;
162
+ pollTimer = setInterval(refresh, 2000);
163
+ }
164
+ function stopPolling() {
165
+ if (pollTimer) { clearInterval(pollTimer); pollTimer = null; }
166
+ }
167
+
168
+ async function refresh() {
169
+ if (!projectId) return;
170
+ try {
171
+ const st = await api(`/project/${projectId}/status`, { headers: headers() });
172
+ const wasRunning = !$("btnCancel").hidden;
173
+ render(st);
174
+ if (wasRunning && !(st.running || st.status === "translating")) {
175
+ log(`Çeviri ${STATUS_TR[st.status] || st.status}. ${st.translated_units}/${st.total_units} metin.` +
176
+ (st.review_count ? ` ${st.review_count} satır incelenmeli.` : ""));
177
+ }
178
+ } catch (e) {
179
+ log("Durum alınamadı: " + e.message);
180
+ stopPolling();
181
+ }
182
+ }
183
+
184
+ // ------------------------------------------------------------------ actions
185
+
186
+ $("btnStart").addEventListener("click", async () => {
187
+ if (!projectId) return;
188
+ try {
189
+ log("Çeviri başlatılıyor...");
190
+ await api(`/project/${projectId}/start`, {
191
+ method: "POST",
192
+ headers: headers({ "Content-Type": "application/json" }),
193
+ body: JSON.stringify({ target_lang: $("targetLang").value }),
194
+ });
195
+ startPolling();
196
+ refresh();
197
+ } catch (e) {
198
+ log("Başlatılamadı: " + e.message);
199
+ }
200
+ });
201
+
202
+ $("btnCancel").addEventListener("click", async () => {
203
+ try {
204
+ await api(`/project/${projectId}/cancel`, { method: "POST", headers: headers() });
205
+ log("Durdurma istendi — devam eden istekler bitince duracak.");
206
+ } catch (e) {
207
+ log("Durdurulamadı: " + e.message);
208
+ }
209
+ });
210
+
211
+ $("btnDownload").addEventListener("click", async () => {
212
+ if (!projectId) return;
213
+ log("Paketleniyor...");
214
+ try {
215
+ // Fetched rather than linked so the API key header can be sent, and so a
216
+ // failure surfaces as a log line instead of a broken download.
217
+ const res = await fetch(`${apiBase()}/project/${projectId}/download`, { headers: headers() });
218
+ if (!res.ok) throw new Error(await res.text());
219
+ const blob = await res.blob();
220
+ const url = URL.createObjectURL(blob);
221
+ const a = document.createElement("a");
222
+ a.href = url;
223
+ a.download = "translated.zip";
224
+ document.body.appendChild(a);
225
+ a.click();
226
+ a.remove();
227
+ URL.revokeObjectURL(url);
228
+ log(`İndirildi (${(blob.size / 1048576).toFixed(2)} MB).`);
229
+ } catch (e) {
230
+ log("İndirilemedi: " + e.message);
231
+ }
232
+ });
233
+
234
+ $("btnDelete").addEventListener("click", async () => {
235
+ if (!projectId) return;
236
+ try {
237
+ await api(`/project/${projectId}`, { method: "DELETE", headers: headers() });
238
+ log("Proje silindi.");
239
+ stopPolling();
240
+ projectId = null;
241
+ $("projectPanel").hidden = true;
242
+ $("reviewPanel").hidden = true;
243
+ } catch (e) {
244
+ log("Silinemedi: " + e.message);
245
+ }
246
+ });
247
+
248
+ // --------------------------------------------------------------- initialise
249
+
250
+ if (location.protocol === "http:" || location.protocol === "https:") {
251
+ $("apiBase").value = location.origin;
252
+ }
253
+ loadLanguages();
254
+
255
+ // Pick up a job that is already running - e.g. after a page reload, or after
256
+ // the Space restarted and resumed the run on its own.
257
+ (async () => {
258
+ try {
259
+ const list = await api("/project", { headers: headers() });
260
+ if (list.projects && list.projects.length) {
261
+ const active = list.projects.find((p) => p.running) || list.projects[0];
262
+ projectId = active.id;
263
+ render(active);
264
+ log(`Mevcut proje bulundu: ${active.name || active.id} (${STATUS_TR[active.status] || active.status}).`);
265
+ } else {
266
+ log("Arayüz hazır. Bir data klasörü zip'i yükleyin.");
267
+ }
268
+ } catch (e) {
269
+ log("Arayüz hazır. API'ye bağlanılamadı: " + e.message);
270
+ }
271
+ })();
test-ui/style.css CHANGED
@@ -269,3 +269,97 @@ td.time { color: var(--text-dim); white-space: nowrap; }
269
  white-space: pre-wrap;
270
  margin: 0;
271
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  white-space: pre-wrap;
270
  margin: 0;
271
  }
272
+
273
+ /* --- RPG Maker project page ------------------------------------------- */
274
+
275
+ .subhead {
276
+ font-size: 0.8rem;
277
+ color: var(--text-dim);
278
+ text-transform: uppercase;
279
+ letter-spacing: 0.05em;
280
+ margin: 22px 0 10px;
281
+ }
282
+
283
+ code {
284
+ background: #0d1322;
285
+ border: 1px solid var(--panel-border);
286
+ border-radius: 4px;
287
+ padding: 1px 5px;
288
+ font-size: 0.85em;
289
+ }
290
+
291
+ a { color: var(--accent); }
292
+
293
+ .dropzone {
294
+ margin-top: 16px;
295
+ border: 2px dashed var(--panel-border);
296
+ border-radius: 12px;
297
+ padding: 30px 20px;
298
+ text-align: center;
299
+ cursor: pointer;
300
+ display: flex;
301
+ flex-direction: column;
302
+ gap: 6px;
303
+ transition: border-color 0.15s ease, background 0.15s ease;
304
+ }
305
+
306
+ .dropzone:hover,
307
+ .dropzone.dragging {
308
+ border-color: var(--accent);
309
+ background: #131b2e;
310
+ }
311
+
312
+ .progress-block { margin-top: 20px; }
313
+
314
+ .progress-head {
315
+ display: flex;
316
+ justify-content: space-between;
317
+ font-size: 0.85rem;
318
+ margin-bottom: 6px;
319
+ }
320
+
321
+ .progress-track {
322
+ background: #0d1322;
323
+ border: 1px solid var(--panel-border);
324
+ border-radius: 8px;
325
+ height: 20px;
326
+ overflow: hidden;
327
+ }
328
+
329
+ .progress-track.small { height: 12px; }
330
+
331
+ .progress-fill {
332
+ height: 100%;
333
+ width: 0;
334
+ background: linear-gradient(90deg, var(--accent), var(--accent-2));
335
+ transition: width 0.4s ease;
336
+ }
337
+
338
+ .file-list {
339
+ display: flex;
340
+ flex-direction: column;
341
+ gap: 8px;
342
+ max-height: 340px;
343
+ overflow-y: auto;
344
+ }
345
+
346
+ .file-row {
347
+ display: grid;
348
+ grid-template-columns: minmax(120px, 1fr) 2fr 70px;
349
+ align-items: center;
350
+ gap: 12px;
351
+ font-size: 0.82rem;
352
+ }
353
+
354
+ .file-name {
355
+ color: var(--text-dim);
356
+ overflow: hidden;
357
+ text-overflow: ellipsis;
358
+ white-space: nowrap;
359
+ }
360
+
361
+ .file-count {
362
+ text-align: right;
363
+ color: var(--text-dim);
364
+ font-variant-numeric: tabular-nums;
365
+ }