Mercurial Hosting > lang
changeset 74:64e35a92d163
add translation
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Thu, 28 Aug 2025 13:31:46 -0600 |
parents | 60ebb333b40c |
children | b96cf27e719d |
files | src/chat.css src/chat.html.luan src/chat.js src/lib/Chat.luan src/lib/ai/claude/Ai_chat.luan src/lib/ai/claude/Claude.luan src/lib/ai/claude/Translator.luan src/site.js src/translate.js.luan |
diffstat | 9 files changed, 145 insertions(+), 35 deletions(-) [+] |
line wrap: on
line diff
--- a/src/chat.css Thu Aug 28 05:16:32 2025 -0600 +++ b/src/chat.css Thu Aug 28 13:31:46 2025 -0600 @@ -39,9 +39,21 @@ width: 100%; } -div[role=assistant]:has(option[hide_text]:checked) div[message] { +div[role=assistant]:has(option[value=hide_text]:checked) div[message] { + filter: blur(5px); +} +div[role=assistant]:has(option[value=hide_ruby]:checked) div[message] rt { filter: blur(5px); } -div[role=assistant]:has(option[hide_ruby]:checked) div[message] rt { - filter: blur(5px); +div[role=assistant] div[trans] { + display: none; + border-top: 1px solid #cccccc; + white-space: pre-wrap; } +div[role=assistant]:has(option[value=show_trans]:checked) div[trans] { + display: block; +} + +div[controls] { + margin-top: 16px; +}
--- a/src/chat.html.luan Thu Aug 28 05:16:32 2025 -0600 +++ b/src/chat.html.luan Thu Aug 28 13:31:46 2025 -0600 @@ -92,11 +92,11 @@ </p> <p> <select name=show_text> - <option value=show_text>Show text</option> + <option value=hide_text>Hide text</option> <% if chat.has_ruby then %> <option value=hide_ruby>Hide pronunciation</option> <% end %> - <option value=hide_text>Hide text</option> + <option value=show_text>Show text</option> </select> </p> <p>
--- a/src/chat.js Thu Aug 28 05:16:32 2025 -0600 +++ b/src/chat.js Thu Aug 28 13:31:46 2025 -0600 @@ -159,3 +159,26 @@ recorder.stop(); } } + +function showSelected(select) { + if( select.value !== 'show_trans' ) + return; + let top = select.parentNode.parentNode; + let trans = top.querySelector('div[trans=needed]'); + if( !trans ) + return; + let msg = top.getAttribute('msg'); + let message = top.querySelector('div[message]'); + let text = textContent(message); + let url = `translate.js?msg=${msg}&text=${encodeURIComponent(text)}&language=${chat.language}`; + ajax(url); + showWaitingAiIcon(); +} + +function translated(msg,text) { + hideWaitingAiIcon(); + let top = document.querySelector(`div[msg="${msg}"]`); + let trans = top.querySelector('div[trans]'); + trans.setAttribute('trans',''); + trans.innerHTML = text; +}
--- a/src/lib/Chat.luan Thu Aug 28 05:16:32 2025 -0600 +++ b/src/lib/Chat.luan Thu Aug 28 13:31:46 2025 -0600 @@ -90,6 +90,7 @@ autoplay = chat.autoplay is_private = chat.is_private stt_prompt = chat.stt_prompt + language = chat.language } end @@ -112,21 +113,25 @@ local function option(name,text) local selected = name==chat.show_text and " selected" or "" %> - <option <%=name%><%=selected%>><%=text%></option> + <option value=<%=name%><%=selected%>><%=text%></option> <% end local function assistant_controls() return `%> + <div trans=needed>Translating...</div> <div controls> <audio controls preload=none></audio> - <select> + <select onchange="showSelected(this)"> <% - option("show_text","Show text") + option("hide_text","Hide text") if chat.has_ruby then option("hide_ruby","Hide pronunciation") end - option("hide_text","Hide text") + option("show_text","Show text") + if chat.language ~= "en" then + option("show_trans","Show translation") + end %> </select> </div>
--- a/src/lib/ai/claude/Ai_chat.luan Thu Aug 28 05:16:32 2025 -0600 +++ b/src/lib/ai/claude/Ai_chat.luan Thu Aug 28 13:31:46 2025 -0600 @@ -13,6 +13,8 @@ local Thread = require "luan:Thread.luan" local Claude = require "site:/lib/ai/claude/Claude.luan" local claude_chat = Claude.chat or error() +local Utils = require "site:/lib/Utils.luan" +local deep_copy = Utils.deep_copy or error() local Logging = require "luan:logging/Logging.luan" local logger = Logging.logger "claude/Ai_chat" @@ -51,8 +53,8 @@ local function output(text) text = html_encode(text) %> - <h3><%=who%></h3> - <div role="<%=role%>"> + <div role="<%=role%>" msg="<%=i%>"> + <h3><%=who%></h3> <div message markdown><%=text%></div> <% if role=="assistant" then %> <%= assistant_controls %> @@ -164,6 +166,24 @@ return #thread.messages > 0 end +local function chit_chat(thread) + thread = deep_copy(thread) + local messages = thread.messages or error() + for _, message in ipairs(messages) do + local content = message.content or error() + if type(content) == "string" then + content = {{ + type = "text" + text = content + }} + message.content = content + end + end + local content = messages[#messages].content or error() + content[#content].cache_control = { type = "ephemeral" } + return claude_chat(thread) +end + local function ask(thread) local messages = thread.messages or error --[=[ @@ -179,7 +199,7 @@ end --]=] -- logger.info(json_string(thread)) - local resultJson = claude_chat(thread) + local resultJson = chit_chat(thread) local result = json_parse(resultJson) -- logger.info(json_string(result)) result.type == "message" or error()
--- a/src/lib/ai/claude/Claude.luan Thu Aug 28 05:16:32 2025 -0600 +++ b/src/lib/ai/claude/Claude.luan Thu Aug 28 13:31:46 2025 -0600 @@ -7,8 +7,6 @@ local Parsers = require "luan:Parsers.luan" local json_string = Parsers.json_string or error() local Config = require "site:/private/Config.luan" -local Utils = require "site:/lib/Utils.luan" -local deep_copy = Utils.deep_copy or error() local Logging = require "luan:logging/Logging.luan" local logger = Logging.logger "claude/Claude" @@ -27,22 +25,8 @@ local max_tokens = 8192 function Claude.chat(thread) - thread = deep_copy(thread) thread.model = thread.model or model thread.max_tokens = thread.max_tokens or max_tokens - local messages = thread.messages or error() - for _, message in ipairs(messages) do - local content = message.content or error() - if type(content) == "string" then - content = {{ - type = "text" - text = content - }} - message.content = content - end - end - local content = messages[#messages].content or error() - content[#content].cache_control = { type = "ephemeral" } local options = { method = "POST" headers = headers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/ai/claude/Translator.luan Thu Aug 28 13:31:46 2025 -0600 @@ -0,0 +1,40 @@ +local Luan = require "luan:Luan.luan" +local error = Luan.error +local Parsers = require "luan:Parsers.luan" +local json_parse = Parsers.json_parse or error() +local json_string = Parsers.json_string or error() +local Claude = require "site:/lib/ai/claude/Claude.luan" +local claude_chat = Claude.chat or error() +local Logging = require "luan:logging/Logging.luan" +local logger = Logging.logger "claude/Translator" + + +local Translator = {} + +function Translator.translate(text,lang) + local thread = { + system = `%> +Translate <%=lang%> in the text you get to English. +Preserve formatting. +The text may also contain English. Just leave that unchanged. +<% ` + messages = {{ + role = "user" + content = text + }} + temperature = 0 + } + local resultJson = claude_chat(thread) + local result = json_parse(resultJson) + -- logger.info(json_string(result)) + result.type == "message" or error() + result.role == "assistant" or error() + result.stop_reason == "end_turn" or result.stop_reason == "tool_use" or error() + local content = result.content or error() + #content==1 or error() + content = content[1] + content.type == "text" or error() + return content.text or error() +end + +return Translator
--- a/src/site.js Thu Aug 28 05:16:32 2025 -0600 +++ b/src/site.js Thu Aug 28 13:31:46 2025 -0600 @@ -151,6 +151,15 @@ } } +function textContent(div) { + mdDiv.innerHTML = div.innerHTML; + let rts = mdDiv.querySelectorAll('rt'); + for( let rt of rts ) { + rt.remove(); + } + return mdDiv.textContent; +} + function handleMarkdown(voice,instructions) { let converter = window.markdownit(); let divs = document.querySelectorAll('[markdown]'); @@ -160,13 +169,7 @@ div.removeAttribute('markdown'); let parent = div.parentNode; if( parent.getAttribute('role')==='assistant' ) { - mdDiv.innerHTML = div.innerHTML; - let rts = mdDiv.querySelectorAll('rt'); - for( let rt of rts ) { - rt.remove(); - } - //console.log(mdDiv.textContent); - parent.querySelector('audio').src = `/tts.wav?voice=${voice}&instructions=${encodeURIComponent(instructions)}&text=${encodeURIComponent(mdDiv.textContent)}`; + parent.querySelector('audio').src = `/tts.wav?voice=${voice}&instructions=${encodeURIComponent(instructions)}&text=${encodeURIComponent(textContent(div))}`; } } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/translate.js.luan Thu Aug 28 13:31:46 2025 -0600 @@ -0,0 +1,23 @@ +local Luan = require "luan:Luan.luan" +local error = Luan.error +local Parsers = require "luan:Parsers.luan" +local json_string = Parsers.json_string or error() +local Io = require "luan:Io.luan" +local Http = require "luan:http/Http.luan" +local Shared = require "site:/lib/Shared.luan" +local languages = Shared.languages or error() +local Translator = require "site:/lib/ai/claude/Translator.luan" +local translate = Translator.translate or error() + + +return function() + local msg = Http.request.parameters.msg or error() + local text = Http.request.parameters.text or error() + local language = Http.request.parameters.language or error() + language = languages[language] or error() + text = translate(text,language) + Io.stdout = Http.response.text_writer() +%> + translated('<%=msg%>',<%=json_string(text)%>); +<% +end