Mercurial Hosting > lang
diff src/lib/claude/Ai_chat.luan @ 76:3c60bc17403e
remove ai
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Sun, 31 Aug 2025 17:40:20 -0600 |
parents | src/lib/ai/claude/Ai_chat.luan@64e35a92d163 |
children | e8219cec281f |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/claude/Ai_chat.luan Sun Aug 31 17:40:20 2025 -0600 @@ -0,0 +1,255 @@ +local Luan = require "luan:Luan.luan" +local error = Luan.error +local ipairs = Luan.ipairs or error() +local pairs = Luan.pairs or error() +local type = Luan.type or error() +local String = require "luan:String.luan" +local starts_with = String.starts_with or error() +local Html = require "luan:Html.luan" +local html_encode = Html.encode or error() +local Parsers = require "luan:Parsers.luan" +local json_parse = Parsers.json_parse or error() +local json_string = Parsers.json_string or error() +local Thread = require "luan:Thread.luan" +local Claude = require "site:/lib/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" + + +local Ai_chat = {} + +function Ai_chat.output_system_prompt(thread) + thread = json_parse(thread) + local system_prompt = thread.system or error + system_prompt = html_encode(system_prompt) + %><%=system_prompt%><% +end + +function Ai_chat.output_messages_html(assistant_controls,thread,old_thread) + thread = json_parse(thread) + local messages = thread.messages or error + local n = 0 + if old_thread ~= nil then + old_thread = json_parse(old_thread) + local old_messages = old_thread.messages or error + n = #old_messages + end + for i, message in ipairs(messages) do + if i <= n then + continue + end + local role = message.role or error() + local who + if role=="assistant" then + who = "Claude" + elseif role=="user" then + who = "You" + else + error(role) + end + local function output(text) + text = html_encode(text) +%> + <div role="<%=role%>" msg="<%=i%>"> + <h3><%=who%></h3> + <div message markdown><%=text%></div> +<% if role=="assistant" then %> +<%= assistant_controls %> +<% end %> + </div> +<% + end + local content = message.content or error() + if type(content) == "string" then + output(content) + else + for _, part in ipairs(content) do + if part.type=="text" then + local text = part.text or error() + output(text) + end + end + end + end_for +end + +local function get_chat(chat_id) + local Chat = require "site:/lib/Chat.luan" + local User = require "site:/lib/User.luan" + local chat = Chat.get_by_id(chat_id) or error() + local user = User.current() + local is_owner = user ~= nil and user.id == chat.user_id + is_owner or not chat.is_private or error "private" + return chat +end + +local functions = { + get_chat = { + tool = { + description = "Get the contents of a chat/thread with Claude on this website. The contents will be JSON in the format of the Claude API." + input_schema = { + type = "object" + properties = { + chat_id = { + description = "The ID of the chat" + type = "integer" + } + } + } + } + fn = function(input) + local chat_id = input.chat_id or error() + local chat = get_chat(chat_id) + return chat.ai_thread or error() + end + } + get_tts_instructions = { + tool = { + description = "Get the text-to-speech instructions of a chat/thread on this website. These instructions are passed to OpenAI. If there are no instructions, the empty string is returned." + input_schema = { + type = "object" + properties = { + chat_id = { + description = "The ID of the chat" + type = "integer" + } + } + } + } + fn = function(input) + local chat_id = input.chat_id or error() + local chat = get_chat(chat_id) + return chat.tts_instructions or error() + end + } + get_stt_prompt = { + tool = { + description = "Get the speech-to-text prompt of a chat/thread on this website. This prompt is passed to OpenAI. If there is no prompt, the empty string is returned." + input_schema = { + type = "object" + properties = { + chat_id = { + description = "The ID of the chat" + type = "integer" + } + } + } + } + fn = function(input) + local chat_id = input.chat_id or error() + local chat = get_chat(chat_id) + return chat.stt_prompt or error() + end + } +} +local tools = {nil} +for name, f in pairs(functions) do + f.name = name + f.tool.name = name + tools[#tools+1] = f.tool +end + +function Ai_chat.init(system_prompt) + local thread = { + system = system_prompt + tools = tools + messages = {nil} + } + return json_string(thread) +end + +function Ai_chat.has_messages(thread) + thread = json_parse(thread) + 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 +--[=[ + messages[#messages+1] = { + role = "assistant" + content = [[ +hello +]] + } + Thread.sleep(2000) + if true then + return + end +--]=] + -- logger.info(json_string(thread)) + local resultJson = chit_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() + messages[#messages+1] = { + role = "assistant" + content = content + } + local stop_reason = result.stop_reason or error() + if stop_reason == "end_turn" then + -- ok + elseif stop_reason == "tool_use" then + local response = {nil} + for _, part in ipairs(content) do + if part.type == "tool_use" then + local f = functions[part.name] or error() + local input = part.input or error() + response[#response+1] = { + type = "tool_result" + tool_use_id = part.id or error() + content = f.fn(input) + } + end + end + messages[#messages+1] = { + role = "user" + content = response + } + ask(thread) + else + error(stop_reason) + end +end + +function Ai_chat.add(thread,input) + thread = json_parse(thread) + local messages = thread.messages or error + messages[#messages+1] = { + role = "user" + content = input + } + return json_string(thread) +end + +function Ai_chat.respond(thread) + thread = json_parse(thread) + ask(thread) + return json_string(thread) +end + +return Ai_chat