GoshawkVortexAI commited on
Commit
d4095e6
·
verified ·
1 Parent(s): 07fe456

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +547 -466
app.py CHANGED
@@ -1,5 +1,6 @@
1
  import os
2
  import re
 
3
  import sqlite3
4
  import hashlib
5
  import secrets
@@ -23,8 +24,15 @@ DB_PATH = "users.db"
23
  FREE_DAILY_LIMIT = 10
24
  PRO_DAILY_LIMIT = 1000
25
 
26
- ADMIN_EMAIL = os.getenv("ADMIN_EMAIL", "admin@example.com")
27
- ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "CHANGE_THIS_PASSWORD")
 
 
 
 
 
 
 
28
 
29
 
30
  # ============================================================
@@ -32,7 +40,10 @@ ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "CHANGE_THIS_PASSWORD")
32
  # ============================================================
33
 
34
  def db():
35
- conn = sqlite3.connect(DB_PATH, check_same_thread=False)
 
 
 
36
  conn.row_factory = sqlite3.Row
37
  return conn
38
 
@@ -57,21 +68,23 @@ def init_db():
57
 
58
  conn.commit()
59
 
60
- # Admin hesabını otomatik oluştur
61
  existing = conn.execute(
62
  "SELECT id FROM users WHERE email = ?",
63
- (ADMIN_EMAIL,)
64
  ).fetchone()
65
 
66
  if not existing:
67
- password_hash, salt = hash_password(ADMIN_PASSWORD)
 
 
 
68
 
69
  conn.execute("""
70
  INSERT INTO users
71
  (email, password_hash, salt, plan, active)
72
  VALUES (?, ?, ?, 'admin', 1)
73
  """, (
74
- ADMIN_EMAIL,
75
  password_hash,
76
  salt
77
  ))
@@ -82,7 +95,7 @@ def init_db():
82
 
83
 
84
  # ============================================================
85
- # PASSWORD SECURITY
86
  # ============================================================
87
 
88
  def hash_password(password, salt=None):
@@ -100,9 +113,16 @@ def hash_password(password, salt=None):
100
  return password_hash, salt
101
 
102
 
103
- def verify_password(password, stored_hash, salt):
 
 
 
 
104
 
105
- calculated, _ = hash_password(password, salt)
 
 
 
106
 
107
  return secrets.compare_digest(
108
  calculated,
@@ -111,7 +131,7 @@ def verify_password(password, stored_hash, salt):
111
 
112
 
113
  # ============================================================
114
- # USER FUNCTIONS
115
  # ============================================================
116
 
117
  def get_user(email):
@@ -161,7 +181,7 @@ def create_user(email, password):
161
  conn.commit()
162
  conn.close()
163
 
164
- return True, "Hesap oluşturuldu. Giriş yapabilirsin."
165
 
166
 
167
  def login_user(email, password):
@@ -181,7 +201,6 @@ def login_user(email, password):
181
  if not user["active"]:
182
  return None, "Hesabın pasif durumda."
183
 
184
- # Günlük kullanım reset
185
  today = str(date.today())
186
 
187
  if user["usage_date"] != today:
@@ -207,7 +226,7 @@ def login_user(email, password):
207
 
208
 
209
  # ============================================================
210
- # USAGE LIMIT
211
  # ============================================================
212
 
213
  def get_limit(plan):
@@ -228,9 +247,11 @@ def can_use(user):
228
  if user["usage_date"] != today:
229
  return True
230
 
231
- limit = get_limit(user["plan"])
232
-
233
- return user["daily_usage"] < limit
 
 
234
 
235
 
236
  def increment_usage(user_id):
@@ -257,17 +278,34 @@ def increment_usage(user_id):
257
 
258
  print("Loading model...")
259
 
260
- DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
 
 
 
 
261
 
262
  print("Device:", DEVICE)
263
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  tokenizer = AutoTokenizer.from_pretrained(
265
  MODEL_PATH
266
  )
267
 
268
  model = AutoPeftModelForCausalLM.from_pretrained(
269
  MODEL_PATH,
270
- torch_dtype=(
271
  torch.float16
272
  if DEVICE == "cuda"
273
  else torch.float32
@@ -281,611 +319,654 @@ print("Model loaded.")
281
 
282
 
283
  # ============================================================
284
- # SENTIMENT
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  # ============================================================
286
 
287
  def extract_sentiment(text):
288
 
289
- text_lower = text.lower()
290
 
291
- if "bullish" in text_lower:
 
 
 
 
292
  return "Bullish"
293
 
294
- if "bearish" in text_lower:
 
 
 
295
  return "Bearish"
296
 
297
- if "neutral" in text_lower:
 
 
 
298
  return "Neutral"
299
 
300
- return "Unknown"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
 
303
- def analyze_crypto(text, user):
 
 
 
304
 
305
- if not text or not text.strip():
306
- return "Metin gir."
 
 
307
 
308
- # Kullanıcıyı tekrar DB'den kontrol et
309
- current_user = get_user(user["email"])
310
 
311
- if not current_user:
312
- return "Oturum bulunamadı."
313
 
314
- if not current_user["active"]:
315
- return "Hesabın aktif değil."
316
 
317
- if not can_use(current_user):
318
 
319
- limit = get_limit(current_user["plan"])
 
 
320
 
321
- return (
322
- f"Günlük kullanım limitine ulaştın.\n\n"
323
- f"Plan: {current_user['plan'].upper()}\n"
324
- f"Limit: {limit}"
325
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
326
 
327
- prompt = f"""
328
- You are a cryptocurrency financial sentiment classifier.
 
 
 
 
 
 
 
 
329
 
330
- Classify the following cryptocurrency-related text into exactly one
331
- of these labels:
 
 
332
 
333
- Bullish
334
- Neutral
335
- Bearish
 
336
 
337
- Return only one label.
338
 
339
- Text:
340
- {text}
341
 
342
- Sentiment:
343
- """
344
 
345
- inputs = tokenizer(
346
- prompt,
347
- return_tensors="pt",
348
- truncation=True,
349
- max_length=512
 
350
  )
351
 
352
- inputs = {
353
- k: v.to(DEVICE)
354
- for k, v in inputs.items()
355
- }
356
 
357
- with torch.no_grad():
358
 
359
- output = model.generate(
360
- **inputs,
361
- max_new_tokens=10,
362
- do_sample=False
363
- )
364
 
365
- generated = output[
366
- 0
367
- ][
368
- inputs["input_ids"].shape[1]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  ]
370
 
371
- result = tokenizer.decode(
372
- generated,
373
- skip_special_tokens=True
374
- ).strip()
 
 
 
375
 
376
- sentiment = extract_sentiment(result)
 
 
 
 
377
 
378
- increment_usage(current_user["id"])
 
 
 
 
379
 
380
- if sentiment == "Unknown":
381
- return (
382
- "Model sonucu:\n\n"
383
- + result
384
- )
385
 
386
- return sentiment
387
 
388
 
389
  # ============================================================
390
- # AUTH UI
391
  # ============================================================
392
 
393
- def signup(email, password):
 
 
 
394
 
395
- success, message = create_user(
396
- email,
397
- password
398
- )
399
 
400
- return message
 
401
 
 
 
402
 
403
- def login(email, password):
 
404
 
405
- user, message = login_user(
406
- email,
407
- password
408
- )
409
 
410
- if not user:
411
- return (
412
- message,
413
- None,
414
- gr.update(visible=True),
415
- gr.update(visible=False),
416
- gr.update(visible=False),
417
- ""
418
- )
419
 
420
- admin_visible = (
421
- user["plan"] == "admin"
422
- )
423
 
424
- status = (
425
- f"Hoş geldin: {user['email']}\n"
426
- f"Plan: {user['plan'].upper()}\n"
427
- f"Kullanım: {user['daily_usage']}/"
428
- f"{get_limit(user['plan'])}"
429
- )
430
 
431
- return (
432
- status,
433
- dict(user),
434
- gr.update(visible=False),
435
- gr.update(visible=True),
436
- gr.update(visible=admin_visible),
437
- ""
438
- )
439
 
 
 
 
 
 
 
 
440
 
441
- def logout():
 
 
442
 
443
  return (
444
- "",
445
- None,
446
- gr.update(visible=True),
447
- gr.update(visible=False),
448
- gr.update(visible=False),
449
- ""
450
  )
451
 
452
 
453
  # ============================================================
454
- # ADMIN
455
  # ============================================================
456
 
457
- def get_users_dataframe():
458
-
459
- conn = db()
 
460
 
461
- rows = conn.execute("""
462
- SELECT
463
- id,
464
- email,
465
- plan,
466
- active,
467
- daily_usage,
468
- created_at
469
- FROM users
470
- ORDER BY id DESC
471
- """).fetchall()
472
 
473
- conn.close()
474
 
475
- data = []
476
 
477
- for row in rows:
478
 
479
- data.append([
480
- row["id"],
481
- row["email"],
482
- row["plan"],
483
- "Active" if row["active"] else "Inactive",
484
- row["daily_usage"],
485
- row["created_at"]
486
- ])
487
 
488
- return data
 
 
489
 
 
490
 
491
- def refresh_users():
492
 
493
- return get_users_dataframe()
494
 
 
495
 
496
- def admin_change_plan(user_id, plan):
497
 
498
- try:
499
- user_id = int(user_id)
500
- except:
501
- return "Geçersiz kullanıcı ID."
502
 
503
- if plan not in [
504
- "free",
505
- "pro",
506
- "admin"
507
- ]:
508
- return "Geçersiz plan."
509
 
510
- conn = db()
511
 
512
- conn.execute("""
513
- UPDATE users
514
- SET plan = ?
515
- WHERE id = ?
516
- """, (
517
- plan,
518
- user_id
519
- ))
520
 
521
- conn.commit()
522
- conn.close()
523
 
524
- return f"Kullanıcı {user_id} → {plan}"
525
 
 
 
 
 
526
 
527
- def admin_toggle_user(user_id):
528
 
529
- try:
530
- user_id = int(user_id)
531
- except:
532
- return "Geçersiz kullanıcı ID."
533
 
534
- conn = db()
 
535
 
536
- user = conn.execute(
537
- "SELECT active FROM users WHERE id = ?",
538
- (user_id,)
539
- ).fetchone()
540
 
541
- if not user:
542
- conn.close()
543
- return "Kullanıcı bulunamadı."
 
 
 
544
 
545
- new_status = 0 if user["active"] else 1
 
 
 
546
 
547
- conn.execute("""
548
- UPDATE users
549
- SET active = ?
550
- WHERE id = ?
551
- """, (
552
- new_status,
553
- user_id
554
- ))
555
 
556
- conn.commit()
557
- conn.close()
558
 
559
- return (
560
- f"Kullanıcı {user_id}: "
561
- + (
562
- "Aktif"
563
- if new_status
564
- else "Pasif"
565
  )
566
- )
567
 
568
 
569
- def admin_delete_user(user_id):
 
 
 
 
570
 
571
- try:
572
- user_id = int(user_id)
573
- except:
574
- return "Geçersiz kullanıcı ID."
575
 
576
- conn = db()
 
 
 
577
 
578
- user = conn.execute(
579
- "SELECT email FROM users WHERE id = ?",
580
- (user_id,)
581
- ).fetchone()
582
 
583
- if not user:
584
- conn.close()
585
- return "Kullanıcı bulunamadı."
586
 
587
- if user["email"] == ADMIN_EMAIL:
588
- conn.close()
589
- return "Ana admin silinemez."
 
 
 
 
590
 
591
- conn.execute(
592
- "DELETE FROM users WHERE id = ?",
593
- (user_id,)
594
  )
595
 
596
- conn.commit()
597
- conn.close()
 
598
 
599
- return f"Kullanıcı {user_id} silindi."
 
 
 
600
 
 
 
 
 
601
 
602
- # ============================================================
603
- # INITIALIZE
604
- # ============================================================
605
 
606
- init_db()
607
 
 
 
 
608
 
609
- # ============================================================
610
- # GRADIO
611
- # ============================================================
612
 
613
- with gr.Blocks(
614
- title="CryptoBERT AI"
615
- ) as demo:
616
 
617
- session = gr.State(None)
618
 
619
- # ========================================================
620
- # LOGIN
621
- # ========================================================
622
 
623
- with gr.Column(
624
- visible=True
625
- ) as login_panel:
626
 
627
- gr.Markdown(
628
- """
629
- # 🚀 CryptoBERT AI
630
 
631
- Kripto piyasası sentiment analiz sistemi.
632
 
633
- **Bullish / Neutral / Bearish**
634
- """
 
 
 
635
  )
 
636
 
637
- email = gr.Textbox(
638
- label="E-posta",
639
- placeholder="email@example.com"
 
 
640
  )
 
641
 
642
- password = gr.Textbox(
643
- label="Şifre",
644
- type="password"
645
- )
646
 
647
- with gr.Row():
648
 
649
- login_btn = gr.Button(
650
- "Giriş Yap",
651
- variant="primary"
652
- )
653
 
654
- signup_btn = gr.Button(
655
- "Kayıt Ol"
656
- )
657
 
658
- auth_message = gr.Markdown()
 
659
 
 
 
660
 
661
- # ========================================================
662
- # APP
663
- # ========================================================
664
 
665
- with gr.Column(
666
- visible=False
667
- ) as app_panel:
 
668
 
669
- with gr.Row():
670
 
671
- user_status = gr.Markdown()
 
 
672
 
673
- logout_btn = gr.Button(
674
- "Çıkış"
675
- )
676
 
677
- gr.Markdown(
678
- "## 📊 Crypto Sentiment Analysis"
679
- )
680
 
681
- text_input = gr.Textbox(
682
- label="Kripto metni",
683
- placeholder=(
684
- "Bitcoin ETF inflows increased "
685
- "sharply today..."
686
- ),
687
- lines=7
688
- )
689
 
690
- analyze_btn = gr.Button(
691
- "🔍 Analiz Et",
692
- variant="primary"
693
- )
694
 
695
- sentiment_output = gr.Textbox(
696
- label="Sentiment"
697
- )
 
 
698
 
699
 
700
- # ====================================================
701
- # ADMIN PANEL
702
- # ====================================================
703
 
704
- with gr.Column(
705
- visible=False
706
- ) as admin_panel:
707
 
708
- gr.Markdown(
709
- """
710
- # 👑 Admin Panel
711
 
712
- Kullanıcıları buradan yönetebilirsin.
713
- """
714
- )
715
 
716
- users_table = gr.Dataframe(
717
- headers=[
718
- "ID",
719
- "Email",
720
- "Plan",
721
- "Status",
722
- "Daily Usage",
723
- "Created"
724
- ],
725
- datatype=[
726
- "number",
727
- "str",
728
- "str",
729
- "str",
730
- "number",
731
- "str"
732
- ],
733
- value=get_users_dataframe(),
734
- interactive=False
735
- )
736
 
737
- refresh_btn = gr.Button(
738
- "🔄 Kullanıcıları Yenile"
739
- )
740
 
741
- gr.Markdown(
742
- "### Üyelik Değiştir"
743
- )
744
 
745
- plan_user_id = gr.Number(
746
- label="User ID"
747
- )
 
 
 
 
 
748
 
749
- plan_dropdown = gr.Dropdown(
750
- choices=[
751
- "free",
752
- "pro",
753
- "admin"
754
- ],
755
- value="pro",
756
- label="Yeni Plan"
757
- )
758
 
759
- change_plan_btn = gr.Button(
760
- "Planı Değiştir"
761
- )
762
 
763
- admin_message = gr.Markdown()
764
 
765
- gr.Markdown(
766
- "### Kullanıcı Aktif / Pasif"
767
- )
768
 
769
- toggle_user_id = gr.Number(
770
- label="User ID"
771
- )
772
 
773
- toggle_btn = gr.Button(
774
- "Aktif / Pasif Değiştir"
775
- )
776
 
777
- gr.Markdown(
778
- "### Kullanıcı Sil"
779
- )
780
 
781
- delete_user_id = gr.Number(
782
- label="User ID"
783
- )
784
 
785
- delete_btn = gr.Button(
786
- "🗑️ Kullanıcıyı Sil",
787
- variant="stop"
788
- )
789
 
 
790
 
791
- # ========================================================
792
- # EVENTS
793
- # ========================================================
794
-
795
- login_btn.click(
796
- login,
797
- inputs=[
798
- email,
799
- password
800
- ],
801
- outputs=[
802
- auth_message,
803
- session,
804
- login_panel,
805
- app_panel,
806
- admin_panel,
807
- password
808
- ]
809
- )
810
 
811
- signup_btn.click(
812
- signup,
813
- inputs=[
814
- email,
815
- password
816
- ],
817
- outputs=[
818
- auth_message
819
- ]
820
- )
821
 
822
- logout_btn.click(
823
- logout,
824
- outputs=[
825
- user_status,
826
- session,
827
- login_panel,
828
- app_panel,
829
- admin_panel,
830
- sentiment_output
831
- ]
832
- )
833
 
834
- analyze_btn.click(
835
- analyze_crypto,
836
- inputs=[
837
- text_input,
838
- session
839
- ],
840
- outputs=[
841
- sentiment_output
842
- ]
843
- )
844
 
845
- refresh_btn.click(
846
- refresh_users,
847
- outputs=[
848
- users_table
849
- ]
850
- )
851
 
852
- change_plan_btn.click(
853
- admin_change_plan,
854
- inputs=[
855
- plan_user_id,
856
- plan_dropdown
857
- ],
858
- outputs=[
859
- admin_message
860
- ]
861
- )
862
 
863
- toggle_btn.click(
864
- admin_toggle_user,
865
- inputs=[
866
- toggle_user_id
867
- ],
868
- outputs=[
869
- admin_message
870
- ]
871
- )
872
 
873
- delete_btn.click(
874
- admin_delete_user,
875
- inputs=[
876
- delete_user_id
877
- ],
878
- outputs=[
879
- admin_message
880
- ]
881
- )
882
 
 
883
 
884
- # ============================================================
885
- # START
886
- # ============================================================
887
 
888
- demo.launch(
889
- server_name="0.0.0.0",
890
- server_port=7860
891
- )
 
1
  import os
2
  import re
3
+ import time
4
  import sqlite3
5
  import hashlib
6
  import secrets
 
24
  FREE_DAILY_LIMIT = 10
25
  PRO_DAILY_LIMIT = 1000
26
 
27
+ ADMIN_EMAIL = os.getenv(
28
+ "ADMIN_EMAIL",
29
+ "admin@example.com"
30
+ )
31
+
32
+ ADMIN_PASSWORD = os.getenv(
33
+ "ADMIN_PASSWORD",
34
+ "CHANGE_THIS_PASSWORD"
35
+ )
36
 
37
 
38
  # ============================================================
 
40
  # ============================================================
41
 
42
  def db():
43
+ conn = sqlite3.connect(
44
+ DB_PATH,
45
+ check_same_thread=False
46
+ )
47
  conn.row_factory = sqlite3.Row
48
  return conn
49
 
 
68
 
69
  conn.commit()
70
 
 
71
  existing = conn.execute(
72
  "SELECT id FROM users WHERE email = ?",
73
+ (ADMIN_EMAIL.lower().strip(),)
74
  ).fetchone()
75
 
76
  if not existing:
77
+
78
+ password_hash, salt = hash_password(
79
+ ADMIN_PASSWORD
80
+ )
81
 
82
  conn.execute("""
83
  INSERT INTO users
84
  (email, password_hash, salt, plan, active)
85
  VALUES (?, ?, ?, 'admin', 1)
86
  """, (
87
+ ADMIN_EMAIL.lower().strip(),
88
  password_hash,
89
  salt
90
  ))
 
95
 
96
 
97
  # ============================================================
98
+ # PASSWORD
99
  # ============================================================
100
 
101
  def hash_password(password, salt=None):
 
113
  return password_hash, salt
114
 
115
 
116
+ def verify_password(
117
+ password,
118
+ stored_hash,
119
+ salt
120
+ ):
121
 
122
+ calculated, _ = hash_password(
123
+ password,
124
+ salt
125
+ )
126
 
127
  return secrets.compare_digest(
128
  calculated,
 
131
 
132
 
133
  # ============================================================
134
+ # USERS
135
  # ============================================================
136
 
137
  def get_user(email):
 
181
  conn.commit()
182
  conn.close()
183
 
184
+ return True, "Hesap oluşturuldu."
185
 
186
 
187
  def login_user(email, password):
 
201
  if not user["active"]:
202
  return None, "Hesabın pasif durumda."
203
 
 
204
  today = str(date.today())
205
 
206
  if user["usage_date"] != today:
 
226
 
227
 
228
  # ============================================================
229
+ # LIMIT
230
  # ============================================================
231
 
232
  def get_limit(plan):
 
247
  if user["usage_date"] != today:
248
  return True
249
 
250
+ return (
251
+ user["daily_usage"]
252
+ <
253
+ get_limit(user["plan"])
254
+ )
255
 
256
 
257
  def increment_usage(user_id):
 
278
 
279
  print("Loading model...")
280
 
281
+ DEVICE = (
282
+ "cuda"
283
+ if torch.cuda.is_available()
284
+ else "cpu"
285
+ )
286
 
287
  print("Device:", DEVICE)
288
 
289
+ adapter_file = os.path.join(
290
+ MODEL_PATH,
291
+ "adapter_model.safetensors"
292
+ )
293
+
294
+ if not os.path.exists(adapter_file):
295
+
296
+ raise RuntimeError(
297
+ "adapter_model.safetensors bulunamadı. "
298
+ "PEFT adapter ağırlığını Space'e yükle."
299
+ )
300
+
301
+
302
  tokenizer = AutoTokenizer.from_pretrained(
303
  MODEL_PATH
304
  )
305
 
306
  model = AutoPeftModelForCausalLM.from_pretrained(
307
  MODEL_PATH,
308
+ dtype=(
309
  torch.float16
310
  if DEVICE == "cuda"
311
  else torch.float32
 
319
 
320
 
321
  # ============================================================
322
+ # CRYPTO ASSET DETECTION
323
+ # ============================================================
324
+
325
+ KNOWN_ASSETS = {
326
+
327
+ "BTC": [
328
+ "bitcoin",
329
+ "btc"
330
+ ],
331
+
332
+ "ETH": [
333
+ "ethereum",
334
+ "eth"
335
+ ],
336
+
337
+ "SOL": [
338
+ "solana",
339
+ "sol"
340
+ ],
341
+
342
+ "BNB": [
343
+ "binance coin",
344
+ "bnb"
345
+ ],
346
+
347
+ "XRP": [
348
+ "ripple",
349
+ "xrp"
350
+ ],
351
+
352
+ "ADA": [
353
+ "cardano",
354
+ "ada"
355
+ ],
356
+
357
+ "DOGE": [
358
+ "dogecoin",
359
+ "doge"
360
+ ],
361
+
362
+ "AVAX": [
363
+ "avalanche",
364
+ "avax"
365
+ ],
366
+
367
+ "LINK": [
368
+ "chainlink",
369
+ "link"
370
+ ],
371
+
372
+ "DOT": [
373
+ "polkadot",
374
+ "dot"
375
+ ]
376
+ }
377
+
378
+
379
+ def detect_assets(text):
380
+
381
+ lower = text.lower()
382
+
383
+ detected = []
384
+
385
+ for symbol, names in KNOWN_ASSETS.items():
386
+
387
+ for name in names:
388
+
389
+ pattern = (
390
+ r"(?<![a-z])"
391
+ + re.escape(name)
392
+ + r"(?![a-z])"
393
+ )
394
+
395
+ if re.search(pattern, lower):
396
+
397
+ detected.append(symbol)
398
+ break
399
+
400
+ return detected
401
+
402
+
403
+ # ============================================================
404
+ # SENTIMENT EXTRACTION
405
  # ============================================================
406
 
407
  def extract_sentiment(text):
408
 
409
+ lower = text.lower()
410
 
411
+ # Strong explicit labels
412
+ if re.search(
413
+ r"\bbullish\b|\bpositive\b",
414
+ lower
415
+ ):
416
  return "Bullish"
417
 
418
+ if re.search(
419
+ r"\bbearish\b|\bnegative\b",
420
+ lower
421
+ ):
422
  return "Bearish"
423
 
424
+ if re.search(
425
+ r"\bneutral\b|\bsideways\b",
426
+ lower
427
+ ):
428
  return "Neutral"
429
 
430
+ # Keyword fallback
431
+ bullish_words = [
432
+ "rise",
433
+ "rising",
434
+ "surge",
435
+ "surging",
436
+ "gain",
437
+ "gains",
438
+ "growth",
439
+ "breakout",
440
+ "bull",
441
+ "buy",
442
+ "buying",
443
+ "inflow",
444
+ "inflows",
445
+ "adoption",
446
+ "strong",
447
+ "record high",
448
+ "higher"
449
+ ]
450
 
451
+ bearish_words = [
452
+ "fall",
453
+ "falling",
454
+ "drop",
455
+ "dropping",
456
+ "crash",
457
+ "loss",
458
+ "losses",
459
+ "sell",
460
+ "selling",
461
+ "outflow",
462
+ "outflows",
463
+ "bear",
464
+ "resistance",
465
+ "hack",
466
+ "liquidation",
467
+ "lower",
468
+ "weak"
469
+ ]
470
 
471
+ bullish_score = sum(
472
+ word in lower
473
+ for word in bullish_words
474
+ )
475
 
476
+ bearish_score = sum(
477
+ word in lower
478
+ for word in bearish_words
479
+ )
480
 
481
+ if bullish_score > bearish_score:
482
+ return "Bullish"
483
 
484
+ if bearish_score > bullish_score:
485
+ return "Bearish"
486
 
487
+ return "Neutral"
 
488
 
 
489
 
490
+ # ============================================================
491
+ # SENTIMENT SCORE
492
+ # ============================================================
493
 
494
+ def sentiment_score(
495
+ sentiment,
496
+ text
497
+ ):
498
+
499
+ lower = text.lower()
500
+
501
+ bullish_words = [
502
+ "surge",
503
+ "breakout",
504
+ "bullish",
505
+ "strong",
506
+ "inflow",
507
+ "growth",
508
+ "rally",
509
+ "higher",
510
+ "buy"
511
+ ]
512
 
513
+ bearish_words = [
514
+ "crash",
515
+ "bearish",
516
+ "weak",
517
+ "outflow",
518
+ "liquidation",
519
+ "hack",
520
+ "lower",
521
+ "sell"
522
+ ]
523
 
524
+ bull = sum(
525
+ x in lower
526
+ for x in bullish_words
527
+ )
528
 
529
+ bear = sum(
530
+ x in lower
531
+ for x in bearish_words
532
+ )
533
 
534
+ raw = bull - bear
535
 
536
+ if sentiment == "Bullish":
537
+ raw += 1
538
 
539
+ elif sentiment == "Bearish":
540
+ raw -= 1
541
 
542
+ score = max(
543
+ -1,
544
+ min(
545
+ 1,
546
+ raw / 5
547
+ )
548
  )
549
 
550
+ return score
 
 
 
551
 
 
552
 
553
+ # ============================================================
554
+ # STRENGTH
555
+ # ============================================================
 
 
556
 
557
+ def get_strength(score):
558
+
559
+ absolute = abs(score)
560
+
561
+ if absolute >= 0.70:
562
+ return "STRONG"
563
+
564
+ if absolute >= 0.35:
565
+ return "MODERATE"
566
+
567
+ return "WEAK"
568
+
569
+
570
+ # ============================================================
571
+ # RISK
572
+ # ============================================================
573
+
574
+ def get_risk(
575
+ text,
576
+ score
577
+ ):
578
+
579
+ lower = text.lower()
580
+
581
+ high_risk_words = [
582
+ "hack",
583
+ "exploit",
584
+ "crash",
585
+ "liquidation",
586
+ "lawsuit",
587
+ "ban",
588
+ "bankruptcy",
589
+ "rug"
590
  ]
591
 
592
+ medium_risk_words = [
593
+ "volatility",
594
+ "resistance",
595
+ "uncertain",
596
+ "uncertainty",
597
+ "regulation"
598
+ ]
599
 
600
+ if any(
601
+ word in lower
602
+ for word in high_risk_words
603
+ ):
604
+ return "HIGH"
605
 
606
+ if any(
607
+ word in lower
608
+ for word in medium_risk_words
609
+ ):
610
+ return "MEDIUM"
611
 
612
+ if abs(score) > 0.7:
613
+ return "MEDIUM"
 
 
 
614
 
615
+ return "LOW"
616
 
617
 
618
  # ============================================================
619
+ # MARKET IMPACT
620
  # ============================================================
621
 
622
+ def get_market_impact(
623
+ sentiment,
624
+ score
625
+ ):
626
 
627
+ if abs(score) >= 0.70:
628
+ strength = "High"
 
 
629
 
630
+ elif abs(score) >= 0.35:
631
+ strength = "Moderate"
632
 
633
+ else:
634
+ strength = "Low"
635
 
636
+ if sentiment == "Bullish":
637
+ return f"Positive ({strength})"
638
 
639
+ if sentiment == "Bearish":
640
+ return f"Negative ({strength})"
 
 
641
 
642
+ return f"Neutral ({strength})"
 
 
 
 
 
 
 
 
643
 
 
 
 
644
 
645
+ # ============================================================
646
+ # SIGNAL BAR
647
+ # ============================================================
 
 
 
648
 
649
+ def bar(value):
 
 
 
 
 
 
 
650
 
651
+ value = max(
652
+ 0,
653
+ min(
654
+ 100,
655
+ int(value)
656
+ )
657
+ )
658
 
659
+ filled = int(
660
+ value / 5
661
+ )
662
 
663
  return (
664
+ "█" * filled
665
+ +
666
+ "░" * (20 - filled)
 
 
 
667
  )
668
 
669
 
670
  # ============================================================
671
+ # DETAILED ANALYSIS
672
  # ============================================================
673
 
674
+ def analyze_crypto(
675
+ text,
676
+ user
677
+ ):
678
 
679
+ start = time.perf_counter()
 
 
 
 
 
 
 
 
 
 
680
 
681
+ if not text or not text.strip():
682
 
683
+ return "Lütfen analiz edilecek metni gir."
684
 
685
+ if user is None:
686
 
687
+ return "Önce giriş yapmalısın."
 
 
 
 
 
 
 
688
 
689
+ current_user = get_user(
690
+ user["email"]
691
+ )
692
 
693
+ if not current_user:
694
 
695
+ return "Oturum bulunamadı."
696
 
697
+ if not current_user["active"]:
698
 
699
+ return "Hesabın aktif değil."
700
 
701
+ if not can_use(current_user):
702
 
703
+ limit = get_limit(
704
+ current_user["plan"]
705
+ )
 
706
 
707
+ return (
708
+ f"## ⚠️ Günlük Limit Doldu\n\n"
709
+ f"**Plan:** "
710
+ f"{current_user['plan'].upper()}\n\n"
711
+ f"**Limit:** {limit} analiz/gün"
712
+ )
713
 
 
714
 
715
+ # --------------------------------------------------------
716
+ # MODEL
717
+ # --------------------------------------------------------
 
 
 
 
 
718
 
719
+ prompt = f"""
720
+ You are a cryptocurrency sentiment analysis model.
721
 
722
+ Analyze the following crypto market text.
723
 
724
+ Classify the sentiment as exactly one:
725
+ Bullish
726
+ Neutral
727
+ Bearish
728
 
729
+ Return the sentiment label clearly.
730
 
731
+ Text:
732
+ {text}
 
 
733
 
734
+ Sentiment:
735
+ """
736
 
 
 
 
 
737
 
738
+ inputs = tokenizer(
739
+ prompt,
740
+ return_tensors="pt",
741
+ truncation=True,
742
+ max_length=512
743
+ )
744
 
745
+ inputs = {
746
+ k: v.to(DEVICE)
747
+ for k, v in inputs.items()
748
+ }
749
 
 
 
 
 
 
 
 
 
750
 
751
+ with torch.no_grad():
 
752
 
753
+ output = model.generate(
754
+ **inputs,
755
+ max_new_tokens=20,
756
+ do_sample=False
 
 
757
  )
 
758
 
759
 
760
+ generated = output[
761
+ 0
762
+ ][
763
+ inputs["input_ids"].shape[1]:
764
+ ]
765
 
 
 
 
 
766
 
767
+ raw_result = tokenizer.decode(
768
+ generated,
769
+ skip_special_tokens=True
770
+ ).strip()
771
 
 
 
 
 
772
 
773
+ # --------------------------------------------------------
774
+ # ANALYSIS
775
+ # --------------------------------------------------------
776
 
777
+ sentiment = extract_sentiment(
778
+ raw_result
779
+ +
780
+ " "
781
+ +
782
+ text
783
+ )
784
 
785
+ score = sentiment_score(
786
+ sentiment,
787
+ text
788
  )
789
 
790
+ strength = get_strength(
791
+ score
792
+ )
793
 
794
+ risk = get_risk(
795
+ text,
796
+ score
797
+ )
798
 
799
+ impact = get_market_impact(
800
+ sentiment,
801
+ score
802
+ )
803
 
804
+ assets = detect_assets(
805
+ text
806
+ )
807
 
 
808
 
809
+ # --------------------------------------------------------
810
+ # DISPLAY SCORES
811
+ # --------------------------------------------------------
812
 
813
+ if sentiment == "Bullish":
 
 
814
 
815
+ bullish = 70 + abs(score) * 25
816
+ bearish = 10
817
+ neutral = 100 - bullish - bearish
818
 
819
+ elif sentiment == "Bearish":
820
 
821
+ bearish = 70 + abs(score) * 25
822
+ bullish = 10
823
+ neutral = 100 - bearish - bullish
824
 
825
+ else:
 
 
826
 
827
+ neutral = 60 + (1 - abs(score)) * 20
828
+ bullish = (100 - neutral) / 2
829
+ bearish = bullish
830
 
 
831
 
832
+ bullish = max(
833
+ 0,
834
+ min(
835
+ 100,
836
+ bullish
837
  )
838
+ )
839
 
840
+ bearish = max(
841
+ 0,
842
+ min(
843
+ 100,
844
+ bearish
845
  )
846
+ )
847
 
848
+ neutral = max(
849
+ 0,
850
+ 100 - bullish - bearish
851
+ )
852
 
 
853
 
854
+ # --------------------------------------------------------
855
+ # DIRECTION ICON
856
+ # --------------------------------------------------------
 
857
 
858
+ if sentiment == "Bullish":
859
+ icon = "🟢"
 
860
 
861
+ elif sentiment == "Bearish":
862
+ icon = "🔴"
863
 
864
+ else:
865
+ icon = "🟡"
866
 
 
 
 
867
 
868
+ elapsed = (
869
+ time.perf_counter()
870
+ - start
871
+ )
872
 
 
873
 
874
+ # --------------------------------------------------------
875
+ # USAGE
876
+ # --------------------------------------------------------
877
 
878
+ increment_usage(
879
+ current_user["id"]
880
+ )
881
 
882
+ new_user = get_user(
883
+ current_user["email"]
884
+ )
885
 
 
 
 
 
 
 
 
 
886
 
887
+ # --------------------------------------------------------
888
+ # ASSET TEXT
889
+ # --------------------------------------------------------
 
890
 
891
+ asset_text = (
892
+ ", ".join(assets)
893
+ if assets
894
+ else "Belirlenmedi"
895
+ )
896
 
897
 
898
+ # --------------------------------------------------------
899
+ # SCORE TEXT
900
+ # --------------------------------------------------------
901
 
902
+ score_text = (
903
+ f"{score:+.2f}"
904
+ )
905
 
 
 
 
906
 
907
+ # --------------------------------------------------------
908
+ # FINAL REPORT
909
+ # --------------------------------------------------------
910
 
911
+ report = f"""
912
+ # {icon} CRYPTO SENTIMENT ANALYSIS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
913
 
914
+ ## Genel Sinyal
 
 
915
 
916
+ ### **{sentiment.upper()}**
 
 
917
 
918
+ | Metrik | Sonuç |
919
+ |---|---|
920
+ | **Sentiment** | {sentiment} |
921
+ | **Sentiment Score** | `{score_text}` |
922
+ | **Signal Strength** | **{strength}** |
923
+ | **Market Impact** | **{impact}** |
924
+ | **Risk Level** | **{risk}** |
925
+ | **Detected Asset** | **{asset_text}** |
926
 
927
+ ---
 
 
 
 
 
 
 
 
928
 
929
+ ## 📊 Sentiment Distribution
 
 
930
 
931
+ ### 🟢 Bullish — **{bullish:.1f}%**
932
 
933
+ `{bar(bullish)}`
 
 
934
 
935
+ ### 🟡 Neutral — **{neutral:.1f}%**
 
 
936
 
937
+ `{bar(neutral)}`
 
 
938
 
939
+ ### 🔴 Bearish — **{bearish:.1f}%**
 
 
940
 
941
+ `{bar(bearish)}`
 
 
942
 
943
+ > **Not:** Bu yüzdeler mevcut generative modelin gerçek
944
+ > softmax sınıf olasılıkları değildir; model çıktısı ve metin
945
+ > sinyallerinden oluşturulan yaklaşık dağılımdır.
 
946
 
947
+ ---
948
 
949
+ ## 🎯 Signal Interpretation
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
950
 
951
+ **Direction:** `{sentiment}`
 
 
 
 
 
 
 
 
 
952
 
953
+ **Strength:** `{strength}`
 
 
 
 
 
 
 
 
 
 
954
 
955
+ **Risk:** `{risk}`
 
 
 
 
 
 
 
 
 
956
 
957
+ **Market Impact:** `{impact}`
 
 
 
 
 
958
 
959
+ ---
 
 
 
 
 
 
 
 
 
960
 
961
+ ## 🪙 Asset Detection
 
 
 
 
 
 
 
 
962
 
963
+ Detected assets:
 
 
 
 
 
 
 
 
964
 
965
+ **{asset_text}**
966
 
967
+ ---
968
+
969
+ ## 🤖 Model Output
970
 
971
+ ```text
972
+ {raw_result if raw_result else "No explicit label returned."}