Initial commit: Agentic OS troubleshooting platform
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>
This commit is contained in:
75
frontend/components/toast.tsx
Normal file
75
frontend/components/toast.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
"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;
|
||||
}
|
||||
Reference in New Issue
Block a user