Add CalDAV calendar integration, LLM-suggested tags, and settings refinements

- CalDAV integration: per-user calendar config, create/list/search events
  via caldav library, LLM tools for calendar operations from chat
- LLM-suggested tags: new tag_suggestions service prompts LLM with existing
  tags and note content to suggest 3-5 relevant tags; exposed via API
  endpoints (suggest-tags, append-tag); integrated into editor views
  (suggest button + clickable pills) and chat tool calls (pills in
  ToolCallCard with one-click apply)
- Settings/model UI refinements, generation task improvements

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 22:40:20 -05:00
parent 8996b45e50
commit d7bc3f3222
22 changed files with 1158 additions and 837 deletions
+2 -15
View File
@@ -1,17 +1,15 @@
<script setup lang="ts">
import { ref, watch, nextTick } from "vue";
import { ref, nextTick } from "vue";
import { apiGet } from "@/api/client";
import { useChatStore } from "@/stores/chat";
import ModelSelector from "@/components/ModelSelector.vue";
import type { Note } from "@/types/note";
const emit = defineEmits<{
submit: [payload: { content: string; model: string; contextNoteId?: number }];
submit: [payload: { content: string; contextNoteId?: number }];
}>();
const store = useChatStore();
const selectedModel = ref(store.defaultModel || "");
const messageInput = ref("");
const inputEl = ref<HTMLTextAreaElement | null>(null);
@@ -23,20 +21,11 @@ const noteSearchResults = ref<{ id: number; title: string }[]>([]);
const noteSearchLoading = ref(false);
let noteSearchTimer: ReturnType<typeof setTimeout> | null = null;
// Sync default model when store updates
watch(
() => store.defaultModel,
(v) => {
if (!selectedModel.value && v) selectedModel.value = v;
}
);
function onSubmit() {
const content = messageInput.value.trim();
if (!content) return;
emit("submit", {
content,
model: selectedModel.value,
contextNoteId: attachedNote.value?.id,
});
messageInput.value = "";
@@ -127,8 +116,6 @@ function removeAttachedNote() {
</div>
<div class="chat-input-bar">
<ModelSelector v-model="selectedModel" :disabled="!store.chatReady" />
<div class="note-picker-wrapper">
<button
class="btn-attach"