Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
> ```

---
Expand Down Expand Up @@ -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
└──────────────────┘
```

Expand Down
5 changes: 3 additions & 2 deletions cli/src/utils/ollama-detect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ export async function autoDetectOllama(): Promise<void> {
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'),
);
}
93 changes: 93 additions & 0 deletions dashboard/src/components/LlmNudgeBanner.tsx
Original file line number Diff line number Diff line change
@@ -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<LlmNudgeBannerProps['context'], { title: string; description: string }> = {
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<boolean>(() => {
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 (
<div role="status" className="flex items-start gap-3 rounded-lg border bg-muted/40 px-4 py-3 text-sm">
<Sparkles className="h-4 w-4 mt-0.5 shrink-0 text-muted-foreground" />
<div className="flex-1 min-w-0">
<p className="font-medium">{title}</p>
<p className="text-muted-foreground mt-0.5">
{description}{' '}
<span className="text-muted-foreground">
Install{' '}
<a
href="https://ollama.com"
target="_blank"
rel="noopener noreferrer"
className="underline underline-offset-2 hover:text-foreground transition-colors"
>
Ollama
</a>{' '}
for free, local analysis — or configure any provider in Settings.
</span>
</p>
</div>
<div className="flex items-center gap-2 shrink-0 ml-2">
<Button variant="outline" size="sm" className="h-7 text-xs" asChild>
<Link to="/settings">Configure AI Provider</Link>
</Button>
<Button
variant="ghost"
size="icon"
className="h-7 w-7"
onClick={handleDismiss}
aria-label="Dismiss"
>
<X className="h-3.5 w-3.5" />
</Button>
</div>
</div>
);
}
2 changes: 2 additions & 0 deletions dashboard/src/pages/InsightsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'];

Expand Down Expand Up @@ -270,6 +271,7 @@ export default function InsightsPage() {

{/* Scrollable content */}
<div className="flex-1 overflow-y-auto px-6 py-4 space-y-4">
<LlmNudgeBanner context="insights" />
{isError && !isLoading ? (
<ErrorCard message="Failed to load insights" onRetry={refetch} />
) : isLoading ? (
Expand Down
2 changes: 2 additions & 0 deletions dashboard/src/pages/PatternsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>(() => getCurrentIsoWeek());
Expand Down Expand Up @@ -227,6 +228,7 @@ export default function PatternsPage() {

return (
<div className="space-y-4 p-4 lg:p-6">
<LlmNudgeBanner context="patterns" />
{/* Header */}
<div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
<div>
Expand Down
Loading