Add system prompt, glossary, presets for Ollama/WebLLM, image translation support
This commit is contained in:
@@ -70,30 +70,65 @@ class LibreTranslationProvider(TranslationProvider):
|
||||
class OllamaTranslationProvider(TranslationProvider):
|
||||
"""Ollama LLM translation implementation"""
|
||||
|
||||
def __init__(self, base_url: str = "http://localhost:11434", model: str = "llama3", vision_model: str = "llava"):
|
||||
def __init__(self, base_url: str = "http://localhost:11434", model: str = "llama3", vision_model: str = "llava", system_prompt: str = ""):
|
||||
self.base_url = base_url.rstrip('/')
|
||||
self.model = model
|
||||
self.vision_model = vision_model
|
||||
self.model = model.strip() # Remove any leading/trailing whitespace
|
||||
self.vision_model = vision_model.strip()
|
||||
self.custom_system_prompt = system_prompt # Custom context, glossary, instructions
|
||||
|
||||
def translate(self, text: str, target_language: str, source_language: str = 'auto') -> str:
|
||||
if not text or not text.strip():
|
||||
return text
|
||||
|
||||
# Skip very short text or numbers only
|
||||
if len(text.strip()) < 2 or text.strip().isdigit():
|
||||
return text
|
||||
|
||||
try:
|
||||
prompt = f"Translate the following text to {target_language}. Return ONLY the translation, nothing else:\n\n{text}"
|
||||
# Build system prompt with custom context if provided
|
||||
base_prompt = f"You are a translator. Translate the user's text to {target_language}. Return ONLY the translation, nothing else."
|
||||
|
||||
if self.custom_system_prompt:
|
||||
system_content = f"""{base_prompt}
|
||||
|
||||
ADDITIONAL CONTEXT AND INSTRUCTIONS:
|
||||
{self.custom_system_prompt}"""
|
||||
else:
|
||||
system_content = base_prompt
|
||||
|
||||
# Use /api/chat endpoint (more compatible with all models)
|
||||
response = requests.post(
|
||||
f"{self.base_url}/api/generate",
|
||||
f"{self.base_url}/api/chat",
|
||||
json={
|
||||
"model": self.model,
|
||||
"prompt": prompt,
|
||||
"stream": False
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": system_content
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": text
|
||||
}
|
||||
],
|
||||
"stream": False,
|
||||
"options": {
|
||||
"temperature": 0.3,
|
||||
"num_predict": 500
|
||||
}
|
||||
},
|
||||
timeout=30
|
||||
timeout=120 # 2 minutes timeout
|
||||
)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
return result.get("response", text).strip()
|
||||
translated = result.get("message", {}).get("content", "").strip()
|
||||
return translated if translated else text
|
||||
except requests.exceptions.ConnectionError:
|
||||
print(f"Ollama error: Cannot connect to {self.base_url}. Is Ollama running?")
|
||||
return text
|
||||
except requests.exceptions.Timeout:
|
||||
print(f"Ollama error: Request timeout after 120s")
|
||||
return text
|
||||
except Exception as e:
|
||||
print(f"Ollama translation error: {e}")
|
||||
return text
|
||||
@@ -107,21 +142,25 @@ class OllamaTranslationProvider(TranslationProvider):
|
||||
with open(image_path, 'rb') as img_file:
|
||||
image_data = base64.b64encode(img_file.read()).decode('utf-8')
|
||||
|
||||
prompt = f"Extract all text from this image and translate it to {target_language}. Return ONLY the translated text, preserving the structure and formatting."
|
||||
|
||||
# Use /api/chat for vision models too
|
||||
response = requests.post(
|
||||
f"{self.base_url}/api/generate",
|
||||
f"{self.base_url}/api/chat",
|
||||
json={
|
||||
"model": self.vision_model,
|
||||
"prompt": prompt,
|
||||
"images": [image_data],
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": f"Extract all text from this image and translate it to {target_language}. Return ONLY the translated text, preserving the structure and formatting.",
|
||||
"images": [image_data]
|
||||
}
|
||||
],
|
||||
"stream": False
|
||||
},
|
||||
timeout=60
|
||||
)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
return result.get("response", "").strip()
|
||||
return result.get("message", {}).get("content", "").strip()
|
||||
except Exception as e:
|
||||
print(f"Ollama vision translation error: {e}")
|
||||
return ""
|
||||
@@ -158,6 +197,7 @@ class TranslationService:
|
||||
else:
|
||||
# Auto-select provider based on configuration
|
||||
self.provider = self._get_default_provider()
|
||||
self.translate_images = False # Flag to enable image translation
|
||||
|
||||
def _get_default_provider(self) -> TranslationProvider:
|
||||
"""Get the default translation provider from configuration"""
|
||||
@@ -182,6 +222,26 @@ class TranslationService:
|
||||
|
||||
return self.provider.translate(text, target_language, source_language)
|
||||
|
||||
def translate_image(self, image_path: str, target_language: str) -> str:
|
||||
"""
|
||||
Translate text in an image using vision model (Ollama only)
|
||||
|
||||
Args:
|
||||
image_path: Path to image file
|
||||
target_language: Target language code
|
||||
|
||||
Returns:
|
||||
Translated text from image
|
||||
"""
|
||||
if not self.translate_images:
|
||||
return ""
|
||||
|
||||
# Only Ollama supports image translation
|
||||
if isinstance(self.provider, OllamaTranslationProvider):
|
||||
return self.provider.translate_image(image_path, target_language)
|
||||
|
||||
return ""
|
||||
|
||||
def translate_batch(self, texts: list[str], target_language: str, source_language: str = 'auto') -> list[str]:
|
||||
"""
|
||||
Translate multiple text strings
|
||||
|
||||
Reference in New Issue
Block a user