Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ manifest.ini
*.nvda-addon
.sconsign.dblite
/[0-9]*.[0-9]*.[0-9]*.json
venv
.env
.python-version
5 changes: 3 additions & 2 deletions addon/globalPlugins/WordBridge/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from .dictionary.dialog import DictionaryEntryDialog
from .lib.coseeing import obtain_openai_key
from .lib.decimalUtils import decimal_to_str_0
from .lib.typo_corrector import ChineseTypoCorrector, ChineseTypoCorrectorLite
from .lib.typo_corrector import ChineseTypoCorrector, ChineseTypoCorrectorLite, CorrectionOrchestrator
from .lib.utils import strings_diff
from .lib.viewHTML import text2template
from hanzidentifier import has_chinese
Expand Down Expand Up @@ -231,7 +231,8 @@ def correctTypo(self, request):

try:
batch_mode = not DEBUG_MODE
response, _diff_ = corrector.correct_text(request, batch_mode=batch_mode)
orchestrator = CorrectionOrchestrator(corrector)
response, _diff_ = orchestrator.execute(request, batch_mode=batch_mode)
except Exception as e:
ui.message(_("Sorry, an error occurred during the program execution, the details are: {e}").format(e=e))
log.warning(_("Sorry, an error occurred during the program execution, the details are: {e}").format(e=e))
Expand Down
2 changes: 1 addition & 1 deletion addon/globalPlugins/WordBridge/configManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"Google": _("Google"),
"OpenAI": _("OpenAI"),
"OpenRouter": _("OpenRouter"),
"claude-3-5-haiku-20241022": _("claude-3-5-haiku"),
"claude-haiku-4-5-20251001": _("claude-4-5-haiku"),
"claude-3-7-sonnet-20250219": _("claude-3.7-sonnet"),
"claude-sonnet-4-20250514": _("claude-4-sonnet"),
"deepseek-v3": _("deepseek-v3"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"model": {
"model_name": "claude-haiku-4-5-20251001",
"provider": "Anthropic",
"llm_access_method": "personal_api_key",
"require_secret_key": false,
"template_name": {
"standard": "Standard_v1.json",
"lite": "Lite_v1.json"
},
"optional_guidance_enable": {
"keep_non_chinese_char": true,
"no_explanation": false
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"model": {
"model_name": "gemini-2.5-pro-preview-06-05",
"model_name": "gemini-2.5-flash",
"provider": "Google",
"llm_access_method": "personal_api_key",
"require_secret_key": false,
Expand All @@ -13,4 +13,4 @@
"no_explanation": false
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"model": {
"model_name": "gemini-2.5-pro",
"provider": "Google",
"llm_access_method": "personal_api_key",
"require_secret_key": false,
"template_name": {
"standard": "Standard_v1.json",
"lite": "Lite_v1.json"
},
"optional_guidance_enable": {
"keep_non_chinese_char": true,
"no_explanation": false
}
}
}
44 changes: 44 additions & 0 deletions addon/globalPlugins/WordBridge/lib/cost_calculator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""
Cost Calculator for tracking LLM usage and costs.

This module provides utilities for:
- Tracking token usage from API responses
- Calculating costs based on model pricing
"""

from decimal import Decimal
from collections import defaultdict

class CostCalculator:
def __init__(self, model_entry: dict):
self._model_entry = model_entry
self._pricing = model_entry.get("pricing", {})
self._usage_key = model_entry.get("usage_key")

def get_total_usage(self, response_history: list) -> dict:
total_usage = defaultdict(int)
if not self._usage_key:
return total_usage

for response in response_history:
if isinstance(response, dict) and self._usage_key in response:
for usage_type in self._pricing:
if usage_type == "base_unit":
continue
try:
total_usage[usage_type] += response[self._usage_key][usage_type]
except KeyError:
pass

return dict(total_usage)

def get_total_cost(self, response_history: list) -> Decimal:
cost = Decimal("0")
usages = self.get_total_usage(response_history)
for key, value in usages.items():
cost += (
Decimal(str(self._pricing[key]))
* Decimal(str(value))
/ Decimal(str(self._pricing["base_unit"]))
)
return cost
87 changes: 87 additions & 0 deletions addon/globalPlugins/WordBridge/lib/provider.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from copy import deepcopy
import json
import logging
import random
import time
from pathlib import Path

import requests
from requests.utils import urlparse

try:
Expand All @@ -11,6 +15,7 @@
def _(s):
return s

log = logging.getLogger(__name__)

class Provider:
def __init__(self, credential: dict, model: str, llm_settings: dict = {}):
Expand Down Expand Up @@ -73,6 +78,69 @@ def handle_errors(self, response):
message=message
))

def try_connection(self, timeout=10, try_count=1):
url = self.base_url
for r in range(try_count):
try:
response = requests.get(url, timeout=timeout)
return
except Exception as e:
request_error = type(e).__name__
log.error(
"Try = {try_index}, {request_error}, an error occurred when sending request: {e}".format(
try_index=(r + 1),
request_error=request_error,
e=e,
)
)

raise Exception(
_("HTTP request error ({request_error}). Please check the network setting.").format(
request_error=request_error
)
)

def chat_completion(self, messages, system_template, retries=2, backoff=1):
request_data = self.get_request_data(messages, system_template)
api_url = self.url
headers = self.get_headers()

current_backoff = backoff
response = None
request_error = None

for r in range(retries):
timeout = min(self.timeout0 * (r + 1), self.timeout_max) if self.name != "ollama" else 300
try:
response = requests.post(
api_url,
headers=headers,
json=request_data,
timeout=timeout,
)
break
except Exception as e:
request_error = type(e).__name__
log.error(
"Try = {try_index}, {request_error}, an error occurred when sending {provider} request: {e}".format(
try_index=(r + 1),
request_error=request_error,
provider=self.name,
e=e
)
)
current_backoff = min(current_backoff * (1 + random.random()), 3)
time.sleep(current_backoff)

if response is None:
raise Exception(
_("HTTP request error ({request_error}). Please check the network setting.").format(
request_error=request_error
)
)

self.handle_errors(response)
return response.json()

class OpenaiProvider(Provider):
name = "openai"
Expand Down Expand Up @@ -240,3 +308,22 @@ def get_request_data(self, messages, system_template):
}
return data


def get_provider(provider_name: str, credential: dict, model: str, llm_settings: dict = None) -> Provider:
"""
Factory function to create a provider instance based on the provider name.
"""
provider_mapping = {
"openai": OpenaiProvider,
"anthropic": AnthropicProvider,
"baidu": BaiduProvider,
"deepseek": DeepseekProvider,
"google": GoogleProvider,
"openrouter": OpenrouterProvider,
}

provider_class = provider_mapping.get(provider_name.lower())
if not provider_class:
raise ValueError(f"Unsupported provider: {provider_name}")

return provider_class(credential, model, llm_settings or {})
Loading
Loading