first usable type
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
|
__pycache__
|
||||||
.ruff_cache
|
.ruff_cache
|
||||||
.venv
|
.venv
|
12
README.md
12
README.md
@ -2,8 +2,14 @@
|
|||||||
本來使用[LibreTranslate](https://github.com/LibreTranslate/LibreTranslate)作為Google翻譯的替代方案,但使用體驗真的是不佳(至少以正體中文與英文互翻的狀況下,有時候翻譯結果超ㄎ一ㄤ的)~所以就自己DIY寫一個吧🥵
|
本來使用[LibreTranslate](https://github.com/LibreTranslate/LibreTranslate)作為Google翻譯的替代方案,但使用體驗真的是不佳(至少以正體中文與英文互翻的狀況下,有時候翻譯結果超ㄎ一ㄤ的)~所以就自己DIY寫一個吧🥵
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
* MacOS 14
|
* Hardware
|
||||||
|
* MacbookPro14 2021
|
||||||
|
* Software
|
||||||
|
* MacOS 14
|
||||||
|
* [Ollama](https://ollama.com/)
|
||||||
|
|
||||||
## Dirs
|
## Dirs
|
||||||
|
* **tests**
|
||||||
## Files
|
* unittest codes by Pytest
|
||||||
|
* **translator**
|
||||||
|
* app source codes
|
3060
poetry.lock
generated
3060
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -6,8 +6,12 @@ authors = [
|
|||||||
{name = "deng",email = "deng@guineapig.love"}
|
{name = "deng",email = "deng@guineapig.love"}
|
||||||
]
|
]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.13"
|
requires-python = "~=3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"streamlit>=1.45.0,<2.0.0",
|
||||||
|
"langchain>=0.3.0,<0.4.0",
|
||||||
|
"langchain-openai>=0.3.0,<0.4.0",
|
||||||
|
"langchain-community>=0.3.0,<0.4.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
3
tests/test_config.yaml
Normal file
3
tests/test_config.yaml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
test:
|
||||||
|
key1: value1
|
||||||
|
key2: value2
|
17
tests/test_utils.py
Normal file
17
tests/test_utils.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# test_utils.py
|
||||||
|
#
|
||||||
|
# author: deng
|
||||||
|
# date : 20250604
|
||||||
|
|
||||||
|
from translator.utils import parse_config
|
||||||
|
|
||||||
|
|
||||||
|
class TestUtils:
|
||||||
|
def test_parse_config(self) -> None:
|
||||||
|
config = parse_config('tests/test_config.yaml')
|
||||||
|
assert isinstance(config, dict)
|
||||||
|
assert 'test' in config
|
||||||
|
assert 'key1' in config['test']
|
||||||
|
assert 'key2' in config['test']
|
||||||
|
assert config['test']['key1'] == 'value1'
|
||||||
|
assert config['test']['key2'] == 'value2'
|
94
translator/app.py
Normal file
94
translator/app.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# app.py
|
||||||
|
#
|
||||||
|
# author: deng
|
||||||
|
# date : 20250604
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
from langchain.chains import LLMChain
|
||||||
|
from langchain.prompts import PromptTemplate
|
||||||
|
from langchain_community.chat_models import ChatOllama
|
||||||
|
from utils import parse_config
|
||||||
|
|
||||||
|
|
||||||
|
class TranslatorApp:
|
||||||
|
"""Streamlit App for Language Translation"""
|
||||||
|
|
||||||
|
def __init__(self, config_path: str = 'config.yaml'):
|
||||||
|
self._config = parse_config(config_path)
|
||||||
|
self._lang_directions = {
|
||||||
|
'英->中': ('英文', '正體中文', '台灣地區用語'),
|
||||||
|
'中->英': ('正體中文', '英文', '自然通順'),
|
||||||
|
'中->日': ('正體中文', '日文', '自然通順'),
|
||||||
|
'日->中': ('日文', '正體中文', '自然通順')
|
||||||
|
}
|
||||||
|
self._chain = self._prepare_chain()
|
||||||
|
|
||||||
|
def _prepare_chain(self) -> LLMChain:
|
||||||
|
"""Prepare the chain for translation"""
|
||||||
|
template = (
|
||||||
|
'你是專業的翻譯人員,請判斷這段句子「{input_text}」的語言是否為{source_lang},若非的'
|
||||||
|
'話則請回傳一模一樣的句子,若是的話則請將該句翻譯成{target_lang},並且符合{rule}(僅回'
|
||||||
|
'傳翻譯結果即可)。'
|
||||||
|
)
|
||||||
|
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'],
|
||||||
|
stop=None
|
||||||
|
)
|
||||||
|
prompt = PromptTemplate(
|
||||||
|
input_variables=['input_text', 'source_lang', 'target_lang', 'rule'],
|
||||||
|
template=template
|
||||||
|
)
|
||||||
|
return LLMChain(llm=llm, prompt=prompt)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
""" Run the Streamlit app """
|
||||||
|
|
||||||
|
st.title(body='跨過語言的黑水溝')
|
||||||
|
direction = st.radio(
|
||||||
|
label='Direction',
|
||||||
|
options=list(self._lang_directions.keys()),
|
||||||
|
index=0,
|
||||||
|
key='lang_choice',
|
||||||
|
horizontal=True
|
||||||
|
)
|
||||||
|
input_text = st.text_area(
|
||||||
|
label='Input',
|
||||||
|
placeholder='請輸入文字',
|
||||||
|
key='input_text'
|
||||||
|
)
|
||||||
|
output_container = st.empty()
|
||||||
|
_ = output_container.text_area(
|
||||||
|
label='Output',
|
||||||
|
placeholder='',
|
||||||
|
disabled=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if st.button('Go'):
|
||||||
|
if not input_text.strip():
|
||||||
|
st.warning("請輸入要翻譯的文字")
|
||||||
|
return
|
||||||
|
|
||||||
|
with st.spinner('翻譯中...'):
|
||||||
|
|
||||||
|
source_lang, target_lang, rule = self._lang_directions[direction]
|
||||||
|
result = self._chain.run({
|
||||||
|
'input_text': input_text,
|
||||||
|
'source_lang': source_lang,
|
||||||
|
'target_lang': target_lang,
|
||||||
|
'rule': rule
|
||||||
|
}).strip()
|
||||||
|
|
||||||
|
output_container.text_area(
|
||||||
|
label='Output',
|
||||||
|
value=result,
|
||||||
|
disabled=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app = TranslatorApp()
|
||||||
|
app.run()
|
6
translator/config.yaml
Normal file
6
translator/config.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
app:
|
||||||
|
ollama_url: http://localhost:11434
|
||||||
|
ollama_model_name: gemma3:12b-it-qat
|
||||||
|
ollama_max_tokens: 256
|
||||||
|
ollama_temperature: 0.2
|
||||||
|
ollama_top_p: 0.9
|
20
translator/utils.py
Normal file
20
translator/utils.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# utils.py
|
||||||
|
#
|
||||||
|
# author: deng
|
||||||
|
# date : 20250604
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
|
def parse_config(config_path: str) -> dict:
|
||||||
|
"""Config parser
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config_path (str): path of config yaml.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: configuration dictionary
|
||||||
|
"""
|
||||||
|
with open(config_path, 'r', encoding='utf-8') as file:
|
||||||
|
config = yaml.safe_load(file)
|
||||||
|
return config
|
Reference in New Issue
Block a user