121 lines
4.3 KiB
Python
121 lines
4.3 KiB
Python
# app.py
|
||
#
|
||
# author: deng
|
||
# date : 20250604
|
||
|
||
import threading
|
||
|
||
import streamlit as st
|
||
from utils import parse_config
|
||
|
||
|
||
class TranslatorApp:
|
||
"""Streamlit App for Language Translation"""
|
||
|
||
def __init__(self, config_path: str = 'assets/config.toml'):
|
||
self._config = parse_config(config_path)
|
||
self._chain = None
|
||
|
||
# Start pre-warming imports and LLM client in a background thread
|
||
self._lock = threading.Lock()
|
||
threading.Thread(target=self.prepare_chain, daemon=True).start()
|
||
|
||
def prepare_chain(self) -> None:
|
||
"""Prepare the chain for translation.
|
||
Thread-safe: safe to call from both background thread and main thread.
|
||
"""
|
||
if self._chain is not None:
|
||
return
|
||
|
||
with self._lock:
|
||
if self._chain is not None:
|
||
return
|
||
|
||
from langchain.prompts import ChatPromptTemplate
|
||
|
||
system_template = (
|
||
'你是專業的翻譯人員,請判斷接下來句子的語言是否為{source_lang},若是的話則請將該句翻譯成{target_lang},'
|
||
'並且符合{description}(僅回傳翻譯結果即可),若非的話則請回傳一模一樣的句子。'
|
||
)
|
||
user_template = '{input_text}'
|
||
|
||
if self._config['app']['llm_mode'] == 'ollama':
|
||
from langchain_ollama import ChatOllama
|
||
llm = ChatOllama(
|
||
base_url=self._config['app']['ollama']['url'],
|
||
model=self._config['app']['ollama']['model_name'],
|
||
temperature=self._config['app']['ollama']['temperature'],
|
||
max_tokens=self._config['app']['ollama']['max_tokens'],
|
||
top_p=self._config['app']['ollama']['top_p'],
|
||
keep_alive=self._config['app']['ollama']['keep_alive'],
|
||
stop=None
|
||
)
|
||
elif self._config['app']['llm_mode'] == 'openai':
|
||
from langchain_openai import ChatOpenAI
|
||
llm = ChatOpenAI(
|
||
model=self._config['app']['openai']['model_name'],
|
||
temperature=self._config['app']['openai']['temperature'],
|
||
max_tokens=self._config['app']['openai']['max_tokens'],
|
||
top_p=self._config['app']['openai']['top_p']
|
||
)
|
||
else:
|
||
raise ValueError(f"Unsupported llm model: {self._config['app']['llm_mode']}")
|
||
|
||
prompt = ChatPromptTemplate.from_messages([
|
||
('system', system_template),
|
||
('human', user_template)
|
||
])
|
||
self._chain = prompt | llm
|
||
|
||
def run(self) -> None:
|
||
""" Run the Streamlit app """
|
||
|
||
# Interface
|
||
st.set_page_config(
|
||
page_title=self._config['app']['page_title'],
|
||
page_icon=self._config['app']['page_favicon_path'],
|
||
)
|
||
st.title(body=self._config['app']['page_title'])
|
||
|
||
direction = st.radio(
|
||
label='語言',
|
||
options=list(self._config['app']['lang_directions'].keys()),
|
||
index=0,
|
||
key='lang_choice',
|
||
horizontal=True
|
||
)
|
||
input_text = st.text_area(
|
||
label='輸入',
|
||
placeholder='請輸入文字',
|
||
key='input_text'
|
||
)
|
||
translate_button = st.button('翻譯')
|
||
output_container = st.empty()
|
||
|
||
# Action
|
||
if input_text or translate_button:
|
||
if not input_text.strip():
|
||
st.warning('請輸入要翻譯的文字')
|
||
else:
|
||
self.prepare_chain()
|
||
with st.spinner('翻譯中...'):
|
||
result = self._chain.stream({
|
||
'input_text': input_text,
|
||
'source_lang': self._config['app']['lang_directions'][direction]['source_lang'],
|
||
'target_lang': self._config['app']['lang_directions'][direction]['target_lang'],
|
||
'description': self._config['app']['lang_directions'][direction]['description']
|
||
})
|
||
output_container.write_stream(
|
||
stream=result
|
||
)
|
||
|
||
|
||
@st.cache_resource(show_spinner=False, max_entries=1)
|
||
def get_app_instance() -> TranslatorApp:
|
||
return TranslatorApp()
|
||
|
||
|
||
if __name__ == '__main__':
|
||
app = get_app_instance()
|
||
app.run()
|