Files
homelab/integrations/nextjs/index.ts
T

115 lines
5.1 KiB
TypeScript
Raw Normal View History

/**
* NextJS Integration for Homelab LLM & Grafana
*
* ═══════════════════════════════════════════════════════════════════════════════
* SETUP
* ═══════════════════════════════════════════════════════════════════════════════
*
* 1. Copy files to your NextJS project:
* cp integrations/nextjs/*.ts your-nextjs-app/lib/
*
* 2. Environment variables (.env.local):
* LLM_BASE_URL=https://api.riotpiao.com/v1
* GRAFANA_URL=https://grafana.riotpiao.com
* GRAFANA_API_TOKEN=<service-account-token>
* NEXT_PUBLIC_GRAFANA_URL=https://grafana.riotpiao.com
*
* 3. Create Grafana service account (for API access):
* - Grafana UI > Administration > Service Accounts
* - Create account with Viewer role
* - Generate token, save to GRAFANA_API_TOKEN
*
* 4. Copy API routes (see api-routes.ts for templates):
* - app/api/chat/route.ts (LLM chat endpoint)
* - app/api/metrics/route.ts (Grafana metrics proxy)
*
* ═══════════════════════════════════════════════════════════════════════════════
* USAGE EXAMPLES
* ═══════════════════════════════════════════════════════════════════════════════
*
* Server-side (API routes, server components):
*
* import { chat, streamChat } from '@/lib/llm-client';
* import { getLLMMetrics, getDashboard } from '@/lib/grafana-client';
*
* // Non-streaming chat
* const response = await chat({
* model: 'reasoning',
* messages: [{ role: 'user', content: 'Hello' }],
* });
*
* // Streaming chat
* for await (const chunk of streamChat({ model: 'reasoning', messages })) {
* process.stdout.write(chunk);
* }
*
* // Get LLM pod metrics
* const metrics = await getLLMMetrics();
* console.log(`Pods ready: ${metrics.podsReady}`);
*
* Client-side (React components):
*
* import { useChat, useLLMMetrics, useGrafanaEmbed } from '@/lib/hooks';
*
* function ChatUI() {
* const { messages, send, isLoading } = useChat({ model: 'reasoning' });
*
* return (
* <div>
* {messages.map((m, i) => <p key={i}>{m.role}: {m.content}</p>)}
* <button onClick={() => send('Hello')} disabled={isLoading}>
* Send
* </button>
* </div>
* );
* }
*
* function MetricsDashboard() {
* const { metrics, isLoading } = useLLMMetrics(30000);
* const { iframeProps } = useGrafanaEmbed('llm-frontend', 2);
*
* if (isLoading) return <p>Loading...</p>;
*
* return (
* <div>
* <p>Pods ready: {metrics?.podsReady}</p>
* {iframeProps && <iframe {...iframeProps} />}
* </div>
* );
* }
*
* ═══════════════════════════════════════════════════════════════════════════════
* AVAILABLE MODELS
* ═══════════════════════════════════════════════════════════════════════════════
*
* | model | Engine | Notes |
* |--------------------|---------|--------------------------------|
* | reasoning | vLLM | Qwen3-32B, best for complex |
* | ornith:35b | Ollama | General purpose |
* | qwen2.5:3b-instruct| Ollama | Fast, smaller tasks |
*
* ═══════════════════════════════════════════════════════════════════════════════
* GRAFANA EMBEDDING (optional, requires config change)
* ═══════════════════════════════════════════════════════════════════════════════
*
* To enable iframe embedding, add to grafana.ini:
* security:
* allow_embedding: true
*
* Or via Helm values (k8s/infra/logging/grafana-values.yaml):
* grafana.ini:
* security:
* allow_embedding: true
*
* Panel embed URLs follow pattern:
* https://grafana.riotpiao.com/d-solo/llm-frontend?panelId=2&from=now-6h&to=now
*
* Dashboard UID for LLM: llm-frontend
* Panel IDs: 2 (pods ready), 11 (CPU), 12 (Memory), 13 (Restarts)
*/
// Re-export all modules
export * from './llm-client';
export * from './grafana-client';
export * from './hooks';