feat(frontend): translate UI strings to Korean

This commit is contained in:
pys306
2026-04-28 00:02:25 +09:00
parent 475c203ea1
commit 365f4d31d2

View File

@@ -23,7 +23,7 @@ st.set_page_config(page_title="Cowork", layout="wide")
def init(): def init():
agent = GeneraticAgent() agent = GeneraticAgent()
if agent.llmclient is None: if agent.llmclient is None:
st.error("⚠️ 未配置任何可用的 LLM 接口请设置mykey.py。") st.error("⚠️ 사용 가능한 LLM 인터페이스가 구성되지 않았습니다. mykey.py를 설정하세요.")
st.stop() st.stop()
else: threading.Thread(target=agent.run, daemon=True).start() else: threading.Thread(target=agent.run, daemon=True).start()
return agent return agent
@@ -37,23 +37,23 @@ if 'autonomous_enabled' not in st.session_state: st.session_state.autonomous_ena
@st.fragment @st.fragment
def render_sidebar(): def render_sidebar():
current_idx = agent.llm_no current_idx = agent.llm_no
st.caption(f"LLM Core: {current_idx}: {agent.get_llm_name()}", help="点击切换备用链路") st.caption(f"LLM Core: {current_idx}: {agent.get_llm_name()}", help="백업 링크 전환하려면 클릭하세요")
last_reply_time = st.session_state.get('last_reply_time', 0) last_reply_time = st.session_state.get('last_reply_time', 0)
if last_reply_time > 0: if last_reply_time > 0:
st.caption(f"空闲时间{int(time.time()) - last_reply_time}", help="当超过30分钟未收到回复时系统会自动任务") st.caption(f"대기 시간{int(time.time()) - last_reply_time}", help="30분 이상 응답이 없으면 시스템이 자동으로 작업을 시작합니다")
if st.button("切换备用链路"): if st.button("백업 링크 전환"):
agent.next_llm(); st.rerun(scope="fragment") agent.next_llm(); st.rerun(scope="fragment")
if st.button("强行停止任务"): if st.button("작업 강제 중지"):
agent.abort(); st.toast("已发送停止信号"); st.rerun() agent.abort(); st.toast("중지 신호가 전송되었습니다"); st.rerun()
if st.button("重新注入工具"): if st.button("도구 재주입"):
agent.llmclient.last_tools = '' agent.llmclient.last_tools = ''
try: try:
hist_path = os.path.join(script_dir, '..', 'assets', 'tool_usable_history.json') hist_path = os.path.join(script_dir, '..', 'assets', 'tool_usable_history.json')
with open(hist_path, 'r', encoding='utf-8') as f: tool_hist = json.load(f) with open(hist_path, 'r', encoding='utf-8') as f: tool_hist = json.load(f)
agent.llmclient.backend.history.extend(tool_hist) agent.llmclient.backend.history.extend(tool_hist)
st.toast(f"已重新注入工具,追加了 {len(tool_hist)} 条示范记录") st.toast(f"도구가 재주입되었습니다. {len(tool_hist)}개의 예시 기록이 추가되었습니다")
except Exception as e: st.toast(f"注入工具示范失败: {e}") except Exception as e: st.toast(f"도구 예시 주입 실패: {e}")
if st.button("🐱 桌面宠物"): if st.button("🐱 데스크톱 펫"):
kwargs = {'creationflags': 0x08} if sys.platform == 'win32' else {} kwargs = {'creationflags': 0x08} if sys.platform == 'win32' else {}
pet_script = os.path.join(script_dir, 'desktop_pet_v2.pyw') pet_script = os.path.join(script_dir, 'desktop_pet_v2.pyw')
if not os.path.exists(pet_script): pet_script = os.path.join(script_dir, 'desktop_pet.pyw') if not os.path.exists(pet_script): pet_script = os.path.join(script_dir, 'desktop_pet.pyw')
@@ -68,31 +68,31 @@ def render_sidebar():
def _pet_hook(ctx): def _pet_hook(ctx):
parts = [f"Turn {ctx.get('turn','?')}"] parts = [f"Turn {ctx.get('turn','?')}"]
if ctx.get('summary'): parts.append(ctx['summary']) if ctx.get('summary'): parts.append(ctx['summary'])
if ctx.get('exit_reason'): parts.append('任务已完成') if ctx.get('exit_reason'): parts.append('작업 완료')
_pet_req(f'msg={quote(chr(10).join(parts))}') _pet_req(f'msg={quote(chr(10).join(parts))}')
if ctx.get('exit_reason'): _pet_req('state=idle') if ctx.get('exit_reason'): _pet_req('state=idle')
agent._turn_end_hooks['pet'] = _pet_hook agent._turn_end_hooks['pet'] = _pet_hook
st.toast("桌面宠物已启动") st.toast("데스크톱 펫이 시작되었습니다")
st.divider() st.divider()
if st.button("开始空闲自主行动"): if st.button("대기 중 자율 행동 시작"):
st.session_state.last_reply_time = int(time.time()) - 1800 st.session_state.last_reply_time = int(time.time()) - 1800
st.toast("已将上次回复时间设为1800秒前"); st.rerun() st.toast("마지막 응답 시간을 1800초 전으로 설정했습니다"); st.rerun()
if st.session_state.autonomous_enabled: if st.session_state.autonomous_enabled:
if st.button("⏸️ 禁止自主行动"): if st.button("⏸️ 자율 행동 금지"):
st.session_state.autonomous_enabled = False st.session_state.autonomous_enabled = False
st.toast("⏸️ 已禁止自主行动"); st.rerun() st.toast("⏸️ 자율 행동이 금지되었습니다"); st.rerun()
st.caption("🟢 自主行动运行中会在你离开它30分钟后自动进行") st.caption("🟢 자율 행동이 실행 중입니다. 30분 후 자동으로 진행됩니다")
else: else:
if st.button("▶️ 允许自主行动", type="primary"): if st.button("▶️ 자율 행동 허용", type="primary"):
st.session_state.autonomous_enabled = True st.session_state.autonomous_enabled = True
st.toast("已允许自主行动"); st.rerun() st.toast("자율 행동이 허용되었습니다"); st.rerun()
st.caption("🔴 自主行动已停止") st.caption("🔴 자율 행동이 중지되었습니다")
with st.sidebar: render_sidebar() with st.sidebar: render_sidebar()
def fold_turns(text): def fold_turns(text):
"""Return list of segments: [{'type':'text','content':...}, {'type':'fold','title':...,'content':...}]""" """Return list of segments: [{'type':'text','content':...}, {'type':'fold','title':...,'content':...}]"""
# 先把4+反引号块替换为占位符避免误切子agent嵌套的 LLM Running # 먼저 4개 이상의 백틱 블록을 플레이스홀더로 교체하여 하위 에이전트 중첩 LLM Running이 잘못 잘리는 것을 방지합니다.
_ph = [] _ph = []
safe = re.sub(r'`{4,}.*?`{4,}', lambda m: (_ph.append(m.group(0)), f'\x00PH{len(_ph)-1}\x00')[1], text, flags=re.DOTALL) safe = re.sub(r'`{4,}.*?`{4,}', lambda m: (_ph.append(m.group(0)), f'\x00PH{len(_ph)-1}\x00')[1], text, flags=re.DOTALL)
parts = re.split(r'(\**LLM Running \(Turn \d+\) \.\.\.\*\**)', safe) parts = re.split(r'(\**LLM Running \(Turn \d+\) \.\.\.\*\**)', safe)
@@ -118,9 +118,9 @@ def fold_turns(text):
else: segments.append({'type': 'text', 'content': marker + content}) else: segments.append({'type': 'text', 'content': marker + content})
return segments return segments
def render_segments(segments, suffix=''): def render_segments(segments, suffix=''):
# 整块重画:调用方用 slot.container() 包裹,保证 DOM 路径稳定、跨 rerun 对齐(消除"灰色重影")。 # 전체 다시 그리기: 호출자가 slot.container()로 감싸서 DOM 경로가 안정적이고 rerun 간 정렬이 보장되도록 함 ("회색 잔상" 제거).
# heartbeat 空转时 segments 不变 → Streamlit 后端 diff 无变化 → 前端零闪烁; # heartbeat 대기 시 segments 변경 없음 → Streamlit 백엔드 diff 변화 없음 → 프론트엔드 깜빡임 제로;
# container/markdown 本身是 API 调用,StopException 仍会被抛出abort 照常起作用)。 # container/markdown 자체는 API 호출이므로 StopException은 여전히 발생함 (abort는 정상 작동).
for seg in segments: for seg in segments:
if seg['type'] == 'fold': if seg['type'] == 'fold':
with st.expander(seg['title'], expanded=False): st.markdown(seg['content']) with st.expander(seg['title'], expanded=False): st.markdown(seg['content'])
@@ -145,7 +145,7 @@ def agent_backend_stream(prompt):
if "messages" not in st.session_state: st.session_state.messages = [] if "messages" not in st.session_state: st.session_state.messages = []
for msg in st.session_state.messages: for msg in st.session_state.messages:
with st.chat_message(msg["role"]): with st.chat_message(msg["role"]):
# slot=st.empty() + with slot.container(): ... 的外壳DOM 路径和流式渲染完全一致,跨 rerun 对齐 # slot=st.empty() + with slot.container(): ... 외부 껍질을 사용하여 DOM 경로와 스트리밍 렌더링이 완전히 일치하고 rerun 간 정렬됨
slot = st.empty() slot = st.empty()
with slot.container(): with slot.container():
if msg["role"] == "assistant": render_segments(fold_turns(msg["content"])) if msg["role"] == "assistant": render_segments(fold_turns(msg["content"]))
@@ -221,7 +221,7 @@ if prompt := st.chat_input("any task?"):
while frozen < n_done: while frozen < n_done:
with live.container(): render_segments([segs[frozen]]) with live.container(): render_segments([segs[frozen]])
live = st.empty(); frozen += 1 live = st.empty(); frozen += 1
with live.container(): render_segments([segs[-1]], suffix=CURSOR) # live 区域 with live.container(): render_segments([segs[-1]], suffix=CURSOR) # 라이브 영역
segs = fold_turns(response) segs = fold_turns(response)
for i in range(frozen, len(segs)): for i in range(frozen, len(segs)):
with live.container(): render_segments([segs[i]]) with live.container(): render_segments([segs[i]])