diff --git a/README.md b/README.md index 52cae42..c962ce1 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,8 @@ Analyzes your sessions from **Claude Code, Cursor, Codex CLI, Copilot CLI, and V > If you have [Ollama](https://ollama.com) installed, `code-insights` will detect it automatically and use it for AI analysis. No account, no cost, no data leaves your machine. > > ```bash -> ollama pull llama3.3 # recommended -> code-insights sync # Ollama detected automatically +> ollama pull llama3.3 # recommended +> npx @code-insights/cli # Ollama detected automatically > ``` --- @@ -156,7 +156,7 @@ Session files (Claude Code, Cursor, Codex CLI, Copilot CLI, VS Code Copilot Chat ▼ ┌──────────────────┐ │ LLM Providers │ analysis, facets, - │ (your API key) │ reflect, export + │(API key or Ollama)│ reflect, export └──────────────────┘ ``` diff --git a/cli/src/utils/ollama-detect.ts b/cli/src/utils/ollama-detect.ts index 11ed94a..d9a5559 100644 --- a/cli/src/utils/ollama-detect.ts +++ b/cli/src/utils/ollama-detect.ts @@ -85,7 +85,8 @@ export async function autoDetectOllama(): Promise { saveConfig(updatedConfig); console.log( - chalk.green(`\n Ollama detected — using ${chalk.bold(model)} for AI analysis (free & local).`) + - chalk.dim('\n Run `code-insights config llm` to change.\n'), + chalk.green(`\n Ollama detected — configured ${chalk.bold(model)} for AI analysis (free & local).`) + + chalk.dim('\n Open the dashboard and click "Analyze" on any session to get insights.') + + chalk.dim('\n Run `code-insights config llm` to change provider.\n'), ); } diff --git a/dashboard/src/components/LlmNudgeBanner.tsx b/dashboard/src/components/LlmNudgeBanner.tsx new file mode 100644 index 0000000..8a959fd --- /dev/null +++ b/dashboard/src/components/LlmNudgeBanner.tsx @@ -0,0 +1,93 @@ +import { useState } from 'react'; +import { Link } from 'react-router'; +import { X, Sparkles } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { useLlmConfig } from '@/hooks/useConfig'; + +interface LlmNudgeBannerProps { + context: 'insights' | 'patterns'; +} + +const COPY: Record = { + insights: { + title: 'Get AI-powered insights', + description: + 'Configure a provider to extract decisions, learnings, and patterns from your sessions.', + }, + patterns: { + title: 'Enable cross-session pattern detection', + description: + 'An AI provider is required to generate weekly friction and pattern analysis.', + }, +}; + +function localStorageKey(context: LlmNudgeBannerProps['context']): string { + return `code-insights:llm-nudge-dismissed-${context}`; +} + +export function LlmNudgeBanner({ context }: LlmNudgeBannerProps) { + const { data: llmConfig, isLoading: configLoading } = useLlmConfig(); + const [dismissed, setDismissed] = useState(() => { + try { + return localStorage.getItem(localStorageKey(context)) === 'true'; + } catch { + return false; + } + }); + + // Don't render until config has resolved (prevents flash) + if (configLoading) return null; + + // Don't show if LLM is already configured + if (llmConfig?.provider) return null; + + // Don't show if user dismissed + if (dismissed) return null; + + function handleDismiss() { + try { + localStorage.setItem(localStorageKey(context), 'true'); + } catch { /* ignore storage errors */ } + setDismissed(true); + } + + const { title, description } = COPY[context]; + + return ( +
+ +
+

{title}

+

+ {description}{' '} + + Install{' '} + + Ollama + {' '} + for free, local analysis — or configure any provider in Settings. + +

+
+
+ + +
+
+ ); +} diff --git a/dashboard/src/pages/InsightsPage.tsx b/dashboard/src/pages/InsightsPage.tsx index 9ddbf63..9742942 100644 --- a/dashboard/src/pages/InsightsPage.tsx +++ b/dashboard/src/pages/InsightsPage.tsx @@ -30,6 +30,7 @@ import { InsightTypePills } from '@/components/filters/InsightTypePills'; import { SaveFilterPopover } from '@/components/filters/SaveFilterPopover'; import { SavedFiltersDropdown } from '@/components/filters/SavedFiltersDropdown'; import { useSavedFilters } from '@/hooks/useSavedFilters'; +import { LlmNudgeBanner } from '@/components/LlmNudgeBanner'; const INSIGHT_TYPES: InsightType[] = ['summary', 'decision', 'learning', 'technique', 'prompt_quality']; @@ -270,6 +271,7 @@ export default function InsightsPage() { {/* Scrollable content */}
+ {isError && !isLoading ? ( ) : isLoading ? ( diff --git a/dashboard/src/pages/PatternsPage.tsx b/dashboard/src/pages/PatternsPage.tsx index 040d0c6..66c4515 100644 --- a/dashboard/src/pages/PatternsPage.tsx +++ b/dashboard/src/pages/PatternsPage.tsx @@ -20,6 +20,7 @@ import { frictionBarColor, getDominantDriver } from '@/lib/constants/patterns'; import { AlertTriangle, Sparkles, Shield, Brain, Copy, Check, Loader2, } from 'lucide-react'; +import { LlmNudgeBanner } from '@/components/LlmNudgeBanner'; export default function PatternsPage() { const [currentWeek, setCurrentWeek] = useState(() => getCurrentIsoWeek()); @@ -227,6 +228,7 @@ export default function PatternsPage() { return (
+ {/* Header */}