Commit 1a941ef5 authored by xeron56's avatar xeron56

Limit CLI languages and remove legacy promotions

parent 0d1afdc8
......@@ -66,7 +66,7 @@ DSE_PASSWORD=
#TRADINGAGENTS_DEEP_THINK_LLM=google/gemini-3.1-pro-preview
#TRADINGAGENTS_QUICK_THINK_LLM=google/gemini-3.5-flash
#TRADINGAGENTS_LLM_BACKEND_URL=
#TRADINGAGENTS_OUTPUT_LANGUAGE=English
#TRADINGAGENTS_OUTPUT_LANGUAGE=English # Supported: English or Bangla
#TRADINGAGENTS_MAX_DEBATE_ROUNDS=1
#TRADINGAGENTS_MAX_RISK_ROUNDS=1
#TRADINGAGENTS_CHECKPOINT_ENABLED=false
......
......@@ -217,7 +217,7 @@ The CLI asks for:
1. DSE ticker.
2. Analysis date.
3. Output language.
3. Output language: English or Bangla (`বাংলা`).
4. Analyst selection.
5. Research/debate depth.
6. LLM provider and models.
......
import getpass
import requests
from rich.console import Console
from rich.panel import Panel
from cli.config import CLI_CONFIG
def fetch_announcements(url: str = None, timeout: float = None) -> dict:
"""Fetch announcements from endpoint. Returns dict with announcements and settings."""
endpoint = url or CLI_CONFIG["announcements_url"]
timeout = timeout or CLI_CONFIG["announcements_timeout"]
fallback = CLI_CONFIG["announcements_fallback"]
try:
response = requests.get(endpoint, timeout=timeout)
response.raise_for_status()
data = response.json()
return {
"announcements": data.get("announcements", [fallback]),
"require_attention": data.get("require_attention", False),
}
except Exception:
return {
"announcements": [fallback],
"require_attention": False,
}
def display_announcements(console: Console, data: dict) -> None:
"""Display announcements panel. Prompts for Enter if require_attention is True."""
announcements = data.get("announcements", [])
require_attention = data.get("require_attention", False)
if not announcements:
return
content = "\n".join(announcements)
panel = Panel(
content,
border_style="cyan",
padding=(1, 2),
title="Announcements",
)
console.print(panel)
if require_attention:
getpass.getpass("Press Enter to continue...")
else:
console.print()
CLI_CONFIG = {
# Announcements
"announcements_url": "https://api.tauric.ai/v1/announcements",
"announcements_timeout": 1.0,
"announcements_fallback": "[cyan]For more information, please visit[/cyan] [link=https://github.com/TauricResearch]https://github.com/TauricResearch[/link]",
}
......@@ -19,7 +19,6 @@ from rich.spinner import Spinner
from rich.table import Table
from rich.text import Text
from cli.announcements import display_announcements, fetch_announcements
from cli.stats_handler import StatsCallbackHandler
from cli.utils import (
ask_anthropic_effort,
......@@ -523,8 +522,7 @@ def get_user_selections():
welcome_content += f"[bold green]{PRODUCT_DISPLAY_NAME}[/bold green]\n"
welcome_content += f"[dim]{PRODUCT_TAGLINE}[/dim]\n\n"
welcome_content += "[bold]Workflow Steps:[/bold]\n"
welcome_content += "I. Analyst Team → II. Research Team → III. Trader → IV. Risk Management → V. Portfolio Management\n\n"
welcome_content += "[dim]Powered by the TradingAgents framework[/dim]"
welcome_content += "I. Analyst Team → II. Research Team → III. Trader → IV. Risk Management → V. Portfolio Management"
# Create and center the welcome box
welcome_box = Panel(
......@@ -536,11 +534,6 @@ def get_user_selections():
)
console.print(Align.center(welcome_box))
console.print()
console.print() # Add vertical space before announcements
# Fetch and display announcements (silent on failure)
announcements = fetch_announcements()
display_announcements(console, announcements)
# Create a boxed questionnaire for each step
def create_question_box(title, prompt, default=None):
......
......@@ -7,6 +7,7 @@ from rich.console import Console
from cli.models import AnalystType, AssetType
from dohasecuritiesstockai.default_config import DEFAULT_CONFIG
from dohasecuritiesstockai.languages import OUTPUT_LANGUAGE_CHOICES
from dohasecuritiesstockai.llm_clients.api_key_env import get_api_key_env
from dohasecuritiesstockai.llm_clients.model_catalog import get_model_options
......@@ -681,22 +682,12 @@ def ensure_api_key(provider: str) -> str | None:
def ask_output_language() -> str:
"""Ask for report output language."""
"""Ask for the English or Bangla report-output language."""
choice = questionary.select(
"Select Output Language:",
choices=[
questionary.Choice("English (default)", "English"),
questionary.Choice("Chinese (中文)", "Chinese"),
questionary.Choice("Japanese (日本語)", "Japanese"),
questionary.Choice("Korean (한국어)", "Korean"),
questionary.Choice("Hindi (हिन्दी)", "Hindi"),
questionary.Choice("Spanish (Español)", "Spanish"),
questionary.Choice("Portuguese (Português)", "Portuguese"),
questionary.Choice("French (Français)", "French"),
questionary.Choice("German (Deutsch)", "German"),
questionary.Choice("Arabic (العربية)", "Arabic"),
questionary.Choice("Russian (Русский)", "Russian"),
questionary.Choice("Custom language", "custom"),
questionary.Choice(label, value)
for label, value in OUTPUT_LANGUAGE_CHOICES
],
style=questionary.Style([
("selected", "fg:yellow noinherit"),
......@@ -709,10 +700,4 @@ def ask_output_language() -> str:
# rather than exiting the run (unlike the required model/provider prompts).
if choice is None:
return "English"
if choice == "custom":
return (questionary.text(
"Enter language name (e.g. Turkish, Vietnamese, Thai, Indonesian):",
validate=lambda x: len(x.strip()) > 0 or "Please enter a language name.",
).ask() or "").strip() or "English"
return choice
......@@ -61,10 +61,15 @@ def get_language_instruction() -> str:
report rather than a mix of languages.
"""
from dohasecuritiesstockai.dataflows.config import get_config
lang = get_config().get("output_language", "English")
if lang.strip().lower() == "english":
from dohasecuritiesstockai.languages import normalize_output_language
language = normalize_output_language(get_config().get("output_language", "English"))
if language == "English":
return ""
return f" Write your entire response in {lang}."
return (
" Write your entire response in natural Bangla using বাংলা script."
" Keep ticker symbols, standard financial abbreviations, and numeric values unchanged."
)
def _clean_identity_value(value: Any) -> str | None:
......@@ -219,4 +224,3 @@ def create_msg_delete():
return {"messages": removal_operations + [placeholder]}
return delete_messages
......@@ -40,7 +40,7 @@ _RSS = "https://www.reddit.com/r/{sub}/search.rss?{qs}"
# blocks generic/anonymous tokens like bare "Mozilla/5.0" or "curl/…" but
# serves this one on both endpoints; the RSS feed accepts it even when the
# JSON search endpoint 403s, so no browser-spoofing is needed.
_UA = "dohasecuritiesstockai/0.2 (+https://github.com/TauricResearch/TradingAgents)"
_UA = "dohasecuritiesstockai/0.2 (+https://github.com/xeron56/DohasecuritiesStockAi)"
_ATOM_NS = {"atom": "http://www.w3.org/2005/Atom"}
# Default subreddits ordered roughly by signal density for ticker-specific
......
......@@ -24,7 +24,7 @@ from .symbol_utils import crypto_base
logger = logging.getLogger(__name__)
_API = "https://api.stocktwits.com/api/2/streams/symbol/{ticker}.json"
_UA = "dohasecuritiesstockai/0.2 (+https://github.com/TauricResearch/TradingAgents)"
_UA = "dohasecuritiesstockai/0.2 (+https://github.com/xeron56/DohasecuritiesStockAi)"
def _stocktwits_symbol(ticker: str) -> str:
......
import os
from dohasecuritiesstockai.languages import normalize_output_language
# Keep the established data directory so the package rename does not orphan
# existing reports, checkpoints, caches, or the trading-memory log.
_TRADINGAGENTS_HOME = os.path.join(os.path.expanduser("~"), ".tradingagents")
......@@ -72,7 +74,10 @@ def _apply_env_overrides(config: dict) -> dict:
if raw is None or raw == "":
continue
try:
config[key] = _coerce(raw, config.get(key))
value = _coerce(raw, config.get(key))
if key == "output_language":
value = normalize_output_language(value)
config[key] = value
except ValueError as exc:
raise ValueError(f"Invalid value for {env_var}: {exc}") from exc
return config
......
"""Supported report-output languages."""
OUTPUT_LANGUAGE_CHOICES = (
("English (default)", "English"),
("Bangla (বাংলা)", "Bangla"),
)
_OUTPUT_LANGUAGE_ALIASES = {
"english": "English",
"en": "English",
"bangla": "Bangla",
"bengali": "Bangla",
"বাংলা": "Bangla",
"bn": "Bangla",
}
def normalize_output_language(value: str | None) -> str:
"""Return the canonical supported language name or raise ``ValueError``."""
normalized = str(value or "English").strip().casefold()
language = _OUTPUT_LANGUAGE_ALIASES.get(normalized)
if language is None:
raise ValueError("output language must be English or Bangla")
return language
......@@ -40,7 +40,7 @@ class TestCliSkipsPromptsFromEnv(unittest.TestCase):
"TRADINGAGENTS_DEEP_THINK_LLM": "kimi-k2.5",
"TRADINGAGENTS_QUICK_THINK_LLM": "deepseek-v4-pro",
"TRADINGAGENTS_LLM_BACKEND_URL": "https://opencode.ai/zen/go/v1",
"TRADINGAGENTS_OUTPUT_LANGUAGE": "Japanese",
"TRADINGAGENTS_OUTPUT_LANGUAGE": "Bangla",
}
fake_cfg = dict(m.DEFAULT_CONFIG)
fake_cfg.update({
......@@ -48,13 +48,11 @@ class TestCliSkipsPromptsFromEnv(unittest.TestCase):
"backend_url": "https://opencode.ai/zen/go/v1",
"quick_think_llm": "deepseek-v4-pro",
"deep_think_llm": "kimi-k2.5",
"output_language": "Japanese",
"output_language": "Bangla",
})
with mock.patch.dict(os.environ, env, clear=False), \
mock.patch.object(m, "DEFAULT_CONFIG", fake_cfg), \
mock.patch.object(m, "fetch_announcements", return_value=None), \
mock.patch.object(m, "display_announcements"), \
mock.patch.object(m, "get_ticker", return_value="AAPL"), \
mock.patch.object(m, "get_analysis_date", return_value="2026-05-29"), \
mock.patch.object(m, "select_analysts", return_value=[]), \
......@@ -79,7 +77,7 @@ class TestCliSkipsPromptsFromEnv(unittest.TestCase):
self.assertEqual(sel["backend_url"], "https://opencode.ai/zen/go/v1")
self.assertEqual(sel["shallow_thinker"], "deepseek-v4-pro")
self.assertEqual(sel["deep_thinker"], "kimi-k2.5")
self.assertEqual(sel["output_language"], "Japanese")
self.assertEqual(sel["output_language"], "Bangla")
@pytest.mark.unit
......@@ -96,8 +94,6 @@ class TestResearchDepthSkippedFromEnv(unittest.TestCase):
with mock.patch.dict(os.environ, env, clear=False), \
mock.patch.object(m, "DEFAULT_CONFIG", fake_cfg), \
mock.patch.object(m, "fetch_announcements", return_value=None), \
mock.patch.object(m, "display_announcements"), \
mock.patch.object(m, "get_ticker", return_value="AAPL"), \
mock.patch.object(m, "get_analysis_date", return_value="2026-05-29"), \
mock.patch.object(m, "select_analysts", return_value=[]), \
......@@ -126,8 +122,6 @@ class TestReasoningEffortSkippedFromEnv(unittest.TestCase):
with mock.patch.dict(os.environ, env, clear=False), \
mock.patch.object(m, "DEFAULT_CONFIG", fake_cfg), \
mock.patch.object(m, "fetch_announcements", return_value=None), \
mock.patch.object(m, "display_announcements"), \
mock.patch.object(m, "get_ticker", return_value="AAPL"), \
mock.patch.object(m, "get_analysis_date", return_value="2026-05-29"), \
mock.patch.object(m, "select_analysts", return_value=[]), \
......
......@@ -35,13 +35,26 @@ def test_string_overrides(monkeypatch):
TRADINGAGENTS_DEEP_THINK_LLM="gemini-3-pro-preview",
TRADINGAGENTS_QUICK_THINK_LLM="gemini-3-flash-preview",
TRADINGAGENTS_LLM_BACKEND_URL="https://example.invalid/v1",
TRADINGAGENTS_OUTPUT_LANGUAGE="Chinese",
TRADINGAGENTS_OUTPUT_LANGUAGE="Bangla",
)
assert dc.DEFAULT_CONFIG["llm_provider"] == "google"
assert dc.DEFAULT_CONFIG["deep_think_llm"] == "gemini-3-pro-preview"
assert dc.DEFAULT_CONFIG["quick_think_llm"] == "gemini-3-flash-preview"
assert dc.DEFAULT_CONFIG["backend_url"] == "https://example.invalid/v1"
assert dc.DEFAULT_CONFIG["output_language"] == "Chinese"
assert dc.DEFAULT_CONFIG["output_language"] == "Bangla"
@pytest.mark.parametrize("alias", ["Bangla", "Bengali", "বাংলা", "bn"])
def test_bangla_output_language_aliases(monkeypatch, alias):
dc = _reload_with_env(monkeypatch, TRADINGAGENTS_OUTPUT_LANGUAGE=alias)
assert dc.DEFAULT_CONFIG["output_language"] == "Bangla"
def test_unsupported_output_language_is_rejected(monkeypatch):
with pytest.raises(ValueError, match="English or Bangla"):
_reload_with_env(monkeypatch, TRADINGAGENTS_OUTPUT_LANGUAGE="Chinese")
monkeypatch.delenv("TRADINGAGENTS_OUTPUT_LANGUAGE", raising=False)
importlib.reload(default_config_module)
def test_int_coercion(monkeypatch):
......
......@@ -39,13 +39,21 @@ class TestLanguageInstruction:
set_config({"output_language": "English"})
assert get_language_instruction() == ""
def test_non_english_emits_directive(self):
def test_bangla_emits_bangla_script_directive(self):
from dohasecuritiesstockai.dataflows.config import set_config
set_config({"output_language": "中文"})
set_config({"output_language": "Bangla"})
out = get_language_instruction()
assert "中文" in out
assert "Bangla" in out
assert "বাংলা" in out
assert "entire response" in out
def test_unsupported_language_is_rejected(self):
from dohasecuritiesstockai.dataflows.config import set_config
set_config({"output_language": "Chinese"})
with pytest.raises(ValueError, match="English or Bangla"):
get_language_instruction()
@pytest.mark.unit
@pytest.mark.parametrize("rel", REPORT_AGENTS)
......
"""OpenRouter model selection: prompts are labeled by mode (#1000); required
prompts exit cleanly on cancel; the output-language prompt defaults to English
on cancel; and the OpenRouter list is newest-first."""
"""OpenRouter and output-language selection behavior."""
from unittest import mock
......@@ -111,12 +109,23 @@ class TestCancelExitsCleanly:
@pytest.mark.unit
class TestLanguageDefaultsToEnglish:
class TestOutputLanguage:
def test_select_cancel_defaults_english(self):
with mock.patch.object(utils.questionary, "select", return_value=_asks(None)):
assert utils.ask_output_language() == "English"
def test_custom_language_cancel_defaults_english(self):
with mock.patch.object(utils.questionary, "select", return_value=_asks("custom")), \
mock.patch.object(utils.questionary, "text", return_value=_asks(None)):
assert utils.ask_output_language() == "English"
def test_selector_only_offers_english_and_bangla(self):
captured = {}
def fake_select(message, **kwargs):
captured["message"] = message
captured["labels"] = [choice.title for choice in kwargs["choices"]]
captured["values"] = [choice.value for choice in kwargs["choices"]]
return _asks("Bangla")
with mock.patch.object(utils.questionary, "select", side_effect=fake_select):
assert utils.ask_output_language() == "Bangla"
assert captured["message"] == "Select Output Language:"
assert captured["values"] == ["English", "Bangla"]
assert captured["labels"] == ["English (default)", "Bangla (বাংলা)"]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment