Initial commit: note-taking/task-tracking app with LLM integration scaffold
Vue 3 + TypeScript frontend with Pinia stores, markdown rendering (marked + DOMPurify), wikilink/tag linkification, and autocomplete. Quart async backend with SQLAlchemy 2.0, PostgreSQL ARRAY columns, task-note companion linking, backlinks, and note-to-task conversion. Docker Compose setup with PostgreSQL 16 and Ollama. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue";
|
||||
import { apiGet } from "@/api/client";
|
||||
import type { Note, NoteListResponse } from "@/types/note";
|
||||
import type { Task, TaskListResponse } from "@/types/task";
|
||||
import NoteCard from "@/components/NoteCard.vue";
|
||||
import TaskCard from "@/components/TaskCard.vue";
|
||||
import type { TaskStatus } from "@/types/task";
|
||||
import { useTasksStore } from "@/stores/tasks";
|
||||
|
||||
const recentNotes = ref<Note[]>([]);
|
||||
const recentTasks = ref<Task[]>([]);
|
||||
const loading = ref(true);
|
||||
const tasksStore = useTasksStore();
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const notesData = await apiGet<NoteListResponse>(
|
||||
"/api/notes?sort=updated_at&order=desc&limit=5"
|
||||
);
|
||||
recentNotes.value = notesData.notes;
|
||||
} catch (e) {
|
||||
console.error("Failed to load recent notes:", e);
|
||||
}
|
||||
|
||||
try {
|
||||
const tasksData = await apiGet<TaskListResponse>(
|
||||
"/api/tasks?sort=updated_at&order=desc&limit=5"
|
||||
);
|
||||
recentTasks.value = tasksData.tasks;
|
||||
} catch (e) {
|
||||
console.error("Failed to load recent tasks:", e);
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
function onStatusToggle(id: number, status: TaskStatus) {
|
||||
tasksStore.patchStatus(id, status).then((updated) => {
|
||||
const idx = recentTasks.value.findIndex((t) => t.id === updated.id);
|
||||
if (idx !== -1) {
|
||||
recentTasks.value[idx] = updated;
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="home">
|
||||
<h1>Fabled Assistant</h1>
|
||||
|
||||
<p v-if="loading" class="loading">Loading...</p>
|
||||
|
||||
<template v-else>
|
||||
<section class="section">
|
||||
<div class="section-header">
|
||||
<h2>Recent Notes</h2>
|
||||
<router-link to="/notes" class="see-all">See all</router-link>
|
||||
</div>
|
||||
<div v-if="recentNotes.length" class="cards">
|
||||
<NoteCard
|
||||
v-for="note in recentNotes"
|
||||
:key="note.id"
|
||||
:note="note"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="empty-state">
|
||||
<p class="empty-text">No notes yet.</p>
|
||||
<router-link to="/notes/new" class="btn-cta">+ New Note</router-link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="section-header">
|
||||
<h2>Recent Tasks</h2>
|
||||
<router-link to="/tasks" class="see-all">See all</router-link>
|
||||
</div>
|
||||
<div v-if="recentTasks.length" class="cards">
|
||||
<TaskCard
|
||||
v-for="task in recentTasks"
|
||||
:key="task.id"
|
||||
:task="task"
|
||||
@status-toggle="onStatusToggle"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="empty-state">
|
||||
<p class="empty-text">No tasks yet.</p>
|
||||
<router-link to="/tasks/new" class="btn-cta">+ New Task</router-link>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.home {
|
||||
max-width: 720px;
|
||||
margin: 2rem auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
.home h1 {
|
||||
margin: 0 0 1.5rem;
|
||||
}
|
||||
.loading {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.section {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.section-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
.see-all {
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.see-all:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 1.5rem 0;
|
||||
}
|
||||
.empty-text {
|
||||
color: var(--color-text-muted);
|
||||
margin: 0 0 0.75rem;
|
||||
}
|
||||
.btn-cta {
|
||||
display: inline-block;
|
||||
padding: 0.4rem 1rem;
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user