GoshawkVortexAI commited on
Commit
cbe7d36
·
verified ·
1 Parent(s): 36d1e0e

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +1298 -0
app.py ADDED
@@ -0,0 +1,1298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import base64
4
+ import time
5
+ import sqlite3
6
+ import hashlib
7
+ import secrets
8
+ from datetime import date
9
+
10
+ import gradio as gr
11
+ import torch
12
+
13
+ from transformers import AutoTokenizer
14
+ from peft import AutoPeftModelForCausalLM
15
+
16
+
17
+ # ============================================================
18
+ # CONFIG
19
+ # ============================================================
20
+
21
+ MODEL_PATH = "."
22
+
23
+ DB_PATH = "users.db"
24
+
25
+ FREE_DAILY_LIMIT = 10
26
+ PRO_DAILY_LIMIT = 1000
27
+
28
+ ADMIN_EMAIL = os.getenv(
29
+ "ADMIN_EMAIL",
30
+ "admin@example.com"
31
+ )
32
+
33
+ ADMIN_PASSWORD = os.getenv(
34
+ "ADMIN_PASSWORD",
35
+ "CHANGE_THIS_PASSWORD"
36
+ )
37
+
38
+
39
+ # ============================================================
40
+ # DATABASE
41
+ # ============================================================
42
+
43
+ def db():
44
+ conn = sqlite3.connect(
45
+ DB_PATH,
46
+ check_same_thread=False
47
+ )
48
+ conn.row_factory = sqlite3.Row
49
+ return conn
50
+
51
+
52
+ def init_db():
53
+
54
+ conn = db()
55
+
56
+ conn.execute("""
57
+ CREATE TABLE IF NOT EXISTS users (
58
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
59
+ email TEXT UNIQUE NOT NULL,
60
+ password_hash TEXT NOT NULL,
61
+ salt TEXT NOT NULL,
62
+ plan TEXT DEFAULT 'free',
63
+ active INTEGER DEFAULT 1,
64
+ daily_usage INTEGER DEFAULT 0,
65
+ usage_date TEXT,
66
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
67
+ )
68
+ """)
69
+
70
+ conn.commit()
71
+
72
+ existing = conn.execute(
73
+ "SELECT id FROM users WHERE email = ?",
74
+ (ADMIN_EMAIL.lower().strip(),)
75
+ ).fetchone()
76
+
77
+ if not existing:
78
+
79
+ password_hash, salt = hash_password(
80
+ ADMIN_PASSWORD
81
+ )
82
+
83
+ conn.execute("""
84
+ INSERT INTO users
85
+ (email, password_hash, salt, plan, active)
86
+ VALUES (?, ?, ?, 'admin', 1)
87
+ """, (
88
+ ADMIN_EMAIL.lower().strip(),
89
+ password_hash,
90
+ salt
91
+ ))
92
+
93
+ conn.commit()
94
+
95
+ conn.close()
96
+
97
+
98
+ # ============================================================
99
+ # PASSWORD
100
+ # ============================================================
101
+
102
+ def hash_password(password, salt=None):
103
+
104
+ if salt is None:
105
+ salt = secrets.token_hex(16)
106
+
107
+ password_hash = hashlib.pbkdf2_hmac(
108
+ "sha256",
109
+ password.encode("utf-8"),
110
+ salt.encode("utf-8"),
111
+ 200_000
112
+ ).hex()
113
+
114
+ return password_hash, salt
115
+
116
+
117
+ def verify_password(
118
+ password,
119
+ stored_hash,
120
+ salt
121
+ ):
122
+
123
+ calculated, _ = hash_password(
124
+ password,
125
+ salt
126
+ )
127
+
128
+ return secrets.compare_digest(
129
+ calculated,
130
+ stored_hash
131
+ )
132
+
133
+
134
+ # ============================================================
135
+ # USERS
136
+ # ============================================================
137
+
138
+ def get_user(email):
139
+
140
+ conn = db()
141
+
142
+ user = conn.execute(
143
+ "SELECT * FROM users WHERE email = ?",
144
+ (email.lower().strip(),)
145
+ ).fetchone()
146
+
147
+ conn.close()
148
+
149
+ return user
150
+
151
+
152
+ def create_user(email, password):
153
+
154
+ email = email.lower().strip()
155
+
156
+ if not re.match(
157
+ r"^[^@\s]+@[^@\s]+\.[^@\s]+$",
158
+ email
159
+ ):
160
+ return False, "Geçerli bir e-posta adresi gir."
161
+
162
+ if len(password) < 8:
163
+ return False, "Şifre en az 8 karakter olmalı."
164
+
165
+ if get_user(email):
166
+ return False, "Bu e-posta zaten kayıtlı."
167
+
168
+ password_hash, salt = hash_password(password)
169
+
170
+ conn = db()
171
+
172
+ conn.execute("""
173
+ INSERT INTO users
174
+ (email, password_hash, salt, plan, active)
175
+ VALUES (?, ?, ?, 'free', 1)
176
+ """, (
177
+ email,
178
+ password_hash,
179
+ salt
180
+ ))
181
+
182
+ conn.commit()
183
+ conn.close()
184
+
185
+ return True, "Hesap oluşturuldu."
186
+
187
+
188
+ def login_user(email, password):
189
+
190
+ user = get_user(email)
191
+
192
+ if not user:
193
+ return None, "E-posta veya şifre hatalı."
194
+
195
+ if not verify_password(
196
+ password,
197
+ user["password_hash"],
198
+ user["salt"]
199
+ ):
200
+ return None, "E-posta veya şifre hatalı."
201
+
202
+ if not user["active"]:
203
+ return None, "Hesabın pasif durumda."
204
+
205
+ today = str(date.today())
206
+
207
+ if user["usage_date"] != today:
208
+
209
+ conn = db()
210
+
211
+ conn.execute("""
212
+ UPDATE users
213
+ SET daily_usage = 0,
214
+ usage_date = ?
215
+ WHERE id = ?
216
+ """, (
217
+ today,
218
+ user["id"]
219
+ ))
220
+
221
+ conn.commit()
222
+ conn.close()
223
+
224
+ user = get_user(email)
225
+
226
+ return user, "Giriş başarılı."
227
+
228
+
229
+ # ============================================================
230
+ # LIMIT
231
+ # ============================================================
232
+
233
+ def get_limit(plan):
234
+
235
+ if plan == "admin":
236
+ return 999999
237
+
238
+ if plan == "pro":
239
+ return PRO_DAILY_LIMIT
240
+
241
+ return FREE_DAILY_LIMIT
242
+
243
+
244
+ def can_use(user):
245
+
246
+ today = str(date.today())
247
+
248
+ if user["usage_date"] != today:
249
+ return True
250
+
251
+ return (
252
+ user["daily_usage"]
253
+ <
254
+ get_limit(user["plan"])
255
+ )
256
+
257
+
258
+ def increment_usage(user_id):
259
+
260
+ conn = db()
261
+
262
+ conn.execute("""
263
+ UPDATE users
264
+ SET daily_usage = daily_usage + 1,
265
+ usage_date = ?
266
+ WHERE id = ?
267
+ """, (
268
+ str(date.today()),
269
+ user_id
270
+ ))
271
+
272
+ conn.commit()
273
+ conn.close()
274
+
275
+
276
+ # ============================================================
277
+ # MODEL
278
+ # ============================================================
279
+
280
+ print("Loading model...")
281
+
282
+ DEVICE = (
283
+ "cuda"
284
+ if torch.cuda.is_available()
285
+ else "cpu"
286
+ )
287
+
288
+ print("Device:", DEVICE)
289
+
290
+ adapter_file = os.path.join(
291
+ MODEL_PATH,
292
+ "adapter_model.safetensors"
293
+ )
294
+
295
+ if not os.path.exists(adapter_file):
296
+
297
+ raise RuntimeError(
298
+ "adapter_model.safetensors bulunamadı. "
299
+ "PEFT adapter ağırlığını Space'e yükle."
300
+ )
301
+
302
+
303
+ tokenizer = AutoTokenizer.from_pretrained(
304
+ MODEL_PATH
305
+ )
306
+
307
+ model = AutoPeftModelForCausalLM.from_pretrained(
308
+ MODEL_PATH,
309
+ dtype=(
310
+ torch.float16
311
+ if DEVICE == "cuda"
312
+ else torch.float32
313
+ )
314
+ )
315
+
316
+ model.to(DEVICE)
317
+ model.eval()
318
+
319
+ print("Model loaded.")
320
+
321
+
322
+ # ============================================================
323
+ # CRYPTO ASSET DETECTION
324
+ # ============================================================
325
+
326
+ KNOWN_ASSETS = {
327
+
328
+ "BTC": [
329
+ "bitcoin",
330
+ "btc"
331
+ ],
332
+
333
+ "ETH": [
334
+ "ethereum",
335
+ "eth"
336
+ ],
337
+
338
+ "SOL": [
339
+ "solana",
340
+ "sol"
341
+ ],
342
+
343
+ "BNB": [
344
+ "binance coin",
345
+ "bnb"
346
+ ],
347
+
348
+ "XRP": [
349
+ "ripple",
350
+ "xrp"
351
+ ],
352
+
353
+ "ADA": [
354
+ "cardano",
355
+ "ada"
356
+ ],
357
+
358
+ "DOGE": [
359
+ "dogecoin",
360
+ "doge"
361
+ ],
362
+
363
+ "AVAX": [
364
+ "avalanche",
365
+ "avax"
366
+ ],
367
+
368
+ "LINK": [
369
+ "chainlink",
370
+ "link"
371
+ ],
372
+
373
+ "DOT": [
374
+ "polkadot",
375
+ "dot"
376
+ ]
377
+ }
378
+
379
+
380
+ def detect_assets(text):
381
+
382
+ lower = text.lower()
383
+
384
+ detected = []
385
+
386
+ for symbol, names in KNOWN_ASSETS.items():
387
+
388
+ for name in names:
389
+
390
+ pattern = (
391
+ r"(?<![a-z])"
392
+ + re.escape(name)
393
+ + r"(?![a-z])"
394
+ )
395
+
396
+ if re.search(pattern, lower):
397
+
398
+ detected.append(symbol)
399
+ break
400
+
401
+ return detected
402
+
403
+
404
+ # ============================================================
405
+ # SENTIMENT EXTRACTION
406
+ # ============================================================
407
+
408
+ def extract_sentiment(text):
409
+
410
+ lower = text.lower()
411
+
412
+ # Strong explicit labels
413
+ if re.search(
414
+ r"\bbullish\b|\bpositive\b",
415
+ lower
416
+ ):
417
+ return "Bullish"
418
+
419
+ if re.search(
420
+ r"\bbearish\b|\bnegative\b",
421
+ lower
422
+ ):
423
+ return "Bearish"
424
+
425
+ if re.search(
426
+ r"\bneutral\b|\bsideways\b",
427
+ lower
428
+ ):
429
+ return "Neutral"
430
+
431
+ # Keyword fallback
432
+ bullish_words = [
433
+ "rise",
434
+ "rising",
435
+ "surge",
436
+ "surging",
437
+ "gain",
438
+ "gains",
439
+ "growth",
440
+ "breakout",
441
+ "bull",
442
+ "buy",
443
+ "buying",
444
+ "inflow",
445
+ "inflows",
446
+ "adoption",
447
+ "strong",
448
+ "record high",
449
+ "higher"
450
+ ]
451
+
452
+ bearish_words = [
453
+ "fall",
454
+ "falling",
455
+ "drop",
456
+ "dropping",
457
+ "crash",
458
+ "loss",
459
+ "losses",
460
+ "sell",
461
+ "selling",
462
+ "outflow",
463
+ "outflows",
464
+ "bear",
465
+ "resistance",
466
+ "hack",
467
+ "liquidation",
468
+ "lower",
469
+ "weak"
470
+ ]
471
+
472
+ bullish_score = sum(
473
+ word in lower
474
+ for word in bullish_words
475
+ )
476
+
477
+ bearish_score = sum(
478
+ word in lower
479
+ for word in bearish_words
480
+ )
481
+
482
+ if bullish_score > bearish_score:
483
+ return "Bullish"
484
+
485
+ if bearish_score > bullish_score:
486
+ return "Bearish"
487
+
488
+ return "Neutral"
489
+
490
+
491
+ # ============================================================
492
+ # SENTIMENT SCORE
493
+ # ============================================================
494
+
495
+ def sentiment_score(
496
+ sentiment,
497
+ text
498
+ ):
499
+
500
+ lower = text.lower()
501
+
502
+ bullish_words = [
503
+ "surge",
504
+ "breakout",
505
+ "bullish",
506
+ "strong",
507
+ "inflow",
508
+ "growth",
509
+ "rally",
510
+ "higher",
511
+ "buy"
512
+ ]
513
+
514
+ bearish_words = [
515
+ "crash",
516
+ "bearish",
517
+ "weak",
518
+ "outflow",
519
+ "liquidation",
520
+ "hack",
521
+ "lower",
522
+ "sell"
523
+ ]
524
+
525
+ bull = sum(
526
+ x in lower
527
+ for x in bullish_words
528
+ )
529
+
530
+ bear = sum(
531
+ x in lower
532
+ for x in bearish_words
533
+ )
534
+
535
+ raw = bull - bear
536
+
537
+ if sentiment == "Bullish":
538
+ raw += 1
539
+
540
+ elif sentiment == "Bearish":
541
+ raw -= 1
542
+
543
+ score = max(
544
+ -1,
545
+ min(
546
+ 1,
547
+ raw / 5
548
+ )
549
+ )
550
+
551
+ return score
552
+
553
+
554
+ # ============================================================
555
+ # STRENGTH
556
+ # ============================================================
557
+
558
+ def get_strength(score):
559
+
560
+ absolute = abs(score)
561
+
562
+ if absolute >= 0.70:
563
+ return "STRONG"
564
+
565
+ if absolute >= 0.35:
566
+ return "MODERATE"
567
+
568
+ return "WEAK"
569
+
570
+
571
+ # ============================================================
572
+ # RISK
573
+ # ============================================================
574
+
575
+ def get_risk(
576
+ text,
577
+ score
578
+ ):
579
+
580
+ lower = text.lower()
581
+
582
+ high_risk_words = [
583
+ "hack",
584
+ "exploit",
585
+ "crash",
586
+ "liquidation",
587
+ "lawsuit",
588
+ "ban",
589
+ "bankruptcy",
590
+ "rug"
591
+ ]
592
+
593
+ medium_risk_words = [
594
+ "volatility",
595
+ "resistance",
596
+ "uncertain",
597
+ "uncertainty",
598
+ "regulation"
599
+ ]
600
+
601
+ if any(
602
+ word in lower
603
+ for word in high_risk_words
604
+ ):
605
+ return "HIGH"
606
+
607
+ if any(
608
+ word in lower
609
+ for word in medium_risk_words
610
+ ):
611
+ return "MEDIUM"
612
+
613
+ if abs(score) > 0.7:
614
+ return "MEDIUM"
615
+
616
+ return "LOW"
617
+
618
+
619
+ # ============================================================
620
+ # MARKET IMPACT
621
+ # ============================================================
622
+
623
+ def get_market_impact(
624
+ sentiment,
625
+ score
626
+ ):
627
+
628
+ if abs(score) >= 0.70:
629
+ strength = "High"
630
+
631
+ elif abs(score) >= 0.35:
632
+ strength = "Moderate"
633
+
634
+ else:
635
+ strength = "Low"
636
+
637
+ if sentiment == "Bullish":
638
+ return f"Positive ({strength})"
639
+
640
+ if sentiment == "Bearish":
641
+ return f"Negative ({strength})"
642
+
643
+ return f"Neutral ({strength})"
644
+
645
+
646
+ # ============================================================
647
+ # SIGNAL BAR
648
+ # ============================================================
649
+
650
+ def bar(value):
651
+
652
+ value = max(
653
+ 0,
654
+ min(
655
+ 100,
656
+ int(value)
657
+ )
658
+ )
659
+
660
+ filled = int(
661
+ value / 5
662
+ )
663
+
664
+ return (
665
+ "█" * filled
666
+ +
667
+ "░" * (20 - filled)
668
+ )
669
+
670
+
671
+ # ============================================================
672
+ # VISUAL CHARTS
673
+ # ============================================================
674
+
675
+ def build_finance_chart(bullish, neutral, bearish, score, sentiment, risk):
676
+ """Dependency-free SVG/HTML dashboard chart."""
677
+ def pct(v):
678
+ return max(0.0, min(100.0, float(v)))
679
+
680
+ bullish = pct(bullish)
681
+ neutral = pct(neutral)
682
+ bearish = pct(bearish)
683
+ score_pct = (float(score) + 1.0) * 50.0
684
+ score_pct = pct(score_pct)
685
+
686
+ return f"""
687
+ <div class='finance-chart'>
688
+ <div class='chart-title'>MARKET SENTIMENT MATRIX</div>
689
+ <div class='chart-subtitle'>AI sentiment distribution • {sentiment} • Risk: {risk}</div>
690
+ <div class='bars'>
691
+ <div class='bar-row'><span class='bar-label'>BULLISH</span><div class='bar-track'><div class='bar-fill bullish' style='width:{bullish:.1f}%'></div></div><b>{bullish:.1f}%</b></div>
692
+ <div class='bar-row'><span class='bar-label'>NEUTRAL</span><div class='bar-track'><div class='bar-fill neutral' style='width:{neutral:.1f}%'></div></div><b>{neutral:.1f}%</b></div>
693
+ <div class='bar-row'><span class='bar-label'>BEARISH</span><div class='bar-track'><div class='bar-fill bearish' style='width:{bearish:.1f}%'></div></div><b>{bearish:.1f}%</b></div>
694
+ </div>
695
+ <div class='score-box'>
696
+ <div class='score-label'>SENTIMENT SCORE</div>
697
+ <div class='score-track'><div class='score-mid'></div><div class='score-marker' style='left:{score_pct:.1f}%'></div></div>
698
+ <div class='score-axis'><span>-1.00 BEARISH</span><span>0</span><span>+1.00 BULLISH</span></div>
699
+ <div class='score-value'>{score:+.2f}</div>
700
+ </div>
701
+ <div class='mini-market'>
702
+ <svg viewBox='0 0 720 150' preserveAspectRatio='none' aria-label='financial market background chart'>
703
+ <polyline points='0,118 45,110 90,121 135,88 180,98 225,72 270,83 315,50 360,62 405,42 450,68 495,38 540,51 585,24 630,42 675,18 720,30' fill='none' stroke='currentColor' stroke-width='3' opacity='.75'/>
704
+ <polyline points='0,145 720,145' fill='none' stroke='currentColor' stroke-width='1' opacity='.18'/>
705
+ <polyline points='0,100 720,100' fill='none' stroke='currentColor' stroke-width='1' opacity='.12'/>
706
+ <polyline points='0,55 720,55' fill='none' stroke='currentColor' stroke-width='1' opacity='.12'/>
707
+ </svg>
708
+ </div>
709
+ </div>
710
+ """
711
+
712
+
713
+ # ============================================================
714
+ # DETAILED ANALYSIS
715
+ # ============================================================
716
+
717
+ def analyze_crypto(
718
+ text,
719
+ user
720
+ ):
721
+
722
+ start = time.perf_counter()
723
+
724
+ if not text or not text.strip():
725
+
726
+ return "Lütfen analiz edilecek metni gir.", ""
727
+
728
+ if user is None:
729
+
730
+ return "Önce giriş yapmalısın.", ""
731
+
732
+ current_user = get_user(
733
+ user["email"]
734
+ )
735
+
736
+ if not current_user:
737
+
738
+ return "Oturum bulunamadı.", ""
739
+
740
+ if not current_user["active"]:
741
+
742
+ return "Hesabın aktif değil.", ""
743
+
744
+ if not can_use(current_user):
745
+
746
+ limit = get_limit(
747
+ current_user["plan"]
748
+ )
749
+
750
+ return (
751
+ f"## ⚠️ Günlük Limit Doldu\n\n"
752
+ f"**Plan:** {current_user['plan'].upper()}\n\n"
753
+ f"**Limit:** {limit} analiz/gün",
754
+ ""
755
+ )
756
+
757
+
758
+ # --------------------------------------------------------
759
+ # MODEL
760
+ # --------------------------------------------------------
761
+
762
+ prompt = f"""
763
+ You are a cryptocurrency sentiment analysis model.
764
+
765
+ Analyze the following crypto market text.
766
+
767
+ Classify the sentiment as exactly one:
768
+ Bullish
769
+ Neutral
770
+ Bearish
771
+
772
+ Return the sentiment label clearly.
773
+
774
+ Text:
775
+ {text}
776
+
777
+ Sentiment:
778
+ """
779
+
780
+
781
+ inputs = tokenizer(
782
+ prompt,
783
+ return_tensors="pt",
784
+ truncation=True,
785
+ max_length=512
786
+ )
787
+
788
+ inputs = {
789
+ k: v.to(DEVICE)
790
+ for k, v in inputs.items()
791
+ }
792
+
793
+
794
+ with torch.no_grad():
795
+
796
+ output = model.generate(
797
+ **inputs,
798
+ max_new_tokens=20,
799
+ do_sample=False
800
+ )
801
+
802
+
803
+ generated = output[
804
+ 0
805
+ ][
806
+ inputs["input_ids"].shape[1]:
807
+ ]
808
+
809
+
810
+ raw_result = tokenizer.decode(
811
+ generated,
812
+ skip_special_tokens=True
813
+ ).strip()
814
+
815
+
816
+ # --------------------------------------------------------
817
+ # ANALYSIS
818
+ # --------------------------------------------------------
819
+
820
+ sentiment = extract_sentiment(
821
+ raw_result
822
+ +
823
+ " "
824
+ +
825
+ text
826
+ )
827
+
828
+ score = sentiment_score(
829
+ sentiment,
830
+ text
831
+ )
832
+
833
+ strength = get_strength(
834
+ score
835
+ )
836
+
837
+ risk = get_risk(
838
+ text,
839
+ score
840
+ )
841
+
842
+ impact = get_market_impact(
843
+ sentiment,
844
+ score
845
+ )
846
+
847
+ assets = detect_assets(
848
+ text
849
+ )
850
+
851
+
852
+ # --------------------------------------------------------
853
+ # DISPLAY SCORES
854
+ # --------------------------------------------------------
855
+
856
+ if sentiment == "Bullish":
857
+
858
+ bullish = 70 + abs(score) * 25
859
+ bearish = 10
860
+ neutral = 100 - bullish - bearish
861
+
862
+ elif sentiment == "Bearish":
863
+
864
+ bearish = 70 + abs(score) * 25
865
+ bullish = 10
866
+ neutral = 100 - bearish - bullish
867
+
868
+ else:
869
+
870
+ neutral = 60 + (1 - abs(score)) * 20
871
+ bullish = (100 - neutral) / 2
872
+ bearish = bullish
873
+
874
+
875
+ bullish = max(
876
+ 0,
877
+ min(
878
+ 100,
879
+ bullish
880
+ )
881
+ )
882
+
883
+ bearish = max(
884
+ 0,
885
+ min(
886
+ 100,
887
+ bearish
888
+ )
889
+ )
890
+
891
+ neutral = max(
892
+ 0,
893
+ 100 - bullish - bearish
894
+ )
895
+
896
+
897
+ # --------------------------------------------------------
898
+ # DIRECTION ICON
899
+ # --------------------------------------------------------
900
+
901
+ if sentiment == "Bullish":
902
+ icon = "🟢"
903
+
904
+ elif sentiment == "Bearish":
905
+ icon = "🔴"
906
+
907
+ else:
908
+ icon = "🟡"
909
+
910
+
911
+ elapsed = (
912
+ time.perf_counter()
913
+ - start
914
+ )
915
+
916
+
917
+ # --------------------------------------------------------
918
+ # USAGE
919
+ # --------------------------------------------------------
920
+
921
+ increment_usage(
922
+ current_user["id"]
923
+ )
924
+
925
+ new_user = get_user(
926
+ current_user["email"]
927
+ )
928
+
929
+
930
+ # --------------------------------------------------------
931
+ # ASSET TEXT
932
+ # --------------------------------------------------------
933
+
934
+ asset_text = (
935
+ ", ".join(assets)
936
+ if assets
937
+ else "Belirlenmedi"
938
+ )
939
+
940
+
941
+ # --------------------------------------------------------
942
+ # SCORE TEXT
943
+ # --------------------------------------------------------
944
+
945
+ score_text = (
946
+ f"{score:+.2f}"
947
+ )
948
+
949
+
950
+ # --------------------------------------------------------
951
+ # FINAL REPORT
952
+ # --------------------------------------------------------
953
+
954
+ report = f"""
955
+ # {icon} CRYPTO SENTIMENT ANALYSIS
956
+
957
+ ## Genel Sinyal
958
+
959
+ ### **{sentiment.upper()}**
960
+
961
+ | Metrik | Sonuç |
962
+ |---|---|
963
+ | **Sentiment** | {sentiment} |
964
+ | **Sentiment Score** | `{score_text}` |
965
+ | **Signal Strength** | **{strength}** |
966
+ | **Market Impact** | **{impact}** |
967
+ | **Risk Level** | **{risk}** |
968
+ | **Detected Asset** | **{asset_text}** |
969
+
970
+ ---
971
+
972
+ ## 📊 Sentiment Distribution
973
+
974
+ ### 🟢 Bullish — **{bullish:.1f}%**
975
+
976
+ `{bar(bullish)}`
977
+
978
+ ### 🟡 Neutral — **{neutral:.1f}%**
979
+
980
+ `{bar(neutral)}`
981
+
982
+ ### 🔴 Bearish — **{bearish:.1f}%**
983
+
984
+ `{bar(bearish)}`
985
+
986
+ > **Not:** Bu yüzdeler mevcut generative modelin gerçek
987
+ > softmax sınıf olasılıkları değildir; model çıktısı ve metin
988
+ > sinyallerinden oluşturulan yaklaşık dağılımdır.
989
+
990
+ ---
991
+
992
+ ## 🎯 Signal Interpretation
993
+
994
+ **Direction:** `{sentiment}`
995
+
996
+ **Strength:** `{strength}`
997
+
998
+ **Risk:** `{risk}`
999
+
1000
+ **Market Impact:** `{impact}`
1001
+
1002
+ ---
1003
+
1004
+ ## 🪙 Asset Detection
1005
+
1006
+ Detected assets:
1007
+
1008
+ **{asset_text}**
1009
+
1010
+ ---
1011
+
1012
+ ## 🤖 Model Output
1013
+
1014
+ ```text
1015
+ {raw_result if raw_result else "No explicit label returned."}
1016
+
1017
+ ```
1018
+
1019
+ """
1020
+ chart = build_finance_chart(
1021
+ bullish, neutral, bearish, score, sentiment, risk
1022
+ )
1023
+
1024
+ return report, chart
1025
+
1026
+
1027
+ # ============================================================
1028
+ # AUTHENTICATION / UI
1029
+ # ============================================================
1030
+
1031
+ def signup_action(email, password):
1032
+ ok, message = create_user(email, password)
1033
+ return message
1034
+
1035
+
1036
+ def login_action(email, password):
1037
+ user, message = login_user(email, password)
1038
+ if user is None:
1039
+ return None, f"❌ {message}"
1040
+ return dict(user), f"✅ {message} — {user['email']} ({user['plan'].upper()})"
1041
+
1042
+
1043
+ def logout_action():
1044
+ return None, "Çıkış yapıldı."
1045
+
1046
+
1047
+ def user_info(user):
1048
+ if not user:
1049
+ return "Giriş yapılmadı."
1050
+ current = get_user(user["email"])
1051
+ if not current:
1052
+ return "Oturum bulunamadı.", ""
1053
+ limit = get_limit(current["plan"])
1054
+ usage = current["daily_usage"] if current["usage_date"] == str(date.today()) else 0
1055
+ return (
1056
+ f"**{current['email']}** \\n"
1057
+ f"Plan: **{current['plan'].upper()}** \\n"
1058
+ f"Günlük kullanım: **{usage}/{limit}**"
1059
+ )
1060
+
1061
+
1062
+ def admin_users_action(user):
1063
+ if not user or user.get("plan") != "admin":
1064
+ return "❌ Admin yetkisi gerekli."
1065
+ conn = db()
1066
+ rows = conn.execute(
1067
+ "SELECT id, email, plan, active, daily_usage, usage_date, created_at FROM users ORDER BY id"
1068
+ ).fetchall()
1069
+ conn.close()
1070
+ if not rows:
1071
+ return "Kullanıcı yok."
1072
+ lines = ["| ID | Email | Plan | Active | Usage | Date |", "|---:|---|---|---:|---:|---|"]
1073
+ for r in rows:
1074
+ lines.append(
1075
+ f"| {r['id']} | {r['email']} | {r['plan']} | {r['active']} | {r['daily_usage']} | {r['usage_date'] or '-'} |"
1076
+ )
1077
+ return "\\n".join(lines)
1078
+
1079
+
1080
+ def admin_change_plan(user, email, plan):
1081
+ if not user or user.get("plan") != "admin":
1082
+ return "❌ Admin yetkisi gerekli."
1083
+ plan = (plan or "").lower().strip()
1084
+ if plan not in {"free", "pro", "admin"}:
1085
+ return "❌ Plan free, pro veya admin olmalı."
1086
+ target = get_user(email)
1087
+ if not target:
1088
+ return "❌ Kullanıcı bulunamadı."
1089
+ conn = db()
1090
+ conn.execute("UPDATE users SET plan = ? WHERE id = ?", (plan, target["id"]))
1091
+ conn.commit()
1092
+ conn.close()
1093
+ return f"✅ {email} → {plan}"
1094
+
1095
+
1096
+ def admin_set_active(user, email, active):
1097
+ if not user or user.get("plan") != "admin":
1098
+ return "❌ Admin yetkisi gerekli."
1099
+ target = get_user(email)
1100
+ if not target:
1101
+ return "❌ Kullanıcı bulunamadı."
1102
+ conn = db()
1103
+ conn.execute("UPDATE users SET active = ? WHERE id = ?", (1 if active else 0, target["id"]))
1104
+ conn.commit()
1105
+ conn.close()
1106
+ return f"✅ {email} → {'aktif' if active else 'pasif'}"
1107
+
1108
+
1109
+ def admin_delete(user, email):
1110
+ if not user or user.get("plan") != "admin":
1111
+ return "❌ Admin yetkisi gerekli."
1112
+ email = (email or "").lower().strip()
1113
+ if email == ADMIN_EMAIL.lower().strip():
1114
+ return "❌ Ana admin hesabı silinemez."
1115
+ target = get_user(email)
1116
+ if not target:
1117
+ return "❌ Kullanıcı bulunamadı."
1118
+ conn = db()
1119
+ conn.execute("DELETE FROM users WHERE id = ?", (target["id"],))
1120
+ conn.commit()
1121
+ conn.close()
1122
+ return f"✅ {email} silindi."
1123
+
1124
+
1125
+ # Initialize database before serving the UI.
1126
+ init_db()
1127
+
1128
+ LOGO_PATH = "Black Minimalist Letter Logo (2).png"
1129
+
1130
+ def logo_data_uri():
1131
+ if not os.path.exists(LOGO_PATH):
1132
+ return ""
1133
+ try:
1134
+ with open(LOGO_PATH, "rb") as f:
1135
+ encoded = base64.b64encode(f.read()).decode("ascii")
1136
+ return f"data:image/png;base64,{encoded}"
1137
+ except Exception:
1138
+ return ""
1139
+
1140
+ LOGO_URI = logo_data_uri()
1141
+
1142
+ FINANCE_CSS = """
1143
+ body, .gradio-container {
1144
+ background:
1145
+ radial-gradient(circle at 15% 15%, rgba(34,197,94,.10), transparent 28%),
1146
+ radial-gradient(circle at 85% 10%, rgba(59,130,246,.12), transparent 30%),
1147
+ linear-gradient(180deg, #07111f 0%, #0b1422 45%, #07101c 100%) !important;
1148
+ color: #e5eefb !important;
1149
+ }
1150
+ .gradio-container { max-width: 1250px !important; }
1151
+ .hero {
1152
+ position: relative; overflow: hidden; padding: 22px; margin-bottom: 18px;
1153
+ border: 1px solid rgba(148,163,184,.18); border-radius: 22px;
1154
+ background: linear-gradient(135deg, rgba(15,23,42,.96), rgba(10,25,43,.88));
1155
+ box-shadow: 0 20px 60px rgba(0,0,0,.28);
1156
+ }
1157
+ .hero:after {
1158
+ content: ""; position:absolute; inset:0; opacity:.12; pointer-events:none;
1159
+ background-image: linear-gradient(135deg, transparent 45%, #22c55e 46%, transparent 47%),
1160
+ linear-gradient(45deg, transparent 65%, #60a5fa 66%, transparent 67%);
1161
+ background-size: 120px 120px, 180px 180px;
1162
+ }
1163
+ .hero-inner { position:relative; z-index:2; display:flex; align-items:center; gap:18px; }
1164
+ .hero-logo { width:86px; height:86px; object-fit:contain; border-radius:16px; background:#000; padding:7px; }
1165
+ .hero h1 { margin:0; font-size:34px; letter-spacing:-1px; }
1166
+ .hero p { margin:7px 0 0; color:#9fb0c6; }
1167
+ .finance-chart { padding:22px; border-radius:18px; margin-top:16px; color:#dbeafe;
1168
+ background:linear-gradient(180deg, rgba(15,23,42,.98), rgba(8,18,31,.98));
1169
+ border:1px solid rgba(96,165,250,.18); box-shadow:0 18px 45px rgba(0,0,0,.25); }
1170
+ .chart-title { font-weight:800; letter-spacing:2px; font-size:15px; }
1171
+ .chart-subtitle { color:#8295ad; font-size:12px; margin:5px 0 20px; }
1172
+ .bar-row { display:grid; grid-template-columns:80px 1fr 62px; gap:12px; align-items:center; margin:12px 0; font-size:12px; }
1173
+ .bar-label { letter-spacing:1px; color:#94a3b8; }
1174
+ .bar-track { height:12px; background:#172235; border-radius:99px; overflow:hidden; }
1175
+ .bar-fill { height:100%; border-radius:99px; }
1176
+ .bullish { background:#22c55e; } .neutral { background:#eab308; } .bearish { background:#ef4444; }
1177
+ .score-box { margin-top:25px; padding-top:17px; border-top:1px solid rgba(148,163,184,.13); position:relative; }
1178
+ .score-label { font-size:11px; letter-spacing:1.5px; color:#94a3b8; }
1179
+ .score-track { position:relative; height:8px; margin:14px 0 5px; border-radius:99px; background:linear-gradient(90deg,#ef4444 0%,#334155 50%,#22c55e 100%); }
1180
+ .score-mid { position:absolute; left:50%; top:-4px; height:16px; width:1px; background:#e2e8f0; opacity:.5; }
1181
+ .score-marker { position:absolute; top:-5px; width:18px; height:18px; margin-left:-9px; border:3px solid #fff; border-radius:50%; background:#60a5fa; box-shadow:0 0 18px rgba(96,165,250,.7); }
1182
+ .score-axis { display:flex; justify-content:space-between; color:#64748b; font-size:10px; }
1183
+ .score-value { text-align:center; font-size:28px; font-weight:800; margin-top:8px; }
1184
+ .mini-market { margin-top:20px; color:#60a5fa; height:105px; opacity:.55; }
1185
+ .mini-market svg { width:100%; height:100%; }
1186
+ footer { display:none !important; }
1187
+ """
1188
+
1189
+ with gr.Blocks(title="Goshawk Vortex AI — Crypto Sentiment", css=FINANCE_CSS) as demo:
1190
+ session = gr.State(None)
1191
+
1192
+ gr.HTML(f"""
1193
+ <div class='hero'>
1194
+ <div class='hero-inner'>
1195
+ <img class='hero-logo' src='{LOGO_URI}' alt='Goshawk Vortex AI logo'>
1196
+ <div>
1197
+ <h1>Goshawk Vortex AI</h1>
1198
+ <p>Crypto Sentiment Intelligence • Market Signals • Risk Analytics</p>
1199
+ </div>
1200
+ </div>
1201
+ </div>
1202
+ """)
1203
+
1204
+ with gr.Tab("🔐 Hesap"):
1205
+ with gr.Row():
1206
+ with gr.Column():
1207
+ gr.Markdown("### Giriş")
1208
+ login_email = gr.Textbox(label="E-posta")
1209
+ login_password = gr.Textbox(label="Şifre", type="password")
1210
+ login_btn = gr.Button("Giriş Yap", variant="primary")
1211
+ logout_btn = gr.Button("Çıkış Yap")
1212
+ with gr.Column():
1213
+ gr.Markdown("### Kayıt")
1214
+ signup_email = gr.Textbox(label="E-posta")
1215
+ signup_password = gr.Textbox(label="Şifre", type="password")
1216
+ signup_btn = gr.Button("Hesap Oluştur")
1217
+
1218
+ auth_status = gr.Markdown("Giriş yapılmadı.")
1219
+ account_info = gr.Markdown("Giriş yapılmadı.")
1220
+
1221
+ login_btn.click(
1222
+ login_action,
1223
+ inputs=[login_email, login_password],
1224
+ outputs=[session, auth_status],
1225
+ ).then(user_info, inputs=session, outputs=account_info)
1226
+
1227
+ logout_btn.click(
1228
+ logout_action,
1229
+ outputs=[session, auth_status],
1230
+ ).then(user_info, inputs=session, outputs=account_info)
1231
+
1232
+ signup_btn.click(
1233
+ signup_action,
1234
+ inputs=[signup_email, signup_password],
1235
+ outputs=auth_status,
1236
+ )
1237
+
1238
+ with gr.Tab("📊 Sentiment Analysis"):
1239
+ analysis_text = gr.Textbox(
1240
+ label="Crypto market text",
1241
+ placeholder="Örn: Bitcoin breaks resistance as institutional inflows increase...",
1242
+ lines=8,
1243
+ )
1244
+ analyze_btn = gr.Button("🔎 Analiz Et", variant="primary")
1245
+ report_output = gr.Markdown()
1246
+ chart_output = gr.HTML()
1247
+ analyze_btn.click(
1248
+ analyze_crypto,
1249
+ inputs=[analysis_text, session],
1250
+ outputs=[report_output, chart_output],
1251
+ )
1252
+
1253
+ with gr.Tab("👑 Admin"):
1254
+ gr.Markdown("Admin işlemleri yalnızca admin planındaki oturumla çalışır.")
1255
+ refresh_btn = gr.Button("Kullanıcıları Yenile")
1256
+ users_output = gr.Markdown()
1257
+ refresh_btn.click(admin_users_action, inputs=session, outputs=users_output)
1258
+
1259
+ with gr.Row():
1260
+ admin_email_input = gr.Textbox(label="Hedef e-posta")
1261
+ admin_plan = gr.Dropdown(["free", "pro", "admin"], value="free", label="Yeni plan")
1262
+ change_plan_btn = gr.Button("Planı Değiştir")
1263
+ activate_btn = gr.Button("Aktif Et")
1264
+ deactivate_btn = gr.Button("Pasif Et")
1265
+ delete_btn = gr.Button("Kullanıcıyı Sil")
1266
+ admin_status = gr.Markdown()
1267
+
1268
+ change_plan_btn.click(
1269
+ admin_change_plan,
1270
+ inputs=[session, admin_email_input, admin_plan],
1271
+ outputs=admin_status,
1272
+ ).then(admin_users_action, inputs=session, outputs=users_output)
1273
+
1274
+ activate_btn.click(
1275
+ admin_set_active,
1276
+ inputs=[session, admin_email_input, gr.State(True)],
1277
+ outputs=admin_status,
1278
+ ).then(admin_users_action, inputs=session, outputs=users_output)
1279
+
1280
+ deactivate_btn.click(
1281
+ admin_set_active,
1282
+ inputs=[session, admin_email_input, gr.State(False)],
1283
+ outputs=admin_status,
1284
+ ).then(admin_users_action, inputs=session, outputs=users_output)
1285
+
1286
+ delete_btn.click(
1287
+ admin_delete,
1288
+ inputs=[session, admin_email_input],
1289
+ outputs=admin_status,
1290
+ ).then(admin_users_action, inputs=session, outputs=users_output)
1291
+
1292
+ gr.Markdown(
1293
+ "⚠️ Sentiment dağılımı mevcut generative PEFT modelinden gerçek softmax sınıf olasılığı değildir."
1294
+ )
1295
+
1296
+
1297
+ if __name__ == "__main__":
1298
+ demo.launch()