changeset 25:3a80ddafe5a4

courses work
author Franklin Schmidt <fschmidt@gmail.com>
date Fri, 01 Aug 2025 00:33:51 -0600
parents 87fe70201aa8
children d3f5448743bf
files src/chat.html.luan src/chat.js src/edit_course.html.luan src/lib/Chat.luan src/lib/Course.luan src/lib/ai/Ai.luan src/lib/ai/claude/Ai_chat.luan src/save_course.js.luan
diffstat 8 files changed, 93 insertions(+), 70 deletions(-) [+]
line wrap: on
line diff
diff -r 87fe70201aa8 -r 3a80ddafe5a4 src/chat.html.luan
--- a/src/chat.html.luan	Thu Jul 31 22:30:26 2025 -0600
+++ b/src/chat.html.luan	Fri Aug 01 00:33:51 2025 -0600
@@ -17,6 +17,7 @@
 	if user == nil then return end
 	local chat_id = Http.request.parameters.chat or error()
 	local chat = get_chat_by_id(chat_id) or error()
+	local added_message = chat.init_ai()
 	Io.stdout = Http.response.text_writer()
 %>
 <!doctype html>
@@ -90,6 +91,9 @@
 		</dialog>
 		<script>
 			handleMarkdown();
+<%	if added_message then %>
+			playLastMessage();
+<%	end %>
 		</script>
 	</body>
 </html>
diff -r 87fe70201aa8 -r 3a80ddafe5a4 src/chat.js
--- a/src/chat.js	Thu Jul 31 22:30:26 2025 -0600
+++ b/src/chat.js	Fri Aug 01 00:33:51 2025 -0600
@@ -40,6 +40,14 @@
 	document.querySelector('[waiting-ai-icon]').style.display = 'none';
 }
 
+function playLastMessage() {
+	let audios = document.querySelectorAll('audio');
+	if( audios.length >= 1 ) {
+		let audio = audios[audios.length-1];
+		audio.play();
+	}
+}
+
 function updateAi(html) {
 	hideWaitingAiIcon();
 	document.querySelector('div[messages]').insertAdjacentHTML('beforeend',html);
@@ -52,11 +60,7 @@
 		scroll.scrollTo(0,scroll.scrollHeight);
 	});
 */
-	let audios = document.querySelectorAll('audio');
-	if( audios.length >= 1 ) {
-		let audio = audios[audios.length-1];
-		audio.play();
-	}
+	playLastMessage();
 }
 
 const isMobile = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
diff -r 87fe70201aa8 -r 3a80ddafe5a4 src/edit_course.html.luan
--- a/src/edit_course.html.luan	Thu Jul 31 22:30:26 2025 -0600
+++ b/src/edit_course.html.luan	Fri Aug 01 00:33:51 2025 -0600
@@ -35,35 +35,39 @@
 	<head>
 <%		head() %>
 		<style>
-			label,
 			input,
 			textarea {
 				display: block;
 			}
+			h4 {
+				margin-top: 22px;
+				margin-bottom: 4px;
+			}
+			input[type=submit] {
+				margin-top: 22px;
+			}
 		</style>
 	</head>
 	<body>
 <%		header() %>
 		<form content onsubmit="ajaxForm('/save_course.js',this)" action="javascript:">
-			<h1>Edit Course</h1>
-			<p>
-				Language: <%= course.language_name() %>
-				<input type=hidden name=language value="<%=course.language%>">
+			<input type=hidden name=language value="<%=course.language%>">
 <%	if course_id ~= nil then %>
-				<input type=hidden name=course value="<%=course_id%>">
+			<input type=hidden name=course value="<%=course_id%>">
 <%	end %>
-			</p>
-			<p>
-				<label prompt>Course name</label>
-				<input required name=name value="<%=html_encode(course.name)%>">
-			</p>
-			<p>
-				<label prompt>AI system prompt</label>
-				<textarea required name=ai_system_prompt><%=html_encode(course.ai_system_prompt)%></textarea>
-			</p>
-			<p>
-				<input type=submit>
-			</p>
+			<h1>Edit Course</h1>
+			<h3><%= course.language_name() %></h3>
+
+			<h4>Course name</h4>
+			<input required name=name value="<%=html_encode(course.name)%>">
+
+			<h4>AI system prompt</h4>
+			<textarea required name=ai_system_prompt><%=html_encode(course.ai_system_prompt)%></textarea>
+
+			<h4>AI first message (optional)</h4>
+			<textarea required name=ai_first_message><%=html_encode(course.ai_first_message or "")%></textarea>
+
+			<input type=submit>
 		</form>
 	</body>
 </html>
diff -r 87fe70201aa8 -r 3a80ddafe5a4 src/lib/Chat.luan
--- a/src/lib/Chat.luan	Thu Jul 31 22:30:26 2025 -0600
+++ b/src/lib/Chat.luan	Fri Aug 01 00:33:51 2025 -0600
@@ -9,10 +9,12 @@
 local html_encode = Html.encode or error()
 local Db = require "site:/lib/Db.luan"
 local run_in_transaction = Db.run_in_transaction or error()
-local Ai = require "site:/lib/ai/Ai.luan"
+local Ai_chat = require "site:/lib/ai/claude/Ai_chat.luan"
 local languages = require "site:/lib/languages.luan"
 local Utils = require "site:/lib/Utils.luan"
 local get_first = Utils.get_first or error()
+local Course = require "site:/lib/Course.luan"
+local get_course_by_id = Course.get_by_id or error()
 
 
 local Chat = {}
@@ -25,7 +27,6 @@
 		updated = doc.chat_updated
 		course_id = doc.course_id
 		name = doc.name
-		ai_name = doc.ai_name
 		ai_thread = doc.ai_thread
 		language = doc.language
 		language_region = doc.language_region
@@ -40,8 +41,7 @@
 		chat_updated = long(chat.updated)
 		course_id = long(chat.course_id)
 		name = chat.name or error()
-		ai_name = chat.ai_name or error()
-		ai_thread = chat.ai_thread -- or error()
+		ai_thread = chat.ai_thread
 		language = chat.language or error()
 		language_region = chat.language_region or error()
 	}
@@ -53,8 +53,6 @@
 
 function Chat.new(chat)
 	chat.updated = chat.updated or time_now()
-	chat.ai_name = chat.ai_name or "claude"
-	chat.ai = Ai[chat.ai_name]["Ai_chat.luan"] or error()
 	chat.language_region = chat.language_region or first_region(chat.language)
 
 	function chat.save()
@@ -75,23 +73,38 @@
 		return html_encode(chat.name)
 	end
 
-	function chat.output_system_prompt()
-		chat.ai.output_system_prompt(chat.ai_thread)
-	end
-
-	function chat.output_messages_html()
-		chat.ai.output_messages_html(chat.language_region,chat.ai_thread)
-	end
-
-	function chat.ask(input)
-		local old_thread = chat.ai_thread
-		local ai_thread = chat.ai.ask(old_thread,input)
+	function chat.init_ai()  -- return if added message
+		if chat.ai_thread ~= nil then
+			return false
+		end
+		local course = get_course_by_id(chat.course_id) or error()
+		local ai_first_message = course.ai_first_message
+		local ai_thread = Ai_chat.ask_first(course.ai_system_prompt,ai_first_message)
 		run_in_transaction( function()
 			chat = chat.reload()
 			chat.ai_thread = ai_thread
 			chat.save()
 		end )
-		return `chat.ai.output_messages_html(chat.language_region,ai_thread,old_thread)`
+		return ai_first_message ~= nil
+	end
+
+	function chat.output_system_prompt()
+		Ai_chat.output_system_prompt(chat.ai_thread)
+	end
+
+	function chat.output_messages_html()
+		Ai_chat.output_messages_html(chat.language_region,chat.ai_thread)
+	end
+
+	function chat.ask(input)
+		local old_thread = chat.ai_thread
+		local ai_thread = Ai_chat.ask_more(old_thread,input)
+		run_in_transaction( function()
+			chat = chat.reload()
+			chat.ai_thread = ai_thread
+			chat.save()
+		end )
+		return `Ai_chat.output_messages_html(chat.language_region,ai_thread,old_thread)`
 	end
 
 	function chat.language_name()
diff -r 87fe70201aa8 -r 3a80ddafe5a4 src/lib/Course.luan
--- a/src/lib/Course.luan	Thu Jul 31 22:30:26 2025 -0600
+++ b/src/lib/Course.luan	Fri Aug 01 00:33:51 2025 -0600
@@ -20,6 +20,7 @@
 		updated = doc.course_updated
 		name = doc.name
 		ai_system_prompt = doc.ai_system_prompt
+		ai_first_message = doc.ai_first_message
 	}
 end
 
@@ -32,6 +33,7 @@
 		course_updated = long(course.updated)
 		name = course.name or error()
 		ai_system_prompt = course.ai_system_prompt or error()
+		ai_first_message = course.ai_first_message
 	}
 end
 
diff -r 87fe70201aa8 -r 3a80ddafe5a4 src/lib/ai/Ai.luan
--- a/src/lib/ai/Ai.luan	Thu Jul 31 22:30:26 2025 -0600
+++ b/src/lib/ai/Ai.luan	Fri Aug 01 00:33:51 2025 -0600
@@ -3,6 +3,8 @@
 local ipairs = Luan.ipairs or error()
 
 
+error "not used for now"
+
 local Ai = {}
 
 local ais = {"claude"}
diff -r 87fe70201aa8 -r 3a80ddafe5a4 src/lib/ai/claude/Ai_chat.luan
--- a/src/lib/ai/claude/Ai_chat.luan	Thu Jul 31 22:30:26 2025 -0600
+++ b/src/lib/ai/claude/Ai_chat.luan	Fri Aug 01 00:33:51 2025 -0600
@@ -74,34 +74,7 @@
 end
 
 
-local system_prompt = [[
-# Your Role
-
-You are a Japanese language teacher.
-
-# Romaji
-
-CRITICAL REQUIREMENT: When writing Japanese, use ruby markdown syntax {japanese|romaji} for pronunciation guidance.
-
-Apply ruby tags to meaningful pronunciation units:
-- Individual kanji or kanji compounds: {私|watashi}, {学生|gakusei}
-- Hiragana/katakana words and particles: {は|wa}, {です|desu}, {ありがとう|arigatō}
-- Grammatical elements: {ました|mashita}, {ません|masen}
-
-The romaji must reflect ACTUAL PRONUNCIATION, not character-by-character readings.
-Use macrons for long vowels: ā, ī, ū, ē, ō
-
-APPLIES TO ALL JAPANESE TEXT: Example sentences, grammar explanations, vocabulary lists, casual mentions - ANY Japanese characters in your response need ruby tags.
-
-VERIFICATION STEP: Before sending, scan your ENTIRE response for any Japanese characters (hiragana, katakana, kanji) and ensure they all have ruby tags.
-]]
-
-
-function Ai_chat.ask(thread,input)
-	thread = thread and json_parse(thread) or {
-		system = system_prompt
-		messages = {nil}
-	}
+local function ask(thread,input)
 	local messages = thread.messages or error
 	messages[#messages+1] = {
 		role = "user"
@@ -115,7 +88,7 @@
 ]]
 	}
 	if true then
-		return json_string(thread)
+		return
 	end
 --]=]
 	-- logger.info(json_string(thread))
@@ -130,6 +103,22 @@
 		role = "assistant"
 		content = content
 	}
+end
+
+function Ai_chat.ask_first(system_prompt,input)
+	local thread = {
+		system = system_prompt
+		messages = {nil}
+	}
+	if input ~= nil then
+		ask(thread,input)
+	end
+	return json_string(thread)
+end
+
+function Ai_chat.ask_more(thread,input)
+	thread = json_parse(thread)
+	ask(thread,input)
 	return json_string(thread)
 end
 
diff -r 87fe70201aa8 -r 3a80ddafe5a4 src/save_course.js.luan
--- a/src/save_course.js.luan	Thu Jul 31 22:30:26 2025 -0600
+++ b/src/save_course.js.luan	Fri Aug 01 00:33:51 2025 -0600
@@ -1,5 +1,7 @@
 local Luan = require "luan:Luan.luan"
 local error = Luan.error
+local String = require "luan:String.luan"
+local trim = String.trim or error()
 local Time = require "luan:Time.luan"
 local time_now = Time.now or error()
 local Io = require "luan:Io.luan"
@@ -29,6 +31,9 @@
 		end
 		course.name = parameters.name or error()
 		course.ai_system_prompt = parameters.ai_system_prompt or error()
+		local ai_first_message = parameters.ai_first_message or error()
+		ai_first_message = trim(ai_first_message)
+		course.ai_first_message = ai_first_message~="" and ai_first_message or nil
 		course.updated = time_now()
 		course.save()
 	end )