promptable-todo-list/public/index.html
2025-05-11 00:21:33 +02:00

227 lines
No EOL
9.2 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Promptable To-Do List</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
<style>
:root {
--pico-spacing: 0;
--pico-typography-spacing-vertical: 0;
--pico-form-element-spacing-vertical: 0;
--pico-form-element-spacing-horizontal: 0;
--pico-nav-element-spacing-vertical: 0;
--pico-nav-element-spacing-horizontal: 0;
--pico-nav-link-spacing-vertical: 0;
--pico-nav-link-spacing-horizontal: 0;
}
ul {
padding-left: 0;
}
input[type="checkbox"] {
margin: 0;
}
input[type="text"] {
padding: 0 0.25rem;
}
.app {
height: 100dvh;
display: flex;
flex-direction: column;
}
.app__header,
.app__main {
padding: 1rem;
}
.app__header {
display: flex;
justify-content: space-between;
align-items: center;
}
.app__main,
.main__tasks {
display: flex;
flex-direction: column;
row-gap: 1rem;
}
.app__main {
flex: 1;
}
.main__tasks__task,
.main__new-task {
display: flex;
align-items: center;
column-gap: 0.5rem;
}
.main__prompt {
margin-top: auto;
display: grid;
grid-template-columns: auto 1fr 100px;
column-gap: 0.5rem;
}
.main__prompt__submit {
padding: 0 0.5rem;
}
.main__prompt__submit[aria-busy] .main__prompt__submit__label {
display: none;
}
</style>
<script type="module">
import createSimpleElement from 'https://cdn.jsdelivr.net/npm/create-simple-element/createSimpleElement.js';
import axios from 'https://cdn.jsdelivr.net/npm/axios@1.9.0/dist/esm/axios.min.js';
let tasks = JSON.parse(window.localStorage.getItem('tasks') || '[]');
const
sortTasks = () => tasks.sort((a, b) => a.order - b.order),
saveTasks = () => window.localStorage.setItem('tasks', JSON.stringify(tasks)),
reloadTasks = () => {
sortTasks();
saveTasks();
const tasksElement = document.querySelector('.main__tasks');
tasksElement.innerHTML = '';
for(const task of tasks){
const taskElement = createSimpleElement('li.main__tasks__task');
const formElement = document.createElement('form');
formElement.setAttribute('style', 'display: contents');
taskElement.appendChild(formElement);
const isDoneElement = createSimpleElement('input[type="checkbox"]');
if(task.isDone)
isDoneElement.setAttribute('checked', '');
isDoneElement.addEventListener('input', () => {
task.isDone = isDoneElement.checked;
saveTasks();
});
formElement.appendChild(isDoneElement);
const titleElement = createSimpleElement('input[type="text"]');
titleElement.setAttribute('value', task.title);
formElement.appendChild(titleElement);
// TODO up/down
const removeElement = createSimpleElement('button.secondary');
removeElement.textContent = '🗑️';
removeElement.addEventListener('click', () => {
tasks.splice(tasks.indexOf(task), 1);
reloadTasks();
});
formElement.appendChild(removeElement);
// TODO replace form 'submit' event w/ field 'input' event
formElement.addEventListener('submit', event => {
event.preventDefault();
task.isDone = isDoneElement.checked;
task.title = titleElement.value;
saveTasks();
});
tasksElement.appendChild(taskElement);
}
};
document.addEventListener('DOMContentLoaded', async () => {
reloadTasks();
document.querySelector('.main__new-task').addEventListener('submit', event => {
event.preventDefault();
const
isDoneElement = document.querySelector('.main__new-task__is-done'),
titleElement = document.querySelector('.main__new-task__title');
tasks.push({
order: tasks.length ? Math.max(...tasks.map(task => task.order)) + 1 : 0,
isDone: isDoneElement.checked,
title: titleElement.value
});
isDoneElement.checked = false;
titleElement.value = '';
saveTasks();
reloadTasks();
});
const
promptFieldsetElement = document.querySelector('.main__prompt__fieldset'),
promptSubmitElement = document.querySelector('.main__prompt__submit'),
promptModelElement = document.querySelector('.main__prompt__model'),
promptTextElement = document.querySelector('.main__prompt__text');
document.querySelector('.main__prompt').addEventListener('submit', async event => {
event.preventDefault();
promptFieldsetElement.disabled = true;
promptSubmitElement.setAttribute('aria-busy', 'true');
let finalState;
try {
({
data: {
finalState
}
} = await axios.post('/', {
baseURL: 'http://localhost:11434/api',
model: promptModelElement.value,
initialState: tasks,
instructions: promptTextElement.value
}));
}
catch(error){
alert(
error.response?.data
? JSON.stringify(error.response.data, null, 4)
: error.stack
);
}
if(finalState){
tasks = finalState;
saveTasks();
reloadTasks();
promptTextElement.value = '';
}
promptSubmitElement.removeAttribute('aria-busy');
promptFieldsetElement.disabled = false;
});
promptModelElement.value = window.localStorage.getItem('model') || '';
promptModelElement.addEventListener('input', () => window.localStorage.setItem('model', promptModelElement.value));
});
</script>
</head>
<body class="app">
<header class="app__header">
<h1>Promptable To-Do List</h1>
<nav>
<ul>
<li><a href="https://git.kaki87.net/KaKi87/promptable-todo-list.git" target="_blank">Source code</a></li>
</ul>
</nav>
</header>
<main class="app__main">
<ul class="main__tasks"></ul>
<form class="main__new-task">
<label style="display: contents"><input class="main__new-task__is-done" type="checkbox"></label>
<label style="display: contents"><input class="main__new-task__title" type="text" placeholder="Manually add a new task..."></label>
</form>
<form class="main__prompt">
<fieldset class="main__prompt__fieldset" style="display: contents">
<label style="display: contents"><input class="main__prompt__model" type="text" placeholder="Model ID" required></label>
<label style="display: contents"><input class="main__prompt__text" type="text" placeholder="Prompt..." required></label>
<button class="main__prompt__submit" type="submit"><span class="main__prompt__submit__label">Submit</span></button>
</fieldset>
</form>
</main>
</body>
</html>