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