Really-amin commited on
Commit
ce3ff09
·
verified ·
1 Parent(s): d6d843f

Upload 295 files

Browse files
Files changed (5) hide show
  1. ai_models.py +288 -51
  2. api_server_extended.py +132 -44
  3. index.html +49 -1
  4. static/css/main.css +161 -0
  5. static/js/app.js +230 -58
ai_models.py CHANGED
@@ -15,6 +15,19 @@ try:
15
  except ImportError:
16
  TRANSFORMERS_AVAILABLE = False
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  logger = logging.getLogger(__name__)
19
  settings = get_settings()
20
 
@@ -31,17 +44,26 @@ if HF_MODE == "auth" and not HF_TOKEN_ENV:
31
  HF_MODE = "off"
32
  logger.warning("HF_MODE='auth' but no HF_TOKEN found, resetting to 'off'")
33
 
34
- # Extended Model Catalog
 
35
  CRYPTO_SENTIMENT_MODELS = [
36
- "ElKulako/cryptobert", "kk08/CryptoBERT",
37
- "burakutf/finetuned-finbert-crypto", "mathugo/crypto_news_bert"
 
 
38
  ]
39
  SOCIAL_SENTIMENT_MODELS = [
40
- "svalabs/twitter-xlm-roberta-bitcoin-sentiment",
41
- "mayurjadhav/crypto-sentiment-model"
 
 
 
 
 
 
 
 
42
  ]
43
- FINANCIAL_SENTIMENT_MODELS = ["ProsusAI/finbert", "cardiffnlp/twitter-roberta-base-sentiment"]
44
- NEWS_SENTIMENT_MODELS = ["mrm8488/distilroberta-finetuned-financial-news-sentiment-analysis"]
45
  DECISION_MODELS = ["agarkovv/CryptoTrader-LM"]
46
 
47
  @dataclass(frozen=True)
@@ -96,8 +118,29 @@ class ModelRegistry:
96
  self._pipelines = {}
97
  self._lock = threading.Lock()
98
  self._initialized = False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
  def get_pipeline(self, key: str):
 
101
  if HF_MODE == "off":
102
  raise ModelNotAvailable("HF_MODE=off")
103
  if not TRANSFORMERS_AVAILABLE:
@@ -106,103 +149,285 @@ class ModelRegistry:
106
  raise ModelNotAvailable(f"Unknown key: {key}")
107
 
108
  spec = MODEL_SPECS[key]
 
 
109
  if key in self._pipelines:
110
  return self._pipelines[key]
111
 
 
 
 
 
112
  with self._lock:
 
113
  if key in self._pipelines:
114
  return self._pipelines[key]
 
 
 
 
 
 
 
115
 
116
- auth = HF_TOKEN_ENV if (HF_MODE == "auth" and spec.requires_auth) else (HF_TOKEN_ENV if spec.requires_auth else None)
117
- logger.info(f"Loading model: {spec.model_id}")
118
  try:
119
- self._pipelines[key] = pipeline(spec.task, model=spec.model_id, tokenizer=spec.model_id, use_auth_token=auth)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  except Exception as e:
121
- logger.exception(f"Failed to load {spec.model_id}")
122
- raise ModelNotAvailable(str(e)) from e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
  return self._pipelines[key]
125
 
126
  def initialize_models(self):
 
127
  if self._initialized:
128
- return {"status": "already_initialized", "mode": HF_MODE, "models_loaded": len(self._pipelines)}
 
 
 
 
 
 
129
  if HF_MODE == "off":
130
- return {"status": "disabled", "mode": HF_MODE, "models_loaded": 0, "error": "HF_MODE=off"}
 
 
 
 
 
 
131
  if not TRANSFORMERS_AVAILABLE:
132
- return {"status": "transformers_not_available", "mode": HF_MODE, "models_loaded": 0}
 
 
 
 
 
133
 
134
  loaded, failed = [], []
135
- for key in ["crypto_sent_0", "financial_sent_0"]:
136
- try:
137
- self.get_pipeline(key)
138
- loaded.append(key)
139
- except Exception as e:
140
- failed.append((key, str(e)))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
  self._initialized = True
143
- return {"status": "ok" if loaded else "partial", "mode": HF_MODE, "models_loaded": len(loaded), "loaded": loaded, "failed": failed}
 
 
 
 
 
 
 
 
 
144
 
145
  _registry = ModelRegistry()
146
 
147
  def initialize_models(): return _registry.initialize_models()
148
 
149
  def ensemble_crypto_sentiment(text: str) -> Dict[str, Any]:
 
150
  if not TRANSFORMERS_AVAILABLE:
151
- return {"label": "neutral", "confidence": 0.0, "scores": {}, "model_count": 0, "error": "transformers N/A"}
 
 
 
152
 
153
  results, labels_count, total_conf = {}, {"bullish": 0, "bearish": 0, "neutral": 0}, 0.0
154
 
155
- for key in ["crypto_sent_0", "crypto_sent_1"]:
 
 
 
 
 
156
  try:
157
  pipe = _registry.get_pipeline(key)
158
  res = pipe(text[:512])
159
- if isinstance(res, list) and res: res = res[0]
 
160
 
161
  label = res.get("label", "NEUTRAL").upper()
162
  score = res.get("score", 0.5)
163
 
164
- mapped = "bullish" if "POSITIVE" in label or "BULLISH" in label else ("bearish" if "NEGATIVE" in label or "BEARISH" in label else "neutral")
 
 
 
165
 
166
  spec = MODEL_SPECS[key]
167
  results[spec.model_id] = {"label": mapped, "score": score}
168
  labels_count[mapped] += 1
169
  total_conf += score
 
 
 
 
 
 
 
170
  except Exception as e:
171
- logger.warning(f"Ensemble failed for {key}: {e}")
 
172
 
173
  if not results:
174
- return {"label": "neutral", "confidence": 0.0, "scores": {}, "model_count": 0}
 
 
 
 
 
 
 
175
 
176
  final = max(labels_count, key=labels_count.get)
177
  avg_conf = total_conf / len(results)
178
 
179
- return {"label": final, "confidence": avg_conf, "scores": results, "model_count": len(results)}
 
 
 
 
 
 
180
 
181
  def analyze_crypto_sentiment(text: str): return ensemble_crypto_sentiment(text)
182
 
183
  def analyze_financial_sentiment(text: str):
 
184
  if not TRANSFORMERS_AVAILABLE:
185
- return {"label": "neutral", "score": 0.5, "error": "transformers N/A"}
186
- try:
187
- pipe = _registry.get_pipeline("financial_sent_0")
188
- res = pipe(text[:512])
189
- if isinstance(res, list) and res: res = res[0]
190
- return {"label": res.get("label", "neutral").lower(), "score": res.get("score", 0.5)}
191
- except Exception as e:
192
- logger.error(f"Financial sentiment failed: {e}")
193
- return {"label": "neutral", "score": 0.5, "error": str(e)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
 
195
  def analyze_social_sentiment(text: str):
 
196
  if not TRANSFORMERS_AVAILABLE:
197
- return {"label": "neutral", "score": 0.5, "error": "transformers N/A"}
198
- try:
199
- pipe = _registry.get_pipeline("social_sent_0")
200
- res = pipe(text[:512])
201
- if isinstance(res, list) and res: res = res[0]
202
- return {"label": res.get("label", "neutral").lower(), "score": res.get("score", 0.5)}
203
- except Exception as e:
204
- logger.error(f"Social sentiment failed: {e}")
205
- return {"label": "neutral", "score": 0.5, "error": str(e)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
 
207
  def analyze_market_text(text: str): return ensemble_crypto_sentiment(text)
208
 
@@ -243,12 +468,24 @@ def get_model_info():
243
  }
244
 
245
  def registry_status():
246
- return {
247
- "ok": HF_MODE != "off",
 
248
  "initialized": _registry._initialized,
249
  "pipelines_loaded": len(_registry._pipelines),
250
- "available_models": list(MODEL_SPECS.keys()),
 
 
251
  "transformers_available": TRANSFORMERS_AVAILABLE,
252
  "hf_mode": HF_MODE,
253
- "error": "HF_MODE=off" if HF_MODE == "off" else None
254
  }
 
 
 
 
 
 
 
 
 
 
15
  except ImportError:
16
  TRANSFORMERS_AVAILABLE = False
17
 
18
+ try:
19
+ from huggingface_hub.errors import RepositoryNotFoundError
20
+ HF_HUB_AVAILABLE = True
21
+ except ImportError:
22
+ HF_HUB_AVAILABLE = False
23
+ RepositoryNotFoundError = Exception
24
+
25
+ try:
26
+ import requests
27
+ REQUESTS_AVAILABLE = True
28
+ except ImportError:
29
+ REQUESTS_AVAILABLE = False
30
+
31
  logger = logging.getLogger(__name__)
32
  settings = get_settings()
33
 
 
44
  HF_MODE = "off"
45
  logger.warning("HF_MODE='auth' but no HF_TOKEN found, resetting to 'off'")
46
 
47
+ # Extended Model Catalog - Updated with valid public models
48
+ # Primary models first, fallbacks follow
49
  CRYPTO_SENTIMENT_MODELS = [
50
+ "cardiffnlp/twitter-roberta-base-sentiment-latest", # Primary: reliable public model
51
+ "kk08/CryptoBERT", # Fallback 1
52
+ "burakutf/finetuned-finbert-crypto", # Fallback 2
53
+ "mathugo/crypto_news_bert" # Fallback 3
54
  ]
55
  SOCIAL_SENTIMENT_MODELS = [
56
+ "cardiffnlp/twitter-roberta-base-sentiment-latest", # Primary: reliable public model
57
+ "mayurjadhav/crypto-sentiment-model" # Fallback
58
+ ]
59
+ FINANCIAL_SENTIMENT_MODELS = [
60
+ "cardiffnlp/twitter-roberta-base-sentiment-latest", # Primary: reliable public model
61
+ "ProsusAI/finbert" # Fallback (may require auth)
62
+ ]
63
+ NEWS_SENTIMENT_MODELS = [
64
+ "cardiffnlp/twitter-roberta-base-sentiment-latest", # Primary: reliable public model
65
+ "mrm8488/distilroberta-finetuned-financial-news-sentiment-analysis" # Fallback
66
  ]
 
 
67
  DECISION_MODELS = ["agarkovv/CryptoTrader-LM"]
68
 
69
  @dataclass(frozen=True)
 
118
  self._pipelines = {}
119
  self._lock = threading.Lock()
120
  self._initialized = False
121
+ self._failed_models = {} # Track failed models with reasons
122
+
123
+ def _should_use_token(self, spec: PipelineSpec) -> Optional[str]:
124
+ """Determine if and which token to use for model loading"""
125
+ if HF_MODE == "off":
126
+ return None
127
+
128
+ # In public mode, don't use token even if requires_auth
129
+ if HF_MODE == "public":
130
+ return None
131
+
132
+ # In auth mode, use token if available
133
+ if HF_MODE == "auth":
134
+ if spec.requires_auth and HF_TOKEN_ENV:
135
+ return HF_TOKEN_ENV
136
+ elif spec.requires_auth and not HF_TOKEN_ENV:
137
+ logger.warning(f"Model {spec.model_id} requires auth but no token available")
138
+ return None
139
+
140
+ return None
141
 
142
  def get_pipeline(self, key: str):
143
+ """Get pipeline for a model key, with robust error handling"""
144
  if HF_MODE == "off":
145
  raise ModelNotAvailable("HF_MODE=off")
146
  if not TRANSFORMERS_AVAILABLE:
 
149
  raise ModelNotAvailable(f"Unknown key: {key}")
150
 
151
  spec = MODEL_SPECS[key]
152
+
153
+ # Return cached pipeline if available
154
  if key in self._pipelines:
155
  return self._pipelines[key]
156
 
157
+ # Check if this model already failed
158
+ if key in self._failed_models:
159
+ raise ModelNotAvailable(f"Model failed previously: {self._failed_models[key]}")
160
+
161
  with self._lock:
162
+ # Double-check after acquiring lock
163
  if key in self._pipelines:
164
  return self._pipelines[key]
165
+ if key in self._failed_models:
166
+ raise ModelNotAvailable(f"Model failed previously: {self._failed_models[key]}")
167
+
168
+ # Determine token usage
169
+ auth_token = self._should_use_token(spec)
170
+
171
+ logger.info(f"Loading model: {spec.model_id} (mode={HF_MODE}, auth={'yes' if auth_token else 'no'})")
172
 
 
 
173
  try:
174
+ # Use token parameter instead of deprecated use_auth_token
175
+ pipeline_kwargs = {
176
+ "task": spec.task,
177
+ "model": spec.model_id,
178
+ }
179
+
180
+ # Only add token if we have one and it's needed
181
+ if auth_token:
182
+ pipeline_kwargs["token"] = auth_token
183
+ else:
184
+ # Explicitly set to None to avoid using expired tokens
185
+ pipeline_kwargs["token"] = None
186
+
187
+ self._pipelines[key] = pipeline(**pipeline_kwargs)
188
+ logger.info(f"Successfully loaded model: {spec.model_id}")
189
+ return self._pipelines[key]
190
+
191
+ except RepositoryNotFoundError as e:
192
+ error_msg = f"Repository not found: {spec.model_id}"
193
+ logger.warning(f"{error_msg} - {str(e)}")
194
+ self._failed_models[key] = error_msg
195
+ raise ModelNotAvailable(error_msg) from e
196
+
197
  except Exception as e:
198
+ error_type = type(e).__name__
199
+ error_msg = f"{error_type}: {str(e)[:100]}"
200
+
201
+ # Check for HTTP errors (401, 403, 404)
202
+ if REQUESTS_AVAILABLE and isinstance(e, requests.exceptions.HTTPError):
203
+ status_code = getattr(e.response, 'status_code', None)
204
+ if status_code == 401:
205
+ error_msg = f"Authentication failed (401) for {spec.model_id}"
206
+ elif status_code == 403:
207
+ error_msg = f"Access forbidden (403) for {spec.model_id}"
208
+ elif status_code == 404:
209
+ error_msg = f"Model not found (404): {spec.model_id}"
210
+
211
+ # Check for OSError from transformers
212
+ if isinstance(e, OSError) and "not a valid model identifier" in str(e):
213
+ error_msg = f"Invalid model identifier: {spec.model_id}"
214
+
215
+ logger.warning(f"Failed to load {spec.model_id}: {error_msg}")
216
+ self._failed_models[key] = error_msg
217
+ raise ModelNotAvailable(error_msg) from e
218
 
219
  return self._pipelines[key]
220
 
221
  def initialize_models(self):
222
+ """Initialize models with fallback logic - tries primary models first"""
223
  if self._initialized:
224
+ return {
225
+ "status": "already_initialized",
226
+ "mode": HF_MODE,
227
+ "models_loaded": len(self._pipelines),
228
+ "failed_count": len(self._failed_models)
229
+ }
230
+
231
  if HF_MODE == "off":
232
+ return {
233
+ "status": "disabled",
234
+ "mode": HF_MODE,
235
+ "models_loaded": 0,
236
+ "error": "HF_MODE=off"
237
+ }
238
+
239
  if not TRANSFORMERS_AVAILABLE:
240
+ return {
241
+ "status": "transformers_not_available",
242
+ "mode": HF_MODE,
243
+ "models_loaded": 0,
244
+ "error": "transformers library not installed"
245
+ }
246
 
247
  loaded, failed = [], []
248
+
249
+ # Try to load at least one model from each category with fallback
250
+ categories_to_try = {
251
+ "crypto": ["crypto_sent_0", "crypto_sent_1", "crypto_sent_2"],
252
+ "financial": ["financial_sent_0", "financial_sent_1"],
253
+ "social": ["social_sent_0", "social_sent_1"],
254
+ "news": ["news_sent_0", "news_sent_1"]
255
+ }
256
+
257
+ for category, keys in categories_to_try.items():
258
+ category_loaded = False
259
+ for key in keys:
260
+ if key not in MODEL_SPECS:
261
+ continue
262
+ try:
263
+ self.get_pipeline(key)
264
+ loaded.append(key)
265
+ category_loaded = True
266
+ break # Successfully loaded one from this category
267
+ except ModelNotAvailable as e:
268
+ failed.append((key, str(e)[:100])) # Truncate long errors
269
+ except Exception as e:
270
+ failed.append((key, f"{type(e).__name__}: {str(e)[:100]}"))
271
+
272
+ # Determine status
273
+ if len(loaded) > 0:
274
+ status = "ok" if len(loaded) >= 2 else "partial"
275
+ else:
276
+ status = "failed"
277
 
278
  self._initialized = True
279
+
280
+ return {
281
+ "status": status,
282
+ "mode": HF_MODE,
283
+ "models_loaded": len(loaded),
284
+ "models_failed": len(failed),
285
+ "loaded": loaded[:10], # Limit to first 10 for brevity
286
+ "failed": failed[:10], # Limit to first 10 for brevity
287
+ "failed_count": len(self._failed_models)
288
+ }
289
 
290
  _registry = ModelRegistry()
291
 
292
  def initialize_models(): return _registry.initialize_models()
293
 
294
  def ensemble_crypto_sentiment(text: str) -> Dict[str, Any]:
295
+ """Ensemble crypto sentiment with fallback model selection"""
296
  if not TRANSFORMERS_AVAILABLE:
297
+ return {"label": "neutral", "confidence": 0.0, "scores": {}, "model_count": 0, "error": "transformers N/A", "available": False}
298
+
299
+ if HF_MODE == "off":
300
+ return {"label": "neutral", "confidence": 0.0, "scores": {}, "model_count": 0, "error": "HF_MODE=off", "available": False}
301
 
302
  results, labels_count, total_conf = {}, {"bullish": 0, "bearish": 0, "neutral": 0}, 0.0
303
 
304
+ # Try models in order with fallback
305
+ candidate_keys = ["crypto_sent_0", "crypto_sent_1", "crypto_sent_2", "crypto_sent_3"]
306
+
307
+ for key in candidate_keys:
308
+ if key not in MODEL_SPECS:
309
+ continue
310
  try:
311
  pipe = _registry.get_pipeline(key)
312
  res = pipe(text[:512])
313
+ if isinstance(res, list) and res:
314
+ res = res[0]
315
 
316
  label = res.get("label", "NEUTRAL").upper()
317
  score = res.get("score", 0.5)
318
 
319
+ # Map labels to our standard format
320
+ mapped = "bullish" if "POSITIVE" in label or "BULLISH" in label or "LABEL_2" in label else (
321
+ "bearish" if "NEGATIVE" in label or "BEARISH" in label or "LABEL_0" in label else "neutral"
322
+ )
323
 
324
  spec = MODEL_SPECS[key]
325
  results[spec.model_id] = {"label": mapped, "score": score}
326
  labels_count[mapped] += 1
327
  total_conf += score
328
+
329
+ # If we got at least one result, we can proceed
330
+ if len(results) >= 1:
331
+ break # Got at least one working model
332
+
333
+ except ModelNotAvailable:
334
+ continue # Try next model
335
  except Exception as e:
336
+ logger.warning(f"Ensemble failed for {key}: {str(e)[:100]}")
337
+ continue
338
 
339
  if not results:
340
+ return {
341
+ "label": "neutral",
342
+ "confidence": 0.0,
343
+ "scores": {},
344
+ "model_count": 0,
345
+ "available": False,
346
+ "error": "No models available"
347
+ }
348
 
349
  final = max(labels_count, key=labels_count.get)
350
  avg_conf = total_conf / len(results)
351
 
352
+ return {
353
+ "label": final,
354
+ "confidence": avg_conf,
355
+ "scores": results,
356
+ "model_count": len(results),
357
+ "available": True
358
+ }
359
 
360
  def analyze_crypto_sentiment(text: str): return ensemble_crypto_sentiment(text)
361
 
362
  def analyze_financial_sentiment(text: str):
363
+ """Analyze financial sentiment with fallback"""
364
  if not TRANSFORMERS_AVAILABLE:
365
+ return {"label": "neutral", "score": 0.5, "error": "transformers N/A", "available": False}
366
+
367
+ if HF_MODE == "off":
368
+ return {"label": "neutral", "score": 0.5, "error": "HF_MODE=off", "available": False}
369
+
370
+ # Try models in order
371
+ for key in ["financial_sent_0", "financial_sent_1"]:
372
+ if key not in MODEL_SPECS:
373
+ continue
374
+ try:
375
+ pipe = _registry.get_pipeline(key)
376
+ res = pipe(text[:512])
377
+ if isinstance(res, list) and res:
378
+ res = res[0]
379
+
380
+ label = res.get("label", "neutral").upper()
381
+ score = res.get("score", 0.5)
382
+
383
+ # Map to standard format
384
+ mapped = "bullish" if "POSITIVE" in label or "LABEL_2" in label else (
385
+ "bearish" if "NEGATIVE" in label or "LABEL_0" in label else "neutral"
386
+ )
387
+
388
+ return {"label": mapped, "score": score, "available": True, "model": MODEL_SPECS[key].model_id}
389
+ except ModelNotAvailable:
390
+ continue
391
+ except Exception as e:
392
+ logger.warning(f"Financial sentiment failed for {key}: {str(e)[:100]}")
393
+ continue
394
+
395
+ return {"label": "neutral", "score": 0.5, "error": "No models available", "available": False}
396
 
397
  def analyze_social_sentiment(text: str):
398
+ """Analyze social sentiment with fallback"""
399
  if not TRANSFORMERS_AVAILABLE:
400
+ return {"label": "neutral", "score": 0.5, "error": "transformers N/A", "available": False}
401
+
402
+ if HF_MODE == "off":
403
+ return {"label": "neutral", "score": 0.5, "error": "HF_MODE=off", "available": False}
404
+
405
+ # Try models in order
406
+ for key in ["social_sent_0", "social_sent_1"]:
407
+ if key not in MODEL_SPECS:
408
+ continue
409
+ try:
410
+ pipe = _registry.get_pipeline(key)
411
+ res = pipe(text[:512])
412
+ if isinstance(res, list) and res:
413
+ res = res[0]
414
+
415
+ label = res.get("label", "neutral").upper()
416
+ score = res.get("score", 0.5)
417
+
418
+ # Map to standard format
419
+ mapped = "bullish" if "POSITIVE" in label or "LABEL_2" in label else (
420
+ "bearish" if "NEGATIVE" in label or "LABEL_0" in label else "neutral"
421
+ )
422
+
423
+ return {"label": mapped, "score": score, "available": True, "model": MODEL_SPECS[key].model_id}
424
+ except ModelNotAvailable:
425
+ continue
426
+ except Exception as e:
427
+ logger.warning(f"Social sentiment failed for {key}: {str(e)[:100]}")
428
+ continue
429
+
430
+ return {"label": "neutral", "score": 0.5, "error": "No models available", "available": False}
431
 
432
  def analyze_market_text(text: str): return ensemble_crypto_sentiment(text)
433
 
 
468
  }
469
 
470
  def registry_status():
471
+ """Get registry status with detailed information"""
472
+ status = {
473
+ "ok": HF_MODE != "off" and TRANSFORMERS_AVAILABLE and len(_registry._pipelines) > 0,
474
  "initialized": _registry._initialized,
475
  "pipelines_loaded": len(_registry._pipelines),
476
+ "pipelines_failed": len(_registry._failed_models),
477
+ "available_models": list(_registry._pipelines.keys()),
478
+ "failed_models": list(_registry._failed_models.keys())[:10], # Limit for brevity
479
  "transformers_available": TRANSFORMERS_AVAILABLE,
480
  "hf_mode": HF_MODE,
481
+ "total_specs": len(MODEL_SPECS)
482
  }
483
+
484
+ if HF_MODE == "off":
485
+ status["error"] = "HF_MODE=off"
486
+ elif not TRANSFORMERS_AVAILABLE:
487
+ status["error"] = "transformers not installed"
488
+ elif len(_registry._pipelines) == 0 and _registry._initialized:
489
+ status["error"] = "No models loaded successfully"
490
+
491
+ return status
api_server_extended.py CHANGED
@@ -10,12 +10,15 @@ import sqlite3
10
  import httpx
11
  import json
12
  import subprocess
 
13
  from pathlib import Path
14
  from typing import Optional, Dict, Any, List
15
  from datetime import datetime
16
  from contextlib import asynccontextmanager
17
  from collections import defaultdict
18
 
 
 
19
  from fastapi import FastAPI, HTTPException, Response, Request
20
  from fastapi.middleware.cors import CORSMiddleware
21
  from fastapi.responses import JSONResponse, FileResponse, HTMLResponse
@@ -1046,39 +1049,66 @@ async def analyze_sentiment(request: Dict[str, Any]):
1046
  else:
1047
  result = analyze_market_text(text)
1048
 
 
 
 
 
 
 
 
 
 
 
 
 
1049
  sentiment_label = result.get("label", "neutral")
1050
  confidence = result.get("confidence", result.get("score", 0.5))
1051
- model_used = result.get("model_count", 0)
1052
 
1053
- conn = sqlite3.connect(str(DB_PATH))
1054
- cursor = conn.cursor()
1055
- cursor.execute("""
1056
- INSERT INTO sentiment_analysis
1057
- (text, sentiment_label, confidence, model_used, analysis_type, symbol, scores)
1058
- VALUES (?, ?, ?, ?, ?, ?, ?)
1059
- """, (
1060
- text[:500],
1061
- sentiment_label,
1062
- confidence,
1063
- f"{model_used} models" if isinstance(model_used, int) else str(model_used),
1064
- mode,
1065
- symbol,
1066
- json.dumps(result.get("scores", {}))
1067
- ))
1068
- conn.commit()
1069
- conn.close()
 
 
 
 
 
 
1070
 
1071
  return {
1072
  "success": True,
 
1073
  "sentiment": sentiment_label,
1074
  "confidence": confidence,
1075
  "mode": mode,
1076
  "result": result,
1077
- "saved_to_db": True
1078
  }
1079
 
1080
  except ModelNotAvailable as e:
1081
- raise HTTPException(status_code=503, detail=f"Models not available: {str(e)}")
 
 
 
 
 
 
 
 
1082
 
1083
  except HTTPException:
1084
  raise
@@ -1110,40 +1140,74 @@ async def analyze_news(request: Dict[str, Any]):
1110
 
1111
  sentiment_label = result.get("sentiment", "neutral")
1112
  sentiment_confidence = result.get("sentiment_confidence", 0.5)
 
1113
  related_symbols = request.get("related_symbols", [])
1114
 
1115
- conn = sqlite3.connect(str(DB_PATH))
1116
- cursor = conn.cursor()
1117
- cursor.execute("""
1118
- INSERT INTO news_articles
1119
- (title, content, url, source, sentiment_label, sentiment_confidence, related_symbols, published_date)
1120
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1121
- """, (
1122
- title[:500],
1123
- content[:2000] if content else None,
1124
- url,
1125
- source,
1126
- sentiment_label,
1127
- sentiment_confidence,
1128
- json.dumps(related_symbols) if related_symbols else None,
1129
- published_date
1130
- ))
1131
- conn.commit()
1132
- conn.close()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1133
 
1134
  return {
1135
  "success": True,
 
1136
  "news": {
1137
  "title": title,
1138
  "sentiment": sentiment_label,
1139
  "confidence": sentiment_confidence,
1140
- "details": result.get("sentiment_details", {})
1141
  },
1142
- "saved_to_db": True
1143
  }
1144
 
1145
  except ModelNotAvailable as e:
1146
- raise HTTPException(status_code=503, detail=f"Models not available: {str(e)}")
 
 
 
 
 
 
 
 
 
 
1147
 
1148
  except HTTPException:
1149
  raise
@@ -1387,6 +1451,7 @@ async def predict_with_model(model_key: str, request: Dict[str, Any]):
1387
 
1388
  return {
1389
  "success": True,
 
1390
  "model_key": model_key,
1391
  "model_id": spec.model_id,
1392
  "task": spec.task,
@@ -1395,7 +1460,14 @@ async def predict_with_model(model_key: str, request: Dict[str, Any]):
1395
  "timestamp": datetime.now().isoformat()
1396
  }
1397
  except ModelNotAvailable as e:
1398
- raise HTTPException(status_code=503, detail=f"Model not available: {str(e)}")
 
 
 
 
 
 
 
1399
 
1400
  except HTTPException:
1401
  raise
@@ -1601,12 +1673,18 @@ async def run_hf_sentiment(data: Dict[str, Any]):
1601
  all_results = []
1602
  total_vote = 0.0
1603
  count = 0
 
1604
 
1605
  for text in texts:
1606
  if not text.strip():
1607
  continue
1608
 
1609
  result = analyze_market_text(text.strip())
 
 
 
 
 
1610
  label = result.get("label", "neutral")
1611
  confidence = result.get("confidence", 0.5)
1612
 
@@ -1623,12 +1701,14 @@ async def run_hf_sentiment(data: Dict[str, Any]):
1623
  "text": text[:100],
1624
  "label": label,
1625
  "confidence": confidence,
1626
- "vote": vote_score
 
1627
  })
1628
 
1629
  avg_vote = total_vote / count if count > 0 else 0.0
1630
 
1631
  return {
 
1632
  "vote": avg_vote,
1633
  "results": all_results,
1634
  "count": count,
@@ -1636,7 +1716,15 @@ async def run_hf_sentiment(data: Dict[str, Any]):
1636
  }
1637
 
1638
  except ModelNotAvailable as e:
1639
- raise HTTPException(status_code=503, detail=f"Models not available: {str(e)}")
 
 
 
 
 
 
 
 
1640
 
1641
  except HTTPException:
1642
  raise
 
10
  import httpx
11
  import json
12
  import subprocess
13
+ import logging
14
  from pathlib import Path
15
  from typing import Optional, Dict, Any, List
16
  from datetime import datetime
17
  from contextlib import asynccontextmanager
18
  from collections import defaultdict
19
 
20
+ logger = logging.getLogger(__name__)
21
+
22
  from fastapi import FastAPI, HTTPException, Response, Request
23
  from fastapi.middleware.cors import CORSMiddleware
24
  from fastapi.responses import JSONResponse, FileResponse, HTMLResponse
 
1049
  else:
1050
  result = analyze_market_text(text)
1051
 
1052
+ # Check if models are available
1053
+ if not result.get("available", True):
1054
+ return {
1055
+ "success": False,
1056
+ "available": False,
1057
+ "sentiment": "neutral",
1058
+ "confidence": 0.0,
1059
+ "mode": mode,
1060
+ "error": result.get("error", "Models not available"),
1061
+ "reason": "model_unavailable"
1062
+ }
1063
+
1064
  sentiment_label = result.get("label", "neutral")
1065
  confidence = result.get("confidence", result.get("score", 0.5))
1066
+ model_used = result.get("model_count", result.get("model", "unknown"))
1067
 
1068
+ # Save to database
1069
+ try:
1070
+ conn = sqlite3.connect(str(DB_PATH))
1071
+ cursor = conn.cursor()
1072
+ cursor.execute("""
1073
+ INSERT INTO sentiment_analysis
1074
+ (text, sentiment_label, confidence, model_used, analysis_type, symbol, scores)
1075
+ VALUES (?, ?, ?, ?, ?, ?, ?)
1076
+ """, (
1077
+ text[:500],
1078
+ sentiment_label,
1079
+ confidence,
1080
+ f"{model_used} models" if isinstance(model_used, int) else str(model_used),
1081
+ mode,
1082
+ symbol,
1083
+ json.dumps(result.get("scores", {}))
1084
+ ))
1085
+ conn.commit()
1086
+ conn.close()
1087
+ saved_to_db = True
1088
+ except Exception as db_error:
1089
+ logger.warning(f"Failed to save to database: {db_error}")
1090
+ saved_to_db = False
1091
 
1092
  return {
1093
  "success": True,
1094
+ "available": True,
1095
  "sentiment": sentiment_label,
1096
  "confidence": confidence,
1097
  "mode": mode,
1098
  "result": result,
1099
+ "saved_to_db": saved_to_db
1100
  }
1101
 
1102
  except ModelNotAvailable as e:
1103
+ return {
1104
+ "success": False,
1105
+ "available": False,
1106
+ "sentiment": "neutral",
1107
+ "confidence": 0.0,
1108
+ "mode": mode,
1109
+ "error": str(e),
1110
+ "reason": "model_unavailable"
1111
+ }
1112
 
1113
  except HTTPException:
1114
  raise
 
1140
 
1141
  sentiment_label = result.get("sentiment", "neutral")
1142
  sentiment_confidence = result.get("sentiment_confidence", 0.5)
1143
+ sentiment_details = result.get("sentiment_details", {})
1144
  related_symbols = request.get("related_symbols", [])
1145
 
1146
+ # Check if models were available
1147
+ available = sentiment_details.get("available", True) if isinstance(sentiment_details, dict) else True
1148
+
1149
+ if not available:
1150
+ return {
1151
+ "success": False,
1152
+ "available": False,
1153
+ "news": {
1154
+ "title": title,
1155
+ "sentiment": "neutral",
1156
+ "confidence": 0.0,
1157
+ "error": sentiment_details.get("error", "Models not available")
1158
+ },
1159
+ "reason": "model_unavailable"
1160
+ }
1161
+
1162
+ # Save to database
1163
+ try:
1164
+ conn = sqlite3.connect(str(DB_PATH))
1165
+ cursor = conn.cursor()
1166
+ cursor.execute("""
1167
+ INSERT INTO news_articles
1168
+ (title, content, url, source, sentiment_label, sentiment_confidence, related_symbols, published_date)
1169
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1170
+ """, (
1171
+ title[:500],
1172
+ content[:2000] if content else None,
1173
+ url,
1174
+ source,
1175
+ sentiment_label,
1176
+ sentiment_confidence,
1177
+ json.dumps(related_symbols) if related_symbols else None,
1178
+ published_date
1179
+ ))
1180
+ conn.commit()
1181
+ conn.close()
1182
+ saved_to_db = True
1183
+ except Exception as db_error:
1184
+ logger.warning(f"Failed to save to database: {db_error}")
1185
+ saved_to_db = False
1186
 
1187
  return {
1188
  "success": True,
1189
+ "available": True,
1190
  "news": {
1191
  "title": title,
1192
  "sentiment": sentiment_label,
1193
  "confidence": sentiment_confidence,
1194
+ "details": sentiment_details
1195
  },
1196
+ "saved_to_db": saved_to_db
1197
  }
1198
 
1199
  except ModelNotAvailable as e:
1200
+ return {
1201
+ "success": False,
1202
+ "available": False,
1203
+ "news": {
1204
+ "title": title,
1205
+ "sentiment": "neutral",
1206
+ "confidence": 0.0,
1207
+ "error": str(e)
1208
+ },
1209
+ "reason": "model_unavailable"
1210
+ }
1211
 
1212
  except HTTPException:
1213
  raise
 
1451
 
1452
  return {
1453
  "success": True,
1454
+ "available": True,
1455
  "model_key": model_key,
1456
  "model_id": spec.model_id,
1457
  "task": spec.task,
 
1460
  "timestamp": datetime.now().isoformat()
1461
  }
1462
  except ModelNotAvailable as e:
1463
+ return {
1464
+ "success": False,
1465
+ "available": False,
1466
+ "model_key": model_key,
1467
+ "model_id": spec.model_id,
1468
+ "error": str(e),
1469
+ "reason": "model_unavailable"
1470
+ }
1471
 
1472
  except HTTPException:
1473
  raise
 
1673
  all_results = []
1674
  total_vote = 0.0
1675
  count = 0
1676
+ models_available = False
1677
 
1678
  for text in texts:
1679
  if not text.strip():
1680
  continue
1681
 
1682
  result = analyze_market_text(text.strip())
1683
+
1684
+ # Check if models are available
1685
+ if result.get("available", True):
1686
+ models_available = True
1687
+
1688
  label = result.get("label", "neutral")
1689
  confidence = result.get("confidence", 0.5)
1690
 
 
1701
  "text": text[:100],
1702
  "label": label,
1703
  "confidence": confidence,
1704
+ "vote": vote_score,
1705
+ "available": result.get("available", True)
1706
  })
1707
 
1708
  avg_vote = total_vote / count if count > 0 else 0.0
1709
 
1710
  return {
1711
+ "available": models_available,
1712
  "vote": avg_vote,
1713
  "results": all_results,
1714
  "count": count,
 
1716
  }
1717
 
1718
  except ModelNotAvailable as e:
1719
+ return {
1720
+ "available": False,
1721
+ "vote": 0.0,
1722
+ "results": [],
1723
+ "count": 0,
1724
+ "average_confidence": 0.0,
1725
+ "error": str(e),
1726
+ "reason": "model_unavailable"
1727
+ }
1728
 
1729
  except HTTPException:
1730
  raise
index.html CHANGED
@@ -139,6 +139,30 @@
139
  <h2>تحلیل احساسات</h2>
140
  </div>
141
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  <div class="card">
143
  <h3>تحلیل متن</h3>
144
  <div class="form-group">
@@ -146,13 +170,37 @@
146
  <textarea id="sentiment-text" rows="5" placeholder="مثال: Bitcoin price is rising rapidly!"></textarea>
147
  </div>
148
  <div class="form-group">
149
- <label>انتخاب مدل:</label>
 
 
 
 
 
 
 
 
 
150
  <select id="sentiment-model"></select>
151
  </div>
152
  <button class="btn-primary" onclick="analyzeSentiment()">🔍 تحلیل</button>
153
  <div id="sentiment-result"></div>
154
  </div>
155
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  <div class="card">
157
  <h3>تاریخچه تحلیل‌ها</h3>
158
  <div id="sentiment-history"></div>
 
139
  <h2>تحلیل احساسات</h2>
140
  </div>
141
 
142
+ <!-- Global Market Sentiment -->
143
+ <div class="card">
144
+ <h3>احساسات کلی بازار</h3>
145
+ <p style="color: var(--text-secondary); margin-bottom: 15px;">تحلیل احساسات کلی بازار رمز ارز با استفاده از مدل‌های AI</p>
146
+ <button class="btn-primary" onclick="analyzeGlobalSentiment()">📊 تحلیل احساسات بازار</button>
147
+ <div id="global-sentiment-result" style="margin-top: 20px;"></div>
148
+ </div>
149
+
150
+ <!-- Per-Asset Sentiment -->
151
+ <div class="card">
152
+ <h3>تحلیل احساسات برای هر ارز</h3>
153
+ <div class="form-group">
154
+ <label>نماد ارز (مثال: BTC, ETH):</label>
155
+ <input type="text" id="asset-symbol" placeholder="BTC" style="text-transform: uppercase;">
156
+ </div>
157
+ <div class="form-group">
158
+ <label>متن یا خبر مرتبط (اختیاری):</label>
159
+ <textarea id="asset-sentiment-text" rows="3" placeholder="مثال: Bitcoin breaks resistance at $50,000"></textarea>
160
+ </div>
161
+ <button class="btn-primary" onclick="analyzeAssetSentiment()">🔍 تحلیل احساسات ارز</button>
162
+ <div id="asset-sentiment-result" style="margin-top: 20px;"></div>
163
+ </div>
164
+
165
+ <!-- Text Analysis -->
166
  <div class="card">
167
  <h3>تحلیل متن</h3>
168
  <div class="form-group">
 
170
  <textarea id="sentiment-text" rows="5" placeholder="مثال: Bitcoin price is rising rapidly!"></textarea>
171
  </div>
172
  <div class="form-group">
173
+ <label>نوع تحلیل:</label>
174
+ <select id="sentiment-mode">
175
+ <option value="auto">خودکار (Crypto)</option>
176
+ <option value="crypto">رمز ارز (Crypto)</option>
177
+ <option value="financial">مالی (Financial)</option>
178
+ <option value="social">اجتماعی (Social)</option>
179
+ </select>
180
+ </div>
181
+ <div class="form-group">
182
+ <label>انتخاب مدل (اختیاری):</label>
183
  <select id="sentiment-model"></select>
184
  </div>
185
  <button class="btn-primary" onclick="analyzeSentiment()">🔍 تحلیل</button>
186
  <div id="sentiment-result"></div>
187
  </div>
188
 
189
+ <!-- News/Financial Sentiment -->
190
+ <div class="card">
191
+ <h3>تحلیل احساسات اخبار و مالی</h3>
192
+ <div class="form-group">
193
+ <label>عنوان خبر:</label>
194
+ <input type="text" id="news-title" placeholder="مثال: Bitcoin ETF Approval Expected">
195
+ </div>
196
+ <div class="form-group">
197
+ <label>محتوا یا توضیحات:</label>
198
+ <textarea id="news-content" rows="4" placeholder="متن کامل خبر یا توضیحات..."></textarea>
199
+ </div>
200
+ <button class="btn-primary" onclick="analyzeNewsSentiment()">📰 تحلیل خبر</button>
201
+ <div id="news-sentiment-result" style="margin-top: 20px;"></div>
202
+ </div>
203
+
204
  <div class="card">
205
  <h3>تاریخچه تحلیل‌ها</h3>
206
  <div id="sentiment-history"></div>
static/css/main.css CHANGED
@@ -406,6 +406,154 @@ table tr:hover {
406
  text-decoration: underline;
407
  }
408
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
  /* Responsive */
410
  @media (max-width: 768px) {
411
  .header-content {
@@ -428,5 +576,18 @@ table tr:hover {
428
  .stats-grid {
429
  grid-template-columns: 1fr;
430
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
431
  }
432
 
 
406
  text-decoration: underline;
407
  }
408
 
409
+ /* Sentiment Badges */
410
+ .sentiment-badge {
411
+ display: inline-block;
412
+ padding: 6px 12px;
413
+ border-radius: 8px;
414
+ font-size: 13px;
415
+ font-weight: 600;
416
+ margin: 5px 5px 5px 0;
417
+ }
418
+
419
+ .sentiment-badge.bullish {
420
+ background: rgba(16, 185, 129, 0.2);
421
+ color: var(--success);
422
+ border: 1px solid rgba(16, 185, 129, 0.3);
423
+ }
424
+
425
+ .sentiment-badge.bearish {
426
+ background: rgba(239, 68, 68, 0.2);
427
+ color: var(--danger);
428
+ border: 1px solid rgba(239, 68, 68, 0.3);
429
+ }
430
+
431
+ .sentiment-badge.neutral {
432
+ background: rgba(156, 163, 175, 0.2);
433
+ color: var(--text-secondary);
434
+ border: 1px solid rgba(156, 163, 175, 0.3);
435
+ }
436
+
437
+ /* AI Result Cards */
438
+ .ai-result-card {
439
+ background: rgba(17, 24, 39, 0.6);
440
+ border: 1px solid var(--border);
441
+ border-radius: 12px;
442
+ padding: 20px;
443
+ margin-top: 15px;
444
+ transition: all 0.3s;
445
+ }
446
+
447
+ .ai-result-card:hover {
448
+ border-color: var(--primary);
449
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.2);
450
+ }
451
+
452
+ .ai-result-header {
453
+ display: flex;
454
+ justify-content: space-between;
455
+ align-items: center;
456
+ margin-bottom: 15px;
457
+ padding-bottom: 10px;
458
+ border-bottom: 1px solid var(--border);
459
+ }
460
+
461
+ .ai-result-metric {
462
+ display: flex;
463
+ flex-direction: column;
464
+ align-items: center;
465
+ padding: 15px;
466
+ background: rgba(31, 41, 55, 0.6);
467
+ border-radius: 10px;
468
+ min-width: 120px;
469
+ }
470
+
471
+ .ai-result-metric-value {
472
+ font-size: 28px;
473
+ font-weight: 800;
474
+ margin-bottom: 5px;
475
+ }
476
+
477
+ .ai-result-metric-label {
478
+ font-size: 12px;
479
+ color: var(--text-secondary);
480
+ text-transform: uppercase;
481
+ }
482
+
483
+ /* Model Status Indicators */
484
+ .model-status {
485
+ display: inline-flex;
486
+ align-items: center;
487
+ gap: 6px;
488
+ padding: 4px 10px;
489
+ border-radius: 6px;
490
+ font-size: 12px;
491
+ font-weight: 600;
492
+ }
493
+
494
+ .model-status.available {
495
+ background: rgba(16, 185, 129, 0.15);
496
+ color: var(--success);
497
+ }
498
+
499
+ .model-status.unavailable {
500
+ background: rgba(239, 68, 68, 0.15);
501
+ color: var(--danger);
502
+ }
503
+
504
+ .model-status.partial {
505
+ background: rgba(245, 158, 11, 0.15);
506
+ color: var(--warning);
507
+ }
508
+
509
+ /* Form Improvements for AI Sections */
510
+ .form-group input[type="text"] {
511
+ text-transform: uppercase;
512
+ }
513
+
514
+ .form-group textarea {
515
+ resize: vertical;
516
+ min-height: 80px;
517
+ }
518
+
519
+ /* Loading States */
520
+ .loading {
521
+ display: flex;
522
+ flex-direction: column;
523
+ align-items: center;
524
+ justify-content: center;
525
+ padding: 40px;
526
+ color: var(--text-secondary);
527
+ }
528
+
529
+ .loading .spinner {
530
+ margin-bottom: 15px;
531
+ }
532
+
533
+ /* Confidence Bar */
534
+ .confidence-bar {
535
+ width: 100%;
536
+ height: 8px;
537
+ background: rgba(31, 41, 55, 0.6);
538
+ border-radius: 4px;
539
+ overflow: hidden;
540
+ margin-top: 5px;
541
+ }
542
+
543
+ .confidence-fill {
544
+ height: 100%;
545
+ background: linear-gradient(90deg, var(--primary), var(--primary-dark));
546
+ transition: width 0.3s ease;
547
+ }
548
+
549
+ .confidence-fill.high {
550
+ background: linear-gradient(90deg, var(--success), #059669);
551
+ }
552
+
553
+ .confidence-fill.low {
554
+ background: linear-gradient(90deg, var(--danger), #dc2626);
555
+ }
556
+
557
  /* Responsive */
558
  @media (max-width: 768px) {
559
  .header-content {
 
576
  .stats-grid {
577
  grid-template-columns: 1fr;
578
  }
579
+
580
+ .ai-result-metric {
581
+ min-width: 100px;
582
+ padding: 10px;
583
+ }
584
+
585
+ .ai-result-metric-value {
586
+ font-size: 20px;
587
+ }
588
+
589
+ .card {
590
+ padding: 15px;
591
+ }
592
  }
593
 
static/js/app.js CHANGED
@@ -514,9 +514,213 @@ async function loadSentimentModels() {
514
  }
515
  }
516
 
517
- // Analyze Sentiment
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
518
  async function analyzeSentiment() {
519
  const text = document.getElementById('sentiment-text').value;
 
520
  const modelKey = document.getElementById('sentiment-model').value;
521
 
522
  if (!text.trim()) {
@@ -528,60 +732,35 @@ async function analyzeSentiment() {
528
  resultDiv.innerHTML = '<div class="loading"><div class="spinner"></div> در حال تحلیل...</div>';
529
 
530
  try {
531
- // Try multiple endpoints
532
  let response;
533
- let endpoint = '/api/hf/run-sentiment';
534
 
535
- // If model key is selected, try model-specific endpoint
536
- if (modelKey) {
537
- try {
538
- endpoint = `/api/models/${modelKey}/predict`;
539
- response = await fetch(endpoint, {
540
- method: 'POST',
541
- headers: { 'Content-Type': 'application/json' },
542
- body: JSON.stringify({ text: text })
543
- });
544
- } catch {
545
- // Fallback to general sentiment endpoint
546
- endpoint = '/api/hf/run-sentiment';
547
- response = await fetch(endpoint, {
548
- method: 'POST',
549
- headers: { 'Content-Type': 'application/json' },
550
- body: JSON.stringify({ texts: [text], model_key: modelKey })
551
- });
552
- }
553
- } else {
554
- response = await fetch('/api/hf/run-sentiment', {
555
- method: 'POST',
556
- headers: { 'Content-Type': 'application/json' },
557
- body: JSON.stringify({ texts: [text] })
558
- });
559
- }
560
 
561
  const data = await response.json();
562
 
563
- // Handle different response formats
564
- let result;
565
- if (data.results && data.results.length > 0) {
566
- result = data.results[0];
567
- } else if (data.result) {
568
- result = data.result;
569
- } else if (data.label || data.sentiment) {
570
- result = data;
571
- } else {
572
- throw new Error('فرمت پاسخ نامعتبر است');
573
  }
574
 
575
- const label = result.label || result.sentiment || 'unknown';
576
- const confidence = result.confidence || result.score || 0;
577
- const vote = result.vote || 0;
578
- const modelUsed = result.model || modelKey || 'default';
579
 
580
  // Determine sentiment emoji and color
581
- const sentimentEmoji = label.toUpperCase().includes('POSITIVE') || label.toUpperCase().includes('BULLISH') ? '📈' :
582
- label.toUpperCase().includes('NEGATIVE') || label.toUpperCase().includes('BEARISH') ? '📉' : '➡️';
583
- const sentimentColor = label.toUpperCase().includes('POSITIVE') || label.toUpperCase().includes('BULLISH') ? 'var(--success)' :
584
- label.toUpperCase().includes('NEGATIVE') || label.toUpperCase().includes('BEARISH') ? 'var(--danger)' : 'var(--text-secondary)';
585
 
586
  resultDiv.innerHTML = `
587
  <div class="alert alert-success" style="margin-top: 20px; border-left: 4px solid ${sentimentColor};">
@@ -590,7 +769,8 @@ async function analyzeSentiment() {
590
  <div>
591
  <strong>احساسات:</strong>
592
  <span style="color: ${sentimentColor}; font-weight: 700; font-size: 18px;">
593
- ${sentimentEmoji} ${label}
 
594
  </span>
595
  </div>
596
  <div>
@@ -599,17 +779,9 @@ async function analyzeSentiment() {
599
  ${(confidence * 100).toFixed(2)}%
600
  </span>
601
  </div>
602
- ${vote !== undefined ? `
603
- <div>
604
- <strong>رأی مدل:</strong>
605
- <span style="color: ${vote > 0 ? 'var(--success)' : vote < 0 ? 'var(--danger)' : 'var(--text-secondary)'}; font-weight: 600;">
606
- ${vote > 0 ? '↑' : vote < 0 ? '↓' : '→'} ${vote.toFixed(2)}
607
- </span>
608
- </div>
609
- ` : ''}
610
  <div>
611
- <strong>مدل استفاده شده:</strong>
612
- <span style="font-family: monospace; font-size: 12px; color: var(--text-secondary);">${modelUsed}</span>
613
  </div>
614
  <div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid var(--border);">
615
  <strong>متن تحلیل شده:</strong>
@@ -626,7 +798,7 @@ async function analyzeSentiment() {
626
  text: text.substring(0, 100),
627
  label: label,
628
  confidence: confidence,
629
- model: modelUsed,
630
  timestamp: new Date().toISOString()
631
  });
632
 
 
514
  }
515
  }
516
 
517
+ // Analyze Global Market Sentiment
518
+ async function analyzeGlobalSentiment() {
519
+ const resultDiv = document.getElementById('global-sentiment-result');
520
+ resultDiv.innerHTML = '<div class="loading"><div class="spinner"></div> در حال تحلیل احساسات بازار...</div>';
521
+
522
+ try {
523
+ // Use market text analysis with sample market-related text
524
+ const marketText = "Cryptocurrency market analysis: Bitcoin, Ethereum, and major altcoins showing mixed signals. Market sentiment analysis required.";
525
+
526
+ const response = await fetch('/api/sentiment/analyze', {
527
+ method: 'POST',
528
+ headers: { 'Content-Type': 'application/json' },
529
+ body: JSON.stringify({ text: marketText, mode: 'crypto' })
530
+ });
531
+
532
+ const data = await response.json();
533
+
534
+ if (!data.available) {
535
+ resultDiv.innerHTML = `
536
+ <div class="alert alert-warning">
537
+ <strong>⚠️ مدل‌ها در دسترس نیستند:</strong> ${data.error || 'مدل‌های AI در حال حاضر در دسترس نیستند'}
538
+ </div>
539
+ `;
540
+ return;
541
+ }
542
+
543
+ const sentiment = data.sentiment || 'neutral';
544
+ const confidence = data.confidence || 0;
545
+ const sentimentEmoji = sentiment === 'bullish' ? '📈' : sentiment === 'bearish' ? '📉' : '➡️';
546
+ const sentimentColor = sentiment === 'bullish' ? 'var(--success)' : sentiment === 'bearish' ? 'var(--danger)' : 'var(--text-secondary)';
547
+
548
+ resultDiv.innerHTML = `
549
+ <div class="alert alert-success" style="border-left: 4px solid ${sentimentColor};">
550
+ <h4 style="margin-bottom: 15px;">احساسات کلی بازار</h4>
551
+ <div style="display: grid; gap: 10px;">
552
+ <div style="text-align: center; padding: 20px;">
553
+ <div style="font-size: 48px; margin-bottom: 10px;">${sentimentEmoji}</div>
554
+ <div style="font-size: 24px; font-weight: 700; color: ${sentimentColor}; margin-bottom: 5px;">
555
+ ${sentiment === 'bullish' ? 'صعودی' : sentiment === 'bearish' ? 'نزولی' : 'خنثی'}
556
+ </div>
557
+ <div style="color: var(--text-secondary);">
558
+ اعتماد: ${(confidence * 100).toFixed(1)}%
559
+ </div>
560
+ </div>
561
+ <div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid var(--border);">
562
+ <strong>جزئیات:</strong>
563
+ <div style="margin-top: 5px; font-size: 13px; color: var(--text-secondary);">
564
+ این تحلیل بر اساس مدل‌های AI انجام شده است.
565
+ </div>
566
+ </div>
567
+ </div>
568
+ </div>
569
+ `;
570
+ } catch (error) {
571
+ console.error('Global sentiment analysis error:', error);
572
+ resultDiv.innerHTML = `<div class="alert alert-error">خطا در تحلیل: ${error.message}</div>`;
573
+ showError('خطا در تحلیل احساسات بازار');
574
+ }
575
+ }
576
+
577
+ // Analyze Asset Sentiment
578
+ async function analyzeAssetSentiment() {
579
+ const symbol = document.getElementById('asset-symbol').value.trim().toUpperCase();
580
+ const text = document.getElementById('asset-sentiment-text').value.trim();
581
+
582
+ if (!symbol) {
583
+ showError('لطفاً نماد ارز را وارد کنید');
584
+ return;
585
+ }
586
+
587
+ const resultDiv = document.getElementById('asset-sentiment-result');
588
+ resultDiv.innerHTML = '<div class="loading"><div class="spinner"></div> در حال تحلیل...</div>';
589
+
590
+ try {
591
+ // Use provided text or default text with symbol
592
+ const analysisText = text || `${symbol} market analysis and sentiment`;
593
+
594
+ const response = await fetch('/api/sentiment/analyze', {
595
+ method: 'POST',
596
+ headers: { 'Content-Type': 'application/json' },
597
+ body: JSON.stringify({ text: analysisText, mode: 'crypto', symbol: symbol })
598
+ });
599
+
600
+ const data = await response.json();
601
+
602
+ if (!data.available) {
603
+ resultDiv.innerHTML = `
604
+ <div class="alert alert-warning">
605
+ <strong>⚠️ مدل‌ها در دسترس نیستند:</strong> ${data.error || 'مدل‌های AI در حال حاضر در دسترس نیستند'}
606
+ </div>
607
+ `;
608
+ return;
609
+ }
610
+
611
+ const sentiment = data.sentiment || 'neutral';
612
+ const confidence = data.confidence || 0;
613
+ const sentimentEmoji = sentiment === 'bullish' ? '📈' : sentiment === 'bearish' ? '📉' : '➡️';
614
+ const sentimentColor = sentiment === 'bullish' ? 'var(--success)' : sentiment === 'bearish' ? 'var(--danger)' : 'var(--text-secondary)';
615
+
616
+ resultDiv.innerHTML = `
617
+ <div class="alert alert-success" style="border-left: 4px solid ${sentimentColor};">
618
+ <h4 style="margin-bottom: 15px;">نتیجه تحلیل احساسات ${symbol}</h4>
619
+ <div style="display: grid; gap: 10px;">
620
+ <div>
621
+ <strong>احساسات:</strong>
622
+ <span style="color: ${sentimentColor}; font-weight: 700; font-size: 18px;">
623
+ ${sentimentEmoji} ${sentiment === 'bullish' ? 'صعودی' : sentiment === 'bearish' ? 'نزولی' : 'خنثی'}
624
+ </span>
625
+ </div>
626
+ <div>
627
+ <strong>اعتماد:</strong>
628
+ <span style="color: var(--primary); font-weight: 600;">
629
+ ${(confidence * 100).toFixed(2)}%
630
+ </span>
631
+ </div>
632
+ ${text ? `
633
+ <div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid var(--border);">
634
+ <strong>متن تحلیل شده:</strong>
635
+ <div style="margin-top: 5px; padding: 10px; background: rgba(31, 41, 55, 0.6); border-radius: 5px; font-size: 13px; color: var(--text-secondary);">
636
+ "${text.substring(0, 200)}${text.length > 200 ? '...' : ''}"
637
+ </div>
638
+ </div>
639
+ ` : ''}
640
+ </div>
641
+ </div>
642
+ `;
643
+ } catch (error) {
644
+ console.error('Asset sentiment analysis error:', error);
645
+ resultDiv.innerHTML = `<div class="alert alert-error">خطا در تحلیل: ${error.message}</div>`;
646
+ showError('خطا در تحلیل احساسات ارز');
647
+ }
648
+ }
649
+
650
+ // Analyze News Sentiment
651
+ async function analyzeNewsSentiment() {
652
+ const title = document.getElementById('news-title').value.trim();
653
+ const content = document.getElementById('news-content').value.trim();
654
+
655
+ if (!title && !content) {
656
+ showError('لطفاً عنوان یا محتوای خبر را وارد کنید');
657
+ return;
658
+ }
659
+
660
+ const resultDiv = document.getElementById('news-sentiment-result');
661
+ resultDiv.innerHTML = '<div class="loading"><div class="spinner"></div> در حال تحلیل...</div>';
662
+
663
+ try {
664
+ const response = await fetch('/api/news/analyze', {
665
+ method: 'POST',
666
+ headers: { 'Content-Type': 'application/json' },
667
+ body: JSON.stringify({ title: title, content: content, description: content })
668
+ });
669
+
670
+ const data = await response.json();
671
+
672
+ if (!data.available) {
673
+ resultDiv.innerHTML = `
674
+ <div class="alert alert-warning">
675
+ <strong>⚠️ مدل‌ها در دسترس نیستند:</strong> ${data.news?.error || data.error || 'مدل‌های AI در حال حاضر در دسترس نیستند'}
676
+ </div>
677
+ `;
678
+ return;
679
+ }
680
+
681
+ const newsData = data.news || {};
682
+ const sentiment = newsData.sentiment || 'neutral';
683
+ const confidence = newsData.confidence || 0;
684
+ const sentimentEmoji = sentiment === 'bullish' || sentiment === 'positive' ? '📈' :
685
+ sentiment === 'bearish' || sentiment === 'negative' ? '📉' : '➡️';
686
+ const sentimentColor = sentiment === 'bullish' || sentiment === 'positive' ? 'var(--success)' :
687
+ sentiment === 'bearish' || sentiment === 'negative' ? 'var(--danger)' : 'var(--text-secondary)';
688
+
689
+ resultDiv.innerHTML = `
690
+ <div class="alert alert-success" style="border-left: 4px solid ${sentimentColor};">
691
+ <h4 style="margin-bottom: 15px;">نتیجه تحلیل خبر</h4>
692
+ <div style="display: grid; gap: 10px;">
693
+ <div>
694
+ <strong>عنوان:</strong>
695
+ <span style="color: var(--text-primary);">${title || 'بدون عنوان'}</span>
696
+ </div>
697
+ <div>
698
+ <strong>احساسات:</strong>
699
+ <span style="color: ${sentimentColor}; font-weight: 700; font-size: 18px;">
700
+ ${sentimentEmoji} ${sentiment === 'bullish' || sentiment === 'positive' ? 'مثبت' :
701
+ sentiment === 'bearish' || sentiment === 'negative' ? 'منفی' : 'خنثی'}
702
+ </span>
703
+ </div>
704
+ <div>
705
+ <strong>اعتماد:</strong>
706
+ <span style="color: var(--primary); font-weight: 600;">
707
+ ${(confidence * 100).toFixed(2)}%
708
+ </span>
709
+ </div>
710
+ </div>
711
+ </div>
712
+ `;
713
+ } catch (error) {
714
+ console.error('News sentiment analysis error:', error);
715
+ resultDiv.innerHTML = `<div class="alert alert-error">خطا در تحلیل: ${error.message}</div>`;
716
+ showError('خطا در تحلیل خبر');
717
+ }
718
+ }
719
+
720
+ // Analyze Sentiment (updated)
721
  async function analyzeSentiment() {
722
  const text = document.getElementById('sentiment-text').value;
723
+ const mode = document.getElementById('sentiment-mode').value;
724
  const modelKey = document.getElementById('sentiment-model').value;
725
 
726
  if (!text.trim()) {
 
732
  resultDiv.innerHTML = '<div class="loading"><div class="spinner"></div> در حال تحلیل...</div>';
733
 
734
  try {
 
735
  let response;
 
736
 
737
+ // Use the sentiment/analyze endpoint with mode
738
+ response = await fetch('/api/sentiment/analyze', {
739
+ method: 'POST',
740
+ headers: { 'Content-Type': 'application/json' },
741
+ body: JSON.stringify({ text: text, mode: mode })
742
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
743
 
744
  const data = await response.json();
745
 
746
+ if (!data.available) {
747
+ resultDiv.innerHTML = `
748
+ <div class="alert alert-warning">
749
+ <strong>⚠️ مدل‌ها در دسترس نیستند:</strong> ${data.error || 'مدل‌های AI در حال حاضر در دسترس نیستند'}
750
+ </div>
751
+ `;
752
+ return;
 
 
 
753
  }
754
 
755
+ const label = data.sentiment || 'neutral';
756
+ const confidence = data.confidence || 0;
757
+ const result = data.result || {};
 
758
 
759
  // Determine sentiment emoji and color
760
+ const sentimentEmoji = label === 'bullish' || label === 'positive' ? '📈' :
761
+ label === 'bearish' || label === 'negative' ? '📉' : '➡️';
762
+ const sentimentColor = label === 'bullish' || label === 'positive' ? 'var(--success)' :
763
+ label === 'bearish' || label === 'negative' ? 'var(--danger)' : 'var(--text-secondary)';
764
 
765
  resultDiv.innerHTML = `
766
  <div class="alert alert-success" style="margin-top: 20px; border-left: 4px solid ${sentimentColor};">
 
769
  <div>
770
  <strong>احساسات:</strong>
771
  <span style="color: ${sentimentColor}; font-weight: 700; font-size: 18px;">
772
+ ${sentimentEmoji} ${label === 'bullish' || label === 'positive' ? 'صعودی/مثبت' :
773
+ label === 'bearish' || label === 'negative' ? 'نزولی/منفی' : 'خنثی'}
774
  </span>
775
  </div>
776
  <div>
 
779
  ${(confidence * 100).toFixed(2)}%
780
  </span>
781
  </div>
 
 
 
 
 
 
 
 
782
  <div>
783
+ <strong>نوع تحلیل:</strong>
784
+ <span style="color: var(--text-secondary);">${mode}</span>
785
  </div>
786
  <div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid var(--border);">
787
  <strong>متن تحلیل شده:</strong>
 
798
  text: text.substring(0, 100),
799
  label: label,
800
  confidence: confidence,
801
+ model: mode,
802
  timestamp: new Date().toISOString()
803
  });
804