Self-hosted, Docker-based agentic troubleshooting platform: FastAPI backend + LangGraph agent, Next.js UI, tiered LLM routing (local Ollama -> Gemini -> DeepSeek -> OpenRouter), MCP server manager, encrypted device credentials, RBAC, audit log, project-memory + Obsidian integrations, and editable troubleshooting decision rules tuned for the GeneseasX vessel stack. Co-authored-by: Cursor <cursoragent@cursor.com>
76 lines
2.2 KiB
TypeScript
76 lines
2.2 KiB
TypeScript
"use client";
|
|
|
|
import { createContext, useCallback, useContext, useEffect, useState } from "react";
|
|
|
|
type ToastKind = "success" | "error" | "info";
|
|
type Toast = { id: number; kind: ToastKind; message: string };
|
|
|
|
type ToastContextValue = {
|
|
toast: (message: string, kind?: ToastKind) => void;
|
|
success: (message: string) => void;
|
|
error: (message: string) => void;
|
|
};
|
|
|
|
const ToastContext = createContext<ToastContextValue | null>(null);
|
|
|
|
let _id = 0;
|
|
|
|
export function ToastProvider({ children }: { children: React.ReactNode }) {
|
|
const [toasts, setToasts] = useState<Toast[]>([]);
|
|
|
|
const remove = useCallback((id: number) => {
|
|
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
}, []);
|
|
|
|
const toast = useCallback(
|
|
(message: string, kind: ToastKind = "info") => {
|
|
const id = ++_id;
|
|
setToasts((prev) => [...prev, { id, kind, message }]);
|
|
setTimeout(() => remove(id), 4500);
|
|
},
|
|
[remove]
|
|
);
|
|
|
|
const value: ToastContextValue = {
|
|
toast,
|
|
success: (m) => toast(m, "success"),
|
|
error: (m) => toast(m, "error"),
|
|
};
|
|
|
|
return (
|
|
<ToastContext.Provider value={value}>
|
|
{children}
|
|
<div
|
|
className="pointer-events-none fixed inset-x-0 bottom-4 z-[100] flex flex-col items-center gap-2 px-4"
|
|
aria-live="polite"
|
|
role="status"
|
|
>
|
|
{toasts.map((t) => (
|
|
<button
|
|
key={t.id}
|
|
onClick={() => remove(t.id)}
|
|
className={`pointer-events-auto w-full max-w-md rounded-xl border px-4 py-3 text-left text-sm shadow-lg backdrop-blur transition ${
|
|
t.kind === "success"
|
|
? "border-good/40 bg-good/15 text-good"
|
|
: t.kind === "error"
|
|
? "border-bad/40 bg-bad/15 text-bad"
|
|
: "border-edge bg-panel/90 text-slate-200"
|
|
}`}
|
|
>
|
|
{t.message}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</ToastContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useToast(): ToastContextValue {
|
|
const ctx = useContext(ToastContext);
|
|
if (!ctx) {
|
|
// No-op fallback so components don't crash outside the provider.
|
|
return { toast: () => {}, success: () => {}, error: () => {} };
|
|
}
|
|
return ctx;
|
|
}
|