Fix queued bubbles, queue persistence, duplicate confirm UX, semantic check
- Queued message bubbles now match user bubble style (primary colour, right-aligned, opacity 0.45) in both ChatView and WorkspaceView - Queue persisted to localStorage (fa_conv_queue_<id>); survives page refresh, restored on fetchConversation, cleared on delete - ToolCallCard confirm/deny: buttons now inline in header row; "Create anyway" calls POST /api/notes or /api/tasks directly (no LLM round-trip); shows "✓ Created" / "Skipped" state; removed chatStore dependency - POST /api/notes and POST /api/tasks now accept project (name string) and resolve to project_id server-side, matching tools.py behaviour - Remove semantic similarity duplicate check from create_note and create_task — 0.87 threshold was too aggressive for topically-related notes; title-based exact/fuzzy checks are sufficient Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,16 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { apiPost } from "@/api/client";
|
||||
import { useChatStore } from "@/stores/chat";
|
||||
import type { ToolCallRecord } from "@/types/chat";
|
||||
|
||||
const props = defineProps<{
|
||||
toolCall: ToolCallRecord;
|
||||
}>();
|
||||
|
||||
const chatStore = useChatStore();
|
||||
const appliedTags = ref<Set<string>>(new Set());
|
||||
const applyingTag = ref<string | null>(null);
|
||||
const confirmState = ref<"idle" | "creating" | "created" | "denied">("idle");
|
||||
|
||||
const label = computed(() => {
|
||||
if (props.toolCall.status === "declined") return "Declined";
|
||||
@@ -213,12 +212,31 @@ function toggle() {
|
||||
if (hasDetail.value) collapsed.value = !collapsed.value;
|
||||
}
|
||||
|
||||
function confirmDuplicate() {
|
||||
chatStore.sendMessage("Yes, please create it.", null, undefined, true);
|
||||
async function confirmDuplicate() {
|
||||
if (confirmState.value !== "idle") return;
|
||||
confirmState.value = "creating";
|
||||
const args = props.toolCall.arguments as Record<string, unknown>;
|
||||
const isTask = props.toolCall.function === "create_task";
|
||||
const endpoint = isTask ? "/api/tasks" : "/api/notes";
|
||||
const payload: Record<string, unknown> = {
|
||||
title: args.title,
|
||||
body: args.body ?? "",
|
||||
tags: args.tags ?? [],
|
||||
...(args.due_date ? { due_date: args.due_date } : {}),
|
||||
...(args.priority ? { priority: args.priority } : {}),
|
||||
...(args.project ? { project: args.project } : {}),
|
||||
...(isTask ? { status: args.status ?? "todo" } : {}),
|
||||
};
|
||||
try {
|
||||
await apiPost(endpoint, payload);
|
||||
confirmState.value = "created";
|
||||
} catch {
|
||||
confirmState.value = "idle";
|
||||
}
|
||||
}
|
||||
|
||||
function denyDuplicate() {
|
||||
chatStore.sendMessage("No, skip that creation.", null, undefined, true);
|
||||
confirmState.value = "denied";
|
||||
}
|
||||
|
||||
async function applyTag(tag: string) {
|
||||
@@ -264,6 +282,19 @@ async function applyTag(tag: string) {
|
||||
@click.stop
|
||||
>{{ toolCall.result.similar_note.title }}</router-link>
|
||||
<span v-else class="tool-error">{{ toolCall.result.error }}</span>
|
||||
<template v-if="confirmState === 'created'">
|
||||
<span class="confirm-created">✓ Created</span>
|
||||
</template>
|
||||
<template v-else-if="confirmState === 'denied'">
|
||||
<span class="confirm-denied">Skipped</span>
|
||||
</template>
|
||||
<template v-else-if="confirmState !== 'creating'">
|
||||
<button class="btn-confirm-duplicate" @click.stop="confirmDuplicate">Create anyway</button>
|
||||
<button class="btn-deny-duplicate" @click.stop="denyDuplicate">Skip</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="tool-summary-count">Creating…</span>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else-if="toolCall.status === 'error'">
|
||||
<span class="tool-error">{{ toolCall.result.error }}</span>
|
||||
@@ -326,12 +357,6 @@ async function applyTag(tag: string) {
|
||||
<span v-if="hasDetail" class="tool-chevron" :class="{ open: !collapsed }">▶</span>
|
||||
</div>
|
||||
|
||||
<!-- Duplicate confirmation actions -->
|
||||
<div v-if="toolCall.result.requires_confirmation" class="duplicate-confirm-actions">
|
||||
<button class="btn-confirm-duplicate" @click="confirmDuplicate">Create Anyway</button>
|
||||
<button class="btn-deny-duplicate" @click="denyDuplicate">Don't Create</button>
|
||||
</div>
|
||||
|
||||
<!-- ── Detail section (expandable) ─────────────────────────── -->
|
||||
<div v-if="hasDetail" v-show="!collapsed" class="tool-card-detail">
|
||||
<template v-if="noteContent && noteContent.tags.length">
|
||||
@@ -609,32 +634,38 @@ async function applyTag(tag: string) {
|
||||
.tool-call-card.requires-confirm {
|
||||
border-color: color-mix(in srgb, var(--color-warning, #f59e0b) 60%, transparent);
|
||||
}
|
||||
.duplicate-confirm-actions {
|
||||
display: flex;
|
||||
gap: 0.4rem;
|
||||
padding: 0.4rem 0.6rem;
|
||||
border-top: 1px solid var(--color-border);
|
||||
.confirm-created {
|
||||
color: var(--color-success, #2ecc71);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.confirm-denied {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.78rem;
|
||||
font-style: italic;
|
||||
}
|
||||
.btn-confirm-duplicate {
|
||||
padding: 0.25rem 0.7rem;
|
||||
padding: 0.15rem 0.5rem;
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: var(--radius-sm, 6px);
|
||||
cursor: pointer;
|
||||
font-size: 0.78rem;
|
||||
font-size: 0.72rem;
|
||||
font-family: inherit;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btn-confirm-duplicate:hover { opacity: 0.9; }
|
||||
.btn-deny-duplicate {
|
||||
padding: 0.25rem 0.7rem;
|
||||
padding: 0.15rem 0.5rem;
|
||||
background: none;
|
||||
color: var(--color-text-muted);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm, 6px);
|
||||
cursor: pointer;
|
||||
font-size: 0.78rem;
|
||||
font-size: 0.72rem;
|
||||
font-family: inherit;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btn-deny-duplicate:hover {
|
||||
color: var(--color-danger, #e74c3c);
|
||||
|
||||
Reference in New Issue
Block a user