changeset 24:87fe70201aa8

courses work
author Franklin Schmidt <fschmidt@gmail.com>
date Thu, 31 Jul 2025 22:30:26 -0600
parents 0c17c233c45a
children 3a80ddafe5a4
files courses/j1.txt src/account.html.luan src/chat.html.luan src/chats.html.luan src/edit_course.html.luan src/lang_courses.html.luan src/lib/Chat.luan src/lib/Course.luan src/lib/Db.luan src/lib/User.luan src/new_chat.red.luan src/save_course.js.luan src/your_courses.html.luan
diffstat 13 files changed, 289 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
diff -r 0c17c233c45a -r 87fe70201aa8 courses/j1.txt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/courses/j1.txt	Thu Jul 31 22:30:26 2025 -0600
@@ -0,0 +1,22 @@
+j1
+
+
+# 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.
diff -r 0c17c233c45a -r 87fe70201aa8 src/account.html.luan
--- a/src/account.html.luan	Wed Jul 30 23:29:33 2025 -0600
+++ b/src/account.html.luan	Thu Jul 31 22:30:26 2025 -0600
@@ -30,6 +30,7 @@
 			<h1>Your Account</h1>
 
 			<p><a href="chats.html">Your chats</a></p>
+			<p><a href="your_courses.html">Your courses</a></p>
 			<p><a href="javascript:logout()">Logout</a></p>
 		</div>
 	</body>
diff -r 0c17c233c45a -r 87fe70201aa8 src/chat.html.luan
--- a/src/chat.html.luan	Wed Jul 30 23:29:33 2025 -0600
+++ b/src/chat.html.luan	Thu Jul 31 22:30:26 2025 -0600
@@ -7,17 +7,14 @@
 local header = Shared.header or error()
 local started = Shared.started or error()
 local User = require "site:/lib/User.luan"
-local current_user = User.current or error()
+local current_user = User.current_required or error()
 local Chat = require "site:/lib/Chat.luan"
 local get_chat_by_id = Chat.get_by_id or error()
 
 
 return function()
 	local user = current_user()
-	if user == nil then
-		Http.response.send_redirect("/login.html")
-		return
-	end
+	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()
 	Io.stdout = Http.response.text_writer()
diff -r 0c17c233c45a -r 87fe70201aa8 src/chats.html.luan
--- a/src/chats.html.luan	Wed Jul 30 23:29:33 2025 -0600
+++ b/src/chats.html.luan	Thu Jul 31 22:30:26 2025 -0600
@@ -8,7 +8,7 @@
 local head = Shared.head or error()
 local header = Shared.header or error()
 local User = require "site:/lib/User.luan"
-local current_user = User.current or error()
+local current_user = User.current_required or error()
 local Chat = require "site:/lib/Chat.luan"
 local chat_search = Chat.search or error()
 local languages = require "site:/lib/languages.luan"
@@ -18,10 +18,7 @@
 
 return function()
 	local user = current_user()
-	if user == nil then
-		Http.response.send_redirect("/login.html")
-		return
-	end
+	if user == nil then return end
 	local chats = chat_search( "chat_user_id:"..user.id, "chat_updated desc" )
 	local select_language = #chats > 0 and chats[1].language or nil
 	Io.stdout = Http.response.text_writer()
@@ -43,7 +40,7 @@
 <%		header() %>
 		<div content>
 			<h1>Your Chats</h1>
-			<form action="new_chat.red">
+			<form action="lang_courses.html">
 				<select name=language>
 <%	for _, lang in pairs(languages) do
 		local code = lang.code
diff -r 0c17c233c45a -r 87fe70201aa8 src/edit_course.html.luan
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/edit_course.html.luan	Thu Jul 31 22:30:26 2025 -0600
@@ -0,0 +1,71 @@
+local Luan = require "luan:Luan.luan"
+local error = Luan.error
+local Html = require "luan:Html.luan"
+local html_encode = Html.encode or error()
+local Io = require "luan:Io.luan"
+local Http = require "luan:http/Http.luan"
+local Shared = require "site:/lib/Shared.luan"
+local head = Shared.head or error()
+local header = Shared.header or error()
+local User = require "site:/lib/User.luan"
+local current_user = User.current_required or error()
+local Course = require "site:/lib/Course.luan"
+local get_course_by_id = Course.get_by_id or error()
+
+
+return function()
+	local user = current_user()
+	if user == nil then return end
+	local course_id = Http.request.parameters.course
+	local course
+	if course_id ~= nil then
+		course = get_course_by_id(course_id) or error()
+		course.user_id == user.id or error()
+	else
+		course = Course.new{
+			language = Http.request.parameters.language or error()
+			name = ""
+			ai_system_prompt = ""
+		}
+	end
+	Io.stdout = Http.response.text_writer()
+%>
+<!doctype html>
+<html lang="en">
+	<head>
+<%		head() %>
+		<style>
+			label,
+			input,
+			textarea {
+				display: block;
+			}
+		</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%>">
+<%	if course_id ~= nil then %>
+				<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>
+		</form>
+	</body>
+</html>
+<%
+end
diff -r 0c17c233c45a -r 87fe70201aa8 src/lang_courses.html.luan
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lang_courses.html.luan	Thu Jul 31 22:30:26 2025 -0600
@@ -0,0 +1,55 @@
+local Luan = require "luan:Luan.luan"
+local error = Luan.error
+local ipairs = Luan.ipairs or error()
+local Io = require "luan:Io.luan"
+local Http = require "luan:http/Http.luan"
+local Shared = require "site:/lib/Shared.luan"
+local head = Shared.head or error()
+local header = Shared.header or error()
+local Course = require "site:/lib/Course.luan"
+local course_search = Course.search or error()
+local languages = require "site:/lib/languages.luan"
+local Logging = require "luan:logging/Logging.luan"
+local logger = Logging.logger "chats.html"
+
+
+return function()
+	local language = Http.request.parameters.language or error()
+	local courses = course_search( "course_language:"..language )
+	local select_language = #courses > 0 and courses[1].language or nil
+	Io.stdout = Http.response.text_writer()
+%>
+<!doctype html>
+<html lang="en">
+	<head>
+<%		head() %>
+		<style>
+			form {
+				margin-bottom: 20px;
+			}
+			td {
+				padding: 8px 8px;
+			}
+		</style>
+	</head>
+	<body>
+<%		header() %>
+		<div content>
+			<h1><%=languages[language].name%> Courses</h1>
+			<form action="edit_course.html">
+				<input type=hidden name=language value="<%=language%>">
+				<input type=submit value="new course">
+			</form>
+			<table>
+<%	for _, course in ipairs(courses) do %>
+				<tr>
+					<td><%= course.name_html() %></td>
+					<td><a href="new_chat.red?course=<%=course.id%>">new chat</a></td>
+				</tr>
+<%	end %>
+			</table>
+		</div>
+	</body>
+</html>
+<%
+end
diff -r 0c17c233c45a -r 87fe70201aa8 src/lib/Chat.luan
--- a/src/lib/Chat.luan	Wed Jul 30 23:29:33 2025 -0600
+++ b/src/lib/Chat.luan	Thu Jul 31 22:30:26 2025 -0600
@@ -23,6 +23,7 @@
 		id = doc.id
 		user_id = doc.chat_user_id
 		updated = doc.chat_updated
+		course_id = doc.course_id
 		name = doc.name
 		ai_name = doc.ai_name
 		ai_thread = doc.ai_thread
@@ -37,6 +38,7 @@
 		id = chat.id
 		chat_user_id = long(chat.user_id)
 		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()
diff -r 0c17c233c45a -r 87fe70201aa8 src/lib/Course.luan
--- a/src/lib/Course.luan	Wed Jul 30 23:29:33 2025 -0600
+++ b/src/lib/Course.luan	Thu Jul 31 22:30:26 2025 -0600
@@ -3,6 +3,8 @@
 local ipairs = Luan.ipairs or error()
 local Number = require "luan:Number.luan"
 local long = Number.long or error()
+local Html = require "luan:Html.luan"
+local html_encode = Html.encode or error()
 local Db = require "site:/lib/Db.luan"
 local languages = require "site:/lib/languages.luan"
 
@@ -15,6 +17,7 @@
 		id = doc.id
 		user_id = doc.course_user_id
 		language = doc.course_language
+		updated = doc.course_updated
 		name = doc.name
 		ai_system_prompt = doc.ai_system_prompt
 	}
@@ -26,6 +29,7 @@
 		id = course.id
 		course_user_id = long(course.user_id)
  		course_language = course.language or error()
+		course_updated = long(course.updated)
 		name = course.name or error()
 		ai_system_prompt = course.ai_system_prompt or error()
 	}
@@ -34,9 +38,13 @@
 function Course.new(course)
 
 	function course.save()
-		local doc = to_doc(chat)
+		local doc = to_doc(course)
 		Db.save(doc)
-		chat.id = doc.id
+		course.id = doc.id
+	end
+
+	function course.name_html()
+		return html_encode(course.name)
 	end
 
 	function course.language_name()
@@ -46,7 +54,7 @@
 	return course
 end
 
-local function search(query,sort,rows)
+function Course.search(query,sort,rows)
 	rows = rows or 1000000
 	local courses = {}
 	local docs = Db.search(query,1,rows,{sort=sort})
@@ -56,10 +64,6 @@
 	return courses
 end
 
-function Course.get_for_language(language)
-	return search("course_language:"..language)
-end
-
 function Course.get_by_id(id)
 	local doc = Db.get_document("id:"..id)
 	return doc and doc.type=="course" and from_doc(doc) or nil
diff -r 0c17c233c45a -r 87fe70201aa8 src/lib/Db.luan
--- a/src/lib/Db.luan	Wed Jul 30 23:29:33 2025 -0600
+++ b/src/lib/Db.luan	Thu Jul 31 22:30:26 2025 -0600
@@ -23,6 +23,7 @@
 
 Db.indexed_fields.course_user_id = Lucene.type.long
 Db.indexed_fields.course_language = Lucene.type.string
+Db.indexed_fields.course_updated = Lucene.type.long
 
 Db.restore_from_log()
 
diff -r 0c17c233c45a -r 87fe70201aa8 src/lib/User.luan
--- a/src/lib/User.luan	Wed Jul 30 23:29:33 2025 -0600
+++ b/src/lib/User.luan	Thu Jul 31 22:30:26 2025 -0600
@@ -126,4 +126,10 @@
 end
 User.current = current
 
+function User.current_required()
+	local user = current()
+	user or Http.response.send_redirect "/login.html"
+	return user
+end
+
 return User
diff -r 0c17c233c45a -r 87fe70201aa8 src/new_chat.red.luan
--- a/src/new_chat.red.luan	Wed Jul 30 23:29:33 2025 -0600
+++ b/src/new_chat.red.luan	Thu Jul 31 22:30:26 2025 -0600
@@ -4,15 +4,20 @@
 local User = require "site:/lib/User.luan"
 local current_user = User.current or error()
 local Chat = require "site:/lib/Chat.luan"
+local Course = require "site:/lib/Course.luan"
+local get_course_by_id = Course.get_by_id or error()
+
 
 
 return function()
 	local user = current_user() or error()
-	local language = Http.request.parameters.language or error()
+	local course_id = Http.request.parameters.course or error()
+	local course = get_course_by_id(course_id) or error()
 	local chat = Chat.new{
 		user_id = user.id
-		name = "whatever"
-		language = language
+		course_id = course.id
+		name = course.name
+		language = course.language
 	}
 	chat.save()
 	Http.response.send_redirect("chat.html?chat="..chat.id)
diff -r 0c17c233c45a -r 87fe70201aa8 src/save_course.js.luan
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/save_course.js.luan	Thu Jul 31 22:30:26 2025 -0600
@@ -0,0 +1,39 @@
+local Luan = require "luan:Luan.luan"
+local error = Luan.error
+local Time = require "luan:Time.luan"
+local time_now = Time.now or error()
+local Io = require "luan:Io.luan"
+local Http = require "luan:http/Http.luan"
+local User = require "site:/lib/User.luan"
+local current_user = User.current or error()
+local Db = require "site:/lib/Db.luan"
+local run_in_transaction = Db.run_in_transaction or error()
+local Course = require "site:/lib/Course.luan"
+local get_course_by_id = Course.get_by_id or error()
+
+
+return function()
+	local user = current_user() or error()
+	local parameters = Http.request.parameters
+	local course_id = parameters.course
+	run_in_transaction( function()
+		local course
+		if course_id ~= nil then
+			course = get_course_by_id(course_id) or error()
+			course.user_id == user.id or error()
+		else
+			course = Course.new{
+				user_id = user.id
+				language = parameters.language or error()
+			}
+		end
+		course.name = parameters.name or error()
+		course.ai_system_prompt = parameters.ai_system_prompt or error()
+		course.updated = time_now()
+		course.save()
+	end )
+	Io.stdout = Http.response.text_writer()
+%>
+	location = '/your_courses.html';
+<%
+end
diff -r 0c17c233c45a -r 87fe70201aa8 src/your_courses.html.luan
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/your_courses.html.luan	Thu Jul 31 22:30:26 2025 -0600
@@ -0,0 +1,68 @@
+local Luan = require "luan:Luan.luan"
+local error = Luan.error
+local ipairs = Luan.ipairs or error()
+local pairs = Luan.pairs or error()
+local Io = require "luan:Io.luan"
+local Http = require "luan:http/Http.luan"
+local Shared = require "site:/lib/Shared.luan"
+local head = Shared.head or error()
+local header = Shared.header or error()
+local User = require "site:/lib/User.luan"
+local current_user = User.current_required or error()
+local Course = require "site:/lib/Course.luan"
+local course_search = Course.search or error()
+local languages = require "site:/lib/languages.luan"
+local Logging = require "luan:logging/Logging.luan"
+local logger = Logging.logger "chats.html"
+
+
+return function()
+	local user = current_user()
+	if user == nil then return end
+	local courses = course_search( "course_user_id:"..user.id, "course_updated desc" )
+	local select_language = #courses > 0 and courses[1].language or nil
+	Io.stdout = Http.response.text_writer()
+%>
+<!doctype html>
+<html lang="en">
+	<head>
+<%		head() %>
+		<style>
+			form {
+				margin-bottom: 20px;
+			}
+			td {
+				padding: 8px 8px;
+			}
+		</style>
+	</head>
+	<body>
+<%		header() %>
+		<div content>
+			<h1>Your Courses</h1>
+			<form action="edit_course.html">
+				<select name=language>
+<%	for _, lang in pairs(languages) do
+		local code = lang.code
+		local selected = code==select_language and "selected" or ""
+%>
+					<option value="<%=code%>" <%=selected%> ><%=lang.name%></option>
+<%	end %>
+				</select>
+				<input type=submit value="new course">
+			</form>
+			<table>
+<%	for _, course in ipairs(courses) do %>
+				<tr>
+					<td><%= course.language_name() %></td>
+					<td><%= course.name_html() %></td>
+					<td><a href="new_chat.red?course=<%=course.id%>">new chat</a></td>
+					<td><a href="edit_course.html?course=<%=course.id%>">edit</a></td>
+				</tr>
+<%	end %>
+			</table>
+		</div>
+	</body>
+</html>
+<%
+end