cereeenn120 commited on
Commit
bdc8662
·
1 Parent(s): 773e767

Add multi-language translator

Browse files
Files changed (3) hide show
  1. app.py +4 -2
  2. engine/chat.py +60 -27
  3. engine/translator.py +94 -0
app.py CHANGED
@@ -37,8 +37,9 @@ MODELS = [
37
 
38
  LANGUAGES = ["English", "French", "German", "Italian", "Russian", "Turkish", "Chinese"]
39
 
40
- DEFAULT_MODEL = MODELS[14][1]
41
- DEFAULT_LANGUAGE = "English"
 
42
 
43
  #MODEL = "deepseek-ai/DeepSeek-V3" # or e.g. "openai/gpt-4o-mini"
44
  #MODEL = "Qwen/Qwen3-235B-A22B-Instruct-2507"
@@ -113,6 +114,7 @@ def respond(
113
  temperature=temperature,
114
  top_p=top_p,
115
  language=language,
 
116
  )
117
 
118
 
 
37
 
38
  LANGUAGES = ["English", "French", "German", "Italian", "Russian", "Turkish", "Chinese"]
39
 
40
+ DEFAULT_MODEL = MODELS[14][1]
41
+ DEFAULT_LANGUAGE = "English"
42
+ TRANSLATION_MODEL = "openai/gpt-oss-120b"
43
 
44
  #MODEL = "deepseek-ai/DeepSeek-V3" # or e.g. "openai/gpt-4o-mini"
45
  #MODEL = "Qwen/Qwen3-235B-A22B-Instruct-2507"
 
114
  temperature=temperature,
115
  top_p=top_p,
116
  language=language,
117
+ translation_model=TRANSLATION_MODEL,
118
  )
119
 
120
 
engine/chat.py CHANGED
@@ -22,20 +22,22 @@ from engine.memory import (
22
  )
23
  from engine.parser import parse_response, segments_to_markdown
24
  from engine.prompt_builder import build_messages, load_character
 
25
 
26
  logger = logging.getLogger(__name__)
27
 
28
 
29
  def generate_response(
30
- message: str,
31
- character_id: str,
32
- client: InferenceClient,
33
- persona: dict | None = None,
34
- model: str = "deepseek-ai/DeepSeek-V3",
35
- max_tokens: int = 512,
36
- temperature: float = 0.7,
37
- top_p: float = 0.95,
38
- language: str = "English",
 
39
  ):
40
  """
41
  Core streaming generator.
@@ -78,14 +80,26 @@ def generate_response(
78
  summary = load_summary(character_id)
79
  long_term_memory = load_long_term_memory(character_id)
80
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  messages = build_messages(
82
  character=character,
83
  persona=persona,
84
  summary=summary,
85
  long_term_memory=long_term_memory,
86
  recent_messages=recent_messages,
87
- current_message=message,
88
- language=language,
89
  )
90
 
91
  # ── LLM call ──────────────────────────────────────────────────────────────
@@ -93,18 +107,32 @@ def generate_response(
93
  raw_response = ""
94
 
95
  try:
96
- for chunk in client.chat_completion(
97
- messages,
98
- model=model,
99
- max_tokens=max_tokens,
100
- stream=True,
101
- temperature=temperature,
102
- top_p=top_p,
103
- ):
104
- choices = chunk.choices
105
- if choices and choices[0].delta.content:
106
- raw_response += choices[0].delta.content
107
- yield raw_response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
  except Exception as e:
110
  logger.error(f"[Chat] LLM error: {e}")
@@ -113,14 +141,19 @@ def generate_response(
113
  return
114
 
115
  # ── Post-stream ────────────────────────────────────────────────────────────
116
- # Save raw text to memory (preserves original markers for future context)
117
  try:
118
- append_message(character_id, "user", message)
119
  append_message(character_id, "assistant", raw_response)
120
  except Exception as e:
121
  logger.error(f"[Chat] Memory save failed: {e}")
122
 
123
- # ── Final yield: formatted markdown ───────────────────────────────────���───
124
- segments = parse_response(raw_response)
 
 
 
 
 
125
  formatted = segments_to_markdown(segments)
126
  yield formatted
 
22
  )
23
  from engine.parser import parse_response, segments_to_markdown
24
  from engine.prompt_builder import build_messages, load_character
25
+ from engine.translator import translate_from_english, translate_to_english
26
 
27
  logger = logging.getLogger(__name__)
28
 
29
 
30
  def generate_response(
31
+ message: str,
32
+ character_id: str,
33
+ client: InferenceClient,
34
+ persona: dict | None = None,
35
+ model: str = "Qwen/Qwen3-235B-A22B-Instruct-2507",
36
+ max_tokens: int = 512,
37
+ temperature: float = 0.7,
38
+ top_p: float = 0.95,
39
+ language: str = "English",
40
+ translation_model: str | None = None,
41
  ):
42
  """
43
  Core streaming generator.
 
80
  summary = load_summary(character_id)
81
  long_term_memory = load_long_term_memory(character_id)
82
 
83
+ # ── Translation mode? ──────────────────────────────────────────────────────
84
+ use_translation = language.strip().lower() != "english" and bool(translation_model)
85
+
86
+ if use_translation:
87
+ # Translate user message → English before sending to main model
88
+ en_message = translate_to_english(message, language, client, translation_model)
89
+ prompt_language = "English" # main model always responds in English
90
+ else:
91
+ en_message = message
92
+ prompt_language = language
93
+
94
+ # ── Build prompt ───────────────────────────────────────────────────────────
95
  messages = build_messages(
96
  character=character,
97
  persona=persona,
98
  summary=summary,
99
  long_term_memory=long_term_memory,
100
  recent_messages=recent_messages,
101
+ current_message=en_message,
102
+ language=prompt_language,
103
  )
104
 
105
  # ── LLM call ──────────────────────────────────────────────────────────────
 
107
  raw_response = ""
108
 
109
  try:
110
+ if use_translation:
111
+ # Collect silently — yield placeholder while model generates
112
+ yield "*(…)*"
113
+ for chunk in client.chat_completion(
114
+ messages,
115
+ model=model,
116
+ max_tokens=max_tokens,
117
+ stream=True,
118
+ temperature=temperature,
119
+ top_p=top_p,
120
+ ):
121
+ if chunk.choices and chunk.choices[0].delta.content:
122
+ raw_response += chunk.choices[0].delta.content
123
+ else:
124
+ # Stream tokens in real time
125
+ for chunk in client.chat_completion(
126
+ messages,
127
+ model=model,
128
+ max_tokens=max_tokens,
129
+ stream=True,
130
+ temperature=temperature,
131
+ top_p=top_p,
132
+ ):
133
+ if chunk.choices and chunk.choices[0].delta.content:
134
+ raw_response += chunk.choices[0].delta.content
135
+ yield raw_response
136
 
137
  except Exception as e:
138
  logger.error(f"[Chat] LLM error: {e}")
 
141
  return
142
 
143
  # ── Post-stream ────────────────────────────────────────────────────────────
144
+ # Memory always stores English (so main model gets consistent context)
145
  try:
146
+ append_message(character_id, "user", en_message)
147
  append_message(character_id, "assistant", raw_response)
148
  except Exception as e:
149
  logger.error(f"[Chat] Memory save failed: {e}")
150
 
151
+ # ── Translate response → user's language, format, yield ───────────────────
152
+ if use_translation:
153
+ final_text = translate_from_english(raw_response, language, client, translation_model)
154
+ else:
155
+ final_text = raw_response
156
+
157
+ segments = parse_response(final_text)
158
  formatted = segments_to_markdown(segments)
159
  yield formatted
engine/translator.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Translation layer — translates between the user's language and English.
3
+
4
+ The main LLM always receives and produces English.
5
+ This module handles conversion in both directions so the UI shows the user's language.
6
+
7
+ Formatting rules:
8
+ Text inside (parentheses) and *asterisks* is roleplay markup — it must be
9
+ preserved as-is so the parser can still identify actions and thoughts.
10
+ """
11
+
12
+ import logging
13
+ from huggingface_hub import InferenceClient
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ _TO_EN = (
18
+ "You are a professional translator. "
19
+ "Translate the following text to English. "
20
+ "Preserve any text inside parentheses ( ) and asterisks * exactly as written — do not translate them. "
21
+ "Return ONLY the translation, no explanations or extra text."
22
+ )
23
+
24
+ _FROM_EN = (
25
+ "You are a professional translator. "
26
+ "Translate the following text to {language}. "
27
+ "Preserve any text inside parentheses ( ) and asterisks * exactly as written — do not translate them. "
28
+ "Return ONLY the translation, no explanations or extra text."
29
+ )
30
+
31
+
32
+ def translate_to_english(
33
+ text: str,
34
+ source_language: str,
35
+ client: InferenceClient,
36
+ model: str,
37
+ ) -> str:
38
+ """
39
+ Translate `text` from `source_language` to English.
40
+ Returns the original text unchanged if already English or on any failure.
41
+ """
42
+ if not text.strip() or source_language.strip().lower() == "english":
43
+ return text
44
+
45
+ try:
46
+ response = client.chat_completion(
47
+ messages=[
48
+ {"role": "system", "content": _TO_EN},
49
+ {"role": "user", "content": text},
50
+ ],
51
+ model=model,
52
+ max_tokens=1024,
53
+ stream=False,
54
+ temperature=0.1,
55
+ )
56
+ result = response.choices[0].message.content.strip()
57
+ logger.info(f"[Translator] {source_language}→EN: {text[:60]!r} → {result[:60]!r}")
58
+ return result
59
+ except Exception as e:
60
+ logger.warning(f"[Translator] to_english failed ({e}), keeping original.")
61
+ return text
62
+
63
+
64
+ def translate_from_english(
65
+ text: str,
66
+ target_language: str,
67
+ client: InferenceClient,
68
+ model: str,
69
+ ) -> str:
70
+ """
71
+ Translate `text` from English to `target_language`.
72
+ Returns the original text unchanged if target is English or on any failure.
73
+ """
74
+ if not text.strip() or target_language.strip().lower() == "english":
75
+ return text
76
+
77
+ try:
78
+ system = _FROM_EN.format(language=target_language)
79
+ response = client.chat_completion(
80
+ messages=[
81
+ {"role": "system", "content": system},
82
+ {"role": "user", "content": text},
83
+ ],
84
+ model=model,
85
+ max_tokens=1024,
86
+ stream=False,
87
+ temperature=0.1,
88
+ )
89
+ result = response.choices[0].message.content.strip()
90
+ logger.info(f"[Translator] EN→{target_language}: {text[:60]!r} → {result[:60]!r}")
91
+ return result
92
+ except Exception as e:
93
+ logger.warning(f"[Translator] from_english failed ({e}), keeping original.")
94
+ return text