wahnsinn vibe
This commit is contained in:
137
frontend/admin/src/components/AIChatBox.vue
Normal file
137
frontend/admin/src/components/AIChatBox.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user