138 lines
4.1 KiB
Vue
138 lines
4.1 KiB
Vue
<script setup lang="ts">
|
|
import type { ProposalCard as ProposalCardType } from "@shop/shared/types";
|
|
import { computed, ref } from "vue";
|
|
import { api } from "../api";
|
|
import ProposalCard from "./ProposalCard.vue";
|
|
|
|
interface CardState {
|
|
card: ProposalCardType;
|
|
state: "pending" | "confirmed" | "rejected" | "executing" | "success" | "error";
|
|
result?: any;
|
|
error?: string;
|
|
}
|
|
|
|
const prompt = ref("");
|
|
const planning = ref(false);
|
|
const planError = ref("");
|
|
const cards = ref<CardState[]>([]);
|
|
|
|
async function plan() {
|
|
if (!prompt.value.trim()) return;
|
|
planning.value = true;
|
|
planError.value = "";
|
|
try {
|
|
const r = await api.post("/api/ai_admin/plan", { prompt: prompt.value });
|
|
cards.value = (r.data.cards || []).map((c: ProposalCardType) => ({ card: c, state: "pending" }));
|
|
if (!cards.value.length) planError.value = "Die KI konnte keinen Aktionsplan erzeugen.";
|
|
} catch (e: any) {
|
|
planError.value = e.response?.data?.detail || e.message;
|
|
} finally {
|
|
planning.value = false;
|
|
}
|
|
}
|
|
|
|
function updateArgs(i: number, args: Record<string, any>) {
|
|
cards.value[i].card.args = args;
|
|
// remove from 'missing' those that now have values
|
|
cards.value[i].card.missing = (cards.value[i].card.missing || []).filter(
|
|
(k) => args[k] === undefined || args[k] === null || args[k] === ""
|
|
);
|
|
}
|
|
|
|
async function execute(i: number) {
|
|
const cs = cards.value[i];
|
|
if (cs.card.missing.length) {
|
|
cs.state = "error";
|
|
cs.error = `Fehlende Felder: ${cs.card.missing.join(", ")}`;
|
|
return;
|
|
}
|
|
cs.state = "executing";
|
|
try {
|
|
const r = await api.post("/api/ai_admin/execute", {
|
|
cards: [{ tool: cs.card.tool, args: cs.card.args }],
|
|
});
|
|
const res = r.data.results[0];
|
|
if (res.ok) {
|
|
cs.state = "success";
|
|
cs.result = res.result;
|
|
} else {
|
|
cs.state = "error";
|
|
cs.error = res.error;
|
|
}
|
|
} catch (e: any) {
|
|
cs.state = "error";
|
|
cs.error = e.response?.data?.detail || e.message;
|
|
}
|
|
}
|
|
|
|
function reject(i: number) {
|
|
cards.value[i].state = "rejected";
|
|
}
|
|
|
|
async function confirmAll() {
|
|
for (let i = 0; i < cards.value.length; i++) {
|
|
if (cards.value[i].state === "pending") {
|
|
await execute(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
function reset() {
|
|
cards.value = [];
|
|
prompt.value = "";
|
|
planError.value = "";
|
|
}
|
|
|
|
const pendingCount = computed(() => cards.value.filter((c) => c.state === "pending").length);
|
|
</script>
|
|
|
|
<template>
|
|
<div class="card">
|
|
<h2 class="text-lg font-semibold mb-2">🤖 KI-Assistent</h2>
|
|
<p class="text-sm text-gray-600 mb-3">
|
|
Sag, was getan werden soll. Die KI erzeugt nur Vorschläge — ausgeführt wird erst nach
|
|
deiner Bestätigung. Du kannst auch JSON-Daten reinwerfen.
|
|
</p>
|
|
<div class="flex gap-2">
|
|
<textarea
|
|
v-model="prompt"
|
|
rows="3"
|
|
class="input font-mono text-sm"
|
|
placeholder="z.B. 'setze den Shopnamen auf TEST123' oder [{sku:'NEW-1',...}] erstelle diese"
|
|
/>
|
|
<div class="flex flex-col gap-2">
|
|
<button @click="plan" :disabled="planning" class="btn-primary">
|
|
{{ planning ? "Plane..." : "Planen" }}
|
|
</button>
|
|
<button v-if="cards.length" @click="reset" class="btn-secondary">Reset</button>
|
|
</div>
|
|
</div>
|
|
<div v-if="planning" class="text-xs text-gray-500 mt-2">
|
|
Lokales LLM rechnet. Bei Bulk-Operationen über viele Items kann das ein paar Minuten dauern.
|
|
</div>
|
|
<div v-if="planError" class="text-red-600 text-sm mt-2">{{ planError }}</div>
|
|
|
|
<div v-if="cards.length" class="mt-4">
|
|
<div class="flex items-center justify-between mb-3">
|
|
<div class="font-medium">{{ cards.length }} Vorschlag(e)</div>
|
|
<button v-if="pendingCount > 1" @click="confirmAll" class="btn-success text-sm">
|
|
Alle bestätigen ({{ pendingCount }})
|
|
</button>
|
|
</div>
|
|
<div class="space-y-3">
|
|
<ProposalCard
|
|
v-for="(cs, i) in cards"
|
|
:key="i"
|
|
:card="cs.card"
|
|
:state="cs.state"
|
|
:result="cs.result"
|
|
:error="cs.error"
|
|
@update:args="(a) => updateArgs(i, a)"
|
|
@confirm="execute(i)"
|
|
@reject="reject(i)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|