spine-framework-cortex 0.1.7 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE.md +223 -0
  2. package/README.md +185 -13
  3. package/custom/apps/cortex/LICENSE.md +13 -0
  4. package/custom/apps/cortex/README.md +27 -0
  5. package/custom/apps/cortex/api/cortex-handler.ts +35 -0
  6. package/{components → custom/apps/cortex/components}/CortexSidebar.tsx +27 -22
  7. package/custom/apps/cortex/docs/configuration.md +222 -0
  8. package/{index.tsx → custom/apps/cortex/index.tsx} +14 -5
  9. package/custom/apps/cortex/kb-ingestion.tsx +217 -0
  10. package/{manifest.json → custom/apps/cortex/manifest.json} +12 -1
  11. package/custom/apps/cortex/package.json +31 -0
  12. package/{pages → custom/apps/cortex/pages}/crm/AccountDetailPage.tsx +6 -6
  13. package/package.json +82 -20
  14. /package/{functions → custom/apps/cortex/functions}/custom_anonymous-sessions.ts +0 -0
  15. /package/{functions → custom/apps/cortex/functions}/custom_case_analysis.ts +0 -0
  16. /package/{functions → custom/apps/cortex/functions}/custom_community-escalation.ts +0 -0
  17. /package/{functions → custom/apps/cortex/functions}/custom_cortex-chunks.ts +0 -0
  18. /package/{functions → custom/apps/cortex/functions}/custom_cortex-handler.ts +0 -0
  19. /package/{functions → custom/apps/cortex/functions}/custom_funnel-scoring.ts +0 -0
  20. /package/{functions → custom/apps/cortex/functions}/custom_funnel-signal.ts +0 -0
  21. /package/{functions → custom/apps/cortex/functions}/custom_funnel-timers.ts +0 -0
  22. /package/{functions → custom/apps/cortex/functions}/custom_kb-chunker-test.ts +0 -0
  23. /package/{functions → custom/apps/cortex/functions}/custom_kb-chunker.ts +0 -0
  24. /package/{functions → custom/apps/cortex/functions}/custom_kb-embeddings.ts +0 -0
  25. /package/{functions → custom/apps/cortex/functions}/custom_kb-ingestion.ts +0 -0
  26. /package/{functions → custom/apps/cortex/functions}/custom_support-triage.ts +0 -0
  27. /package/{functions → custom/apps/cortex/functions}/custom_tag_management.ts +0 -0
  28. /package/{functions → custom/apps/cortex/functions}/webhook-handlers.ts +0 -0
  29. /package/{lib → custom/apps/cortex/lib}/resolveTypeId.ts +0 -0
  30. /package/{pages → custom/apps/cortex/pages}/CortexDashboard.tsx +0 -0
  31. /package/{pages → custom/apps/cortex/pages}/community/CommunityPage.tsx +0 -0
  32. /package/{pages → custom/apps/cortex/pages}/courses/CoursesPage.tsx +0 -0
  33. /package/{pages → custom/apps/cortex/pages}/crm/AccountsPage.tsx +0 -0
  34. /package/{pages → custom/apps/cortex/pages}/crm/ActivityPage.tsx +0 -0
  35. /package/{pages → custom/apps/cortex/pages}/crm/ContactDetailPage.tsx +0 -0
  36. /package/{pages → custom/apps/cortex/pages}/crm/ContactsPage.tsx +0 -0
  37. /package/{pages → custom/apps/cortex/pages}/crm/DealDetailPage.tsx +0 -0
  38. /package/{pages → custom/apps/cortex/pages}/crm/DealsPage.tsx +0 -0
  39. /package/{pages → custom/apps/cortex/pages}/crm/HealthPage.tsx +0 -0
  40. /package/{pages → custom/apps/cortex/pages}/intelligence/IntelligencePage.tsx +0 -0
  41. /package/{pages → custom/apps/cortex/pages}/kb/KBEditorPage.tsx +0 -0
  42. /package/{pages → custom/apps/cortex/pages}/kb/KBIngestionPage.tsx +0 -0
  43. /package/{pages → custom/apps/cortex/pages}/kb/KBPage.tsx +0 -0
  44. /package/{pages → custom/apps/cortex/pages}/support/RedactionReview.tsx +0 -0
  45. /package/{pages → custom/apps/cortex/pages}/support/SupportPage.tsx +0 -0
  46. /package/{pages → custom/apps/cortex/pages}/support/TicketDetailPage.tsx +0 -0
  47. /package/{seed → custom/apps/cortex/seed}/accounts.json +0 -0
  48. /package/{seed → custom/apps/cortex/seed}/link-types.json +0 -0
  49. /package/{seed → custom/apps/cortex/seed}/pipelines.json +0 -0
  50. /package/{seed → custom/apps/cortex/seed}/roles.json +0 -0
  51. /package/{seed → custom/apps/cortex/seed}/triggers.json +0 -0
  52. /package/{seed → custom/apps/cortex/seed}/types.json +0 -0
@@ -0,0 +1,222 @@
1
+ # Cortex App Configuration
2
+
3
+ This guide explains how to configure and deploy the Cortex app for customer support, CRM, and community management.
4
+
5
+ ## Database Configuration
6
+
7
+ ### 1. Apps Table Entry
8
+
9
+ Insert the Cortex app into the `public.apps` table:
10
+
11
+ ```sql
12
+ INSERT INTO public.apps (
13
+ id,
14
+ slug,
15
+ name,
16
+ description,
17
+ version,
18
+ app_type,
19
+ source,
20
+ owner_account_id,
21
+ is_active,
22
+ is_system,
23
+ min_role,
24
+ config,
25
+ nav_items,
26
+ route_prefix,
27
+ renderer,
28
+ created_at
29
+ ) VALUES (
30
+ gen_random_uuid(),
31
+ 'cortex',
32
+ 'Cortex',
33
+ 'Unified workspace for CRM, Support, Community, and Knowledge Base',
34
+ '1.0.0',
35
+ 'custom',
36
+ 'spine-framework',
37
+ 'd3ab4cf8-33de-4ca5-97a2-dbc288c94338', -- spine-system account
38
+ true,
39
+ false,
40
+ 'support',
41
+ '{}',
42
+ '[
43
+ {
44
+ "title": "Dashboard",
45
+ "path": "/dashboard",
46
+ "icon": "LayoutDashboard",
47
+ "order": 1
48
+ },
49
+ {
50
+ "title": "CRM",
51
+ "path": "/crm",
52
+ "icon": "Users",
53
+ "order": 2,
54
+ "children": [
55
+ {"title": "Accounts", "path": "/crm/accounts"},
56
+ {"title": "Contacts", "path": "/crm/contacts"},
57
+ {"title": "Deals", "path": "/crm/deals"},
58
+ {"title": "Health", "path": "/crm/health"},
59
+ {"title": "Activity", "path": "/crm/activity"}
60
+ ]
61
+ },
62
+ {
63
+ "title": "Support",
64
+ "path": "/support",
65
+ "icon": "Headphones",
66
+ "order": 3
67
+ },
68
+ {
69
+ "title": "Community",
70
+ "path": "/community",
71
+ "icon": "Users",
72
+ "order": 4
73
+ },
74
+ {
75
+ "title": "Knowledge Base",
76
+ "path": "/kb",
77
+ "icon": "BookOpen",
78
+ "order": 5
79
+ },
80
+ {
81
+ "title": "Courses",
82
+ "path": "/courses",
83
+ "icon": "GraduationCap",
84
+ "order": 6
85
+ },
86
+ {
87
+ "title": "Intelligence",
88
+ "path": "/intelligence",
89
+ "icon": "Brain",
90
+ "order": 7
91
+ }
92
+ ]',
93
+ '/cortex', -- Change to '/' for root serving
94
+ 'custom',
95
+ now()
96
+ );
97
+ ```
98
+
99
+ ### 2. App Installation
100
+
101
+ Install Cortex for your account:
102
+
103
+ ```sql
104
+ INSERT INTO public.app_installations (
105
+ account_id,
106
+ app_slug,
107
+ is_enabled
108
+ ) VALUES (
109
+ 'your-account-id', -- Replace with your account ID
110
+ 'cortex',
111
+ true
112
+ );
113
+ ```
114
+
115
+ ### 3. Required Roles
116
+
117
+ Cortex requires users to have the `support` role. Create this role if it doesn't exist:
118
+
119
+ ```sql
120
+ INSERT INTO public.roles (
121
+ slug,
122
+ name,
123
+ description,
124
+ is_system,
125
+ is_active
126
+ ) VALUES (
127
+ 'support',
128
+ 'Support Agent',
129
+ 'Can access Cortex support features',
130
+ false,
131
+ true
132
+ ) ON CONFLICT (slug) DO NOTHING;
133
+ ```
134
+
135
+ Assign the support role to users who need Cortex access:
136
+
137
+ ```sql
138
+ INSERT INTO public.people (
139
+ account_id,
140
+ user_id,
141
+ role_id,
142
+ is_active
143
+ ) VALUES (
144
+ 'your-account-id',
145
+ 'user-id',
146
+ (SELECT id FROM public.roles WHERE slug = 'support'),
147
+ true
148
+ );
149
+ ```
150
+
151
+ ## Manifest Configuration
152
+
153
+ The `manifest.json` file controls app behavior and routing:
154
+
155
+ ### Key Settings
156
+
157
+ - **`required_roles`**: `["support"]` - Users must have support role
158
+ - **`routes`**: Define all available routes in the app
159
+ - **`nav_items`**: Navigation structure and icons
160
+ - **`route_prefix`**: Where the app is served from
161
+
162
+ ### Route Prefix Configuration
163
+
164
+ #### Subdirectory Serving (Default)
165
+ ```json
166
+ {
167
+ "route_prefix": "/cortex"
168
+ }
169
+ ```
170
+ - App accessible at: `http://domain.com/cortex`
171
+ - Safe for multi-app deployments
172
+ - Default configuration
173
+
174
+ #### Root Serving
175
+ ```json
176
+ {
177
+ "route_prefix": "/"
178
+ }
179
+ ```
180
+ - App accessible at: `http://domain.com/`
181
+ - Use for single-app deployments
182
+ - Requires updating database `route_prefix` field
183
+
184
+ ### Prefix-Aware Routing
185
+
186
+ Cortex uses prefix-aware routing that automatically adapts to the `route_prefix`:
187
+
188
+ - Navigation links automatically include the base path
189
+ - Route definitions work regardless of serving location
190
+ - Breadcrumbs and redirects are prefix-aware
191
+
192
+ No code changes needed when switching between subdirectory and root serving.
193
+
194
+ ## Deployment Options
195
+
196
+ ### Option 1: Subdirectory Deployment (Recommended)
197
+
198
+ 1. Set `route_prefix: "/cortex"` in database
199
+ 2. App available at `/cortex/*` URLs
200
+ 3. Multiple apps can coexist safely
201
+
202
+ ### Option 2: Root Deployment
203
+
204
+ 1. Set `route_prefix: "/"` in database
205
+ 2. App available at root URLs (`/`, `/dashboard`, etc.)
206
+ 3. Use for dedicated Cortex deployments
207
+
208
+ ## Verification
209
+
210
+ Test the configuration:
211
+
212
+ 1. Check `/api/apps?action=list` returns Cortex
213
+ 2. Navigate to the configured route prefix
214
+ 3. Verify navigation works correctly
215
+ 4. Confirm all pages load without errors
216
+
217
+ ## Troubleshooting
218
+
219
+ - **404 errors**: Check `route_prefix` matches database entry
220
+ - **Access denied**: Verify user has `support` role
221
+ - **Navigation broken**: Ensure prefix-aware routing is enabled
222
+ - **Missing pages**: Check all route components exist and export defaults
@@ -4,6 +4,7 @@ import { LoadingSpinner } from '@core/components/ui/LoadingSpinner'
4
4
  import { AppShell } from '@core/components/layout/AppShell'
5
5
  import { CortexSidebar } from './components/CortexSidebar'
6
6
  import { TooltipProvider } from '@core/components/ui/tooltip'
7
+ import { useCurrentApp } from '@core/contexts/AppContext'
7
8
 
8
9
  const CortexDashboard = lazy(() => import('./pages/CortexDashboard'))
9
10
 
@@ -40,13 +41,21 @@ const Fallback = <div className="min-h-[400px] flex items-center justify-center"
40
41
 
41
42
  function CortexLayout() {
42
43
  const location = useLocation()
44
+ const app = useCurrentApp()
45
+
46
+ // Normalize route_prefix: '/' → '' so paths are /dashboard, /crm/accounts, etc.
47
+ const base = app.route_prefix === '/' ? '' : (app.route_prefix || '')
48
+ const prefixDepth = base.split('/').filter(Boolean).length
49
+
43
50
  const segments = location.pathname.split('/').filter(Boolean)
44
- const breadcrumbs: { title: string; url?: string }[] = [{ title: 'Cortex', url: '/cortex/dashboard' }]
45
- if (segments[1] && segments[1] !== 'dashboard') {
46
- breadcrumbs.push({ title: segments[1].charAt(0).toUpperCase() + segments[1].slice(1) })
51
+ const appSegments = segments.slice(prefixDepth) // strips the prefix segments
52
+
53
+ const breadcrumbs: { title: string; url?: string }[] = [{ title: 'Cortex', url: `${base}/dashboard` }]
54
+ if (appSegments[0] && appSegments[0] !== 'dashboard') {
55
+ breadcrumbs.push({ title: appSegments[0].charAt(0).toUpperCase() + appSegments[0].slice(1) })
47
56
  }
48
- if (segments[2]) {
49
- breadcrumbs.push({ title: segments[2].charAt(0).toUpperCase() + segments[2].slice(1) })
57
+ if (appSegments[1]) {
58
+ breadcrumbs.push({ title: appSegments[1].charAt(0).toUpperCase() + appSegments[1].slice(1) })
50
59
  }
51
60
 
52
61
  return (
@@ -0,0 +1,217 @@
1
+ import React, { useState } from 'react'
2
+ import { useApi } from '../../../v2-core/src/hooks/useApi'
3
+ import { Button } from '../../../v2-core/src/components/ui/button'
4
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../../v2-core/src/components/ui/card'
5
+ import { Progress } from '../../../v2-core/src/components/ui/progress'
6
+ import { Alert, AlertDescription } from '../../../v2-core/src/components/ui/alert'
7
+ import { Badge } from '../../../v2-core/src/components/ui/badge'
8
+
9
+ interface IngestionResponse {
10
+ success: boolean
11
+ items_created: number
12
+ items_updated: number
13
+ embeddings_generated: number
14
+ errors: string[]
15
+ skipped: string[]
16
+ }
17
+
18
+ interface IngestionStats {
19
+ total: number
20
+ processed: number
21
+ created: number
22
+ updated: number
23
+ errors: number
24
+ }
25
+
26
+ export default function KBIngestion() {
27
+ const [isIngesting, setIsIngesting] = useState(false)
28
+ const [stats, setStats] = useState<IngestionStats | null>(null)
29
+ const [error, setError] = useState<string | null>(null)
30
+ const [logs, setLogs] = useState<string[]>([])
31
+ const apiFetch = useApi()
32
+
33
+ const addLog = (message: string) => {
34
+ const timestamp = new Date().toLocaleTimeString()
35
+ setLogs(prev => [...prev, `[${timestamp}] ${message}`])
36
+ }
37
+
38
+ const loadChunks = async (): Promise<any[]> => {
39
+ try {
40
+ const response = await fetch('/chunks.json')
41
+ if (!response.ok) {
42
+ throw new Error('Failed to load chunks file')
43
+ }
44
+ const data = await response.json()
45
+ return data.chunks || []
46
+ } catch (err) {
47
+ throw new Error(`Could not load chunks: ${err instanceof Error ? err.message : 'Unknown error'}`)
48
+ }
49
+ }
50
+
51
+ const ingestBatch = async (chunks: any[], batchSize = 10): Promise<IngestionResponse> => {
52
+ const response = await apiFetch('/.netlify/functions/custom_kb-ingestion', {
53
+ method: 'POST',
54
+ headers: {
55
+ 'Content-Type': 'application/json',
56
+ },
57
+ body: JSON.stringify({
58
+ chunks,
59
+ force_update: false
60
+ })
61
+ })
62
+
63
+ if (!response.ok) {
64
+ const errorData = await response.json().catch(() => ({ error: 'Unknown error' }))
65
+ throw new Error(`Batch failed: ${errorData.error || response.statusText}`)
66
+ }
67
+
68
+ return await response.json()
69
+ }
70
+
71
+ const startIngestion = async () => {
72
+ setIsIngesting(true)
73
+ setError(null)
74
+ setStats(null)
75
+ setLogs([])
76
+
77
+ try {
78
+ addLog('Loading chunks from file...')
79
+ const chunks = await loadChunks()
80
+ addLog(`Loaded ${chunks.length} chunks`)
81
+
82
+ if (chunks.length === 0) {
83
+ throw new Error('No chunks found to ingest')
84
+ }
85
+
86
+ const results: IngestionStats = {
87
+ total: chunks.length,
88
+ processed: 0,
89
+ created: 0,
90
+ updated: 0,
91
+ errors: 0
92
+ }
93
+
94
+ const batchSize = 10
95
+ const totalBatches = Math.ceil(chunks.length / batchSize)
96
+
97
+ for (let i = 0; i < chunks.length; i += batchSize) {
98
+ const batch = chunks.slice(i, i + batchSize)
99
+ const batchNum = Math.floor(i / batchSize) + 1
100
+
101
+ addLog(`Processing batch ${batchNum}/${totalBatches} (${batch.length} chunks)...`)
102
+
103
+ try {
104
+ const response = await ingestBatch(batch)
105
+
106
+ results.processed += batch.length
107
+ results.created += response.items_created || 0
108
+ results.updated += response.items_updated || 0
109
+
110
+ if (response.errors && response.errors.length > 0) {
111
+ results.errors += response.errors.length
112
+ addLog(`Batch ${batchNum} had ${response.errors.length} errors`)
113
+ }
114
+
115
+ if (response.skipped && response.skipped.length > 0) {
116
+ addLog(`Batch ${batchNum} skipped ${response.skipped.length} chunks`)
117
+ }
118
+
119
+ addLog(`Batch ${batchNum} complete: ${response.items_created || 0} created, ${response.items_updated || 0} updated`)
120
+
121
+ // Update progress
122
+ setStats({ ...results })
123
+
124
+ // Small delay to prevent overwhelming the server
125
+ await new Promise(resolve => setTimeout(resolve, 500))
126
+
127
+ } catch (batchError) {
128
+ results.errors += batch.length
129
+ addLog(`Batch ${batchNum} failed: ${batchError instanceof Error ? batchError.message : 'Unknown error'}`)
130
+ }
131
+ }
132
+
133
+ addLog(`Ingestion complete! Created: ${results.created}, Updated: ${results.updated}, Errors: ${results.errors}`)
134
+ setStats(results)
135
+
136
+ } catch (err) {
137
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred'
138
+ setError(errorMessage)
139
+ addLog(`ERROR: ${errorMessage}`)
140
+ } finally {
141
+ setIsIngesting(false)
142
+ }
143
+ }
144
+
145
+ const progress = stats ? (stats.processed / stats.total) * 100 : 0
146
+
147
+ return (
148
+ <div className="max-w-4xl mx-auto p-6 space-y-6">
149
+ <Card>
150
+ <CardHeader>
151
+ <CardTitle>KB Code Chunk Ingestion</CardTitle>
152
+ <CardDescription>
153
+ Ingest parsed code chunks from v2-core functions into the Knowledge Base system.
154
+ This creates KB articles and embeddings for each code chunk.
155
+ </CardDescription>
156
+ </CardHeader>
157
+ <CardContent className="space-y-4">
158
+ <div className="flex items-center gap-4">
159
+ <Button
160
+ onClick={startIngestion}
161
+ disabled={isIngesting}
162
+ size="lg"
163
+ >
164
+ {isIngesting ? 'Ingesting...' : 'Start Ingestion'}
165
+ </Button>
166
+
167
+ {stats && (
168
+ <div className="flex gap-2">
169
+ <Badge variant="outline">
170
+ Total: {stats.total}
171
+ </Badge>
172
+ <Badge variant="outline">
173
+ Created: {stats.created}
174
+ </Badge>
175
+ <Badge variant="outline">
176
+ Updated: {stats.updated}
177
+ </Badge>
178
+ {stats.errors > 0 && (
179
+ <Badge variant="destructive">
180
+ Errors: {stats.errors}
181
+ </Badge>
182
+ )}
183
+ </div>
184
+ )}
185
+ </div>
186
+
187
+ {isIngesting && (
188
+ <div className="space-y-2">
189
+ <div className="flex justify-between text-sm">
190
+ <span>Progress</span>
191
+ <span>{stats ? `${stats.processed}/${stats.total}` : 'Starting...'}</span>
192
+ </div>
193
+ <Progress value={progress} className="w-full" />
194
+ </div>
195
+ )}
196
+
197
+ {error && (
198
+ <Alert variant="destructive">
199
+ <AlertDescription>{error}</AlertDescription>
200
+ </Alert>
201
+ )}
202
+
203
+ {logs.length > 0 && (
204
+ <div className="space-y-2">
205
+ <h4 className="font-medium">Activity Log</h4>
206
+ <div className="bg-gray-50 dark:bg-gray-900 p-4 rounded-lg max-h-64 overflow-y-auto">
207
+ <pre className="text-xs font-mono whitespace-pre-wrap">
208
+ {logs.join('\n')}
209
+ </pre>
210
+ </div>
211
+ </div>
212
+ )}
213
+ </CardContent>
214
+ </Card>
215
+ </div>
216
+ )
217
+ }
@@ -5,7 +5,6 @@
5
5
  "version": "1.0.0",
6
6
  "required_roles": ["support"],
7
7
  "routes": [
8
- "/cortex",
9
8
  "/cortex/dashboard",
10
9
  "/cortex/crm",
11
10
  "/cortex/crm/accounts",
@@ -75,6 +74,18 @@
75
74
  "order": 7
76
75
  }
77
76
  ],
77
+ "changelog": [
78
+ {
79
+ "version": "1.0.0",
80
+ "notes": [
81
+ "Root-relative nav paths for subdomain deployment model",
82
+ "Prefix-aware sidebar navigation via useCurrentApp()",
83
+ "Prefix-aware breadcrumbs with Navigate index redirect",
84
+ "FilterTab replaces FunnelTab (lucide Filter icon)",
85
+ "Initial release"
86
+ ]
87
+ }
88
+ ],
78
89
  "features": ["crm", "support", "community", "kb", "courses", "intelligence"],
79
90
  "dependencies": ["items", "accounts", "pipelines", "integrations"],
80
91
  "entry_point": "./index.tsx",
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "spine-framework-cortex",
3
+ "version": "0.1.9",
4
+ "description": "Cortex — AI-powered support, CRM, and knowledge base app for Spine Framework",
5
+ "type": "module",
6
+ "license": "SEE LICENSE IN LICENSE.md",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/spine-framework/cortex",
10
+ "directory": "custom/apps/cortex"
11
+ },
12
+ "peerDependencies": {
13
+ "spine-framework": ">=0.1.0"
14
+ },
15
+ "files": [
16
+ "index.tsx",
17
+ "manifest.json",
18
+ "seed/",
19
+ "pages/",
20
+ "components/",
21
+ "functions/",
22
+ "lib/",
23
+ "README.md",
24
+ "LICENSE.md"
25
+ ],
26
+ "spine": {
27
+ "type": "app",
28
+ "slug": "cortex",
29
+ "manifestPath": "manifest.json"
30
+ }
31
+ }
@@ -7,7 +7,7 @@ import { Skeleton } from '@core/components/ui/skeleton'
7
7
  import { Button } from '@core/components/ui/button'
8
8
  import { ScrollArea } from '@core/components/ui/scroll-area'
9
9
  import { Separator } from '@core/components/ui/separator'
10
- import { ArrowLeft, User, Ticket, Handshake, Activity, Heart, Funnel, TrendingUp, Target, Clock } from 'lucide-react'
10
+ import { ArrowLeft, User, Ticket, Handshake, Activity, Heart, Filter, TrendingUp, Target, Clock } from 'lucide-react'
11
11
 
12
12
  interface Account {
13
13
  id: string
@@ -140,7 +140,7 @@ function ActivityTab({ accountId }: { accountId: string }) {
140
140
  )
141
141
  }
142
142
 
143
- function FunnelTab({ account }: { account: Account }) {
143
+ function FilterTab({ account }: { account: Account }) {
144
144
  const stage = account.data?.lifecycle_stage
145
145
  const score = account.data?.lead_score ?? 0
146
146
  const temp = account.data?.temperature
@@ -217,7 +217,7 @@ function FunnelTab({ account }: { account: Account }) {
217
217
  {queue?.pending_opportunity_id && (
218
218
  <div className="border rounded-lg p-4 bg-yellow-50 border-yellow-200">
219
219
  <div className="flex items-center gap-2">
220
- <Funnel className="h-4 w-4 text-yellow-600" />
220
+ <Filter className="h-4 w-4 text-yellow-600" />
221
221
  <span className="font-medium text-sm">Opportunity in Queue</span>
222
222
  </div>
223
223
  <p className="text-xs text-muted-foreground mt-1">
@@ -317,7 +317,7 @@ function FunnelTab({ account }: { account: Account }) {
317
317
  {/* Empty State */}
318
318
  {!stage && !score && !temp && !ratings && !queue?.pending_opportunity_id && (
319
319
  <div className="text-center py-12 text-muted-foreground">
320
- <Funnel className="h-10 w-10 mx-auto mb-3 opacity-30" />
320
+ <Filter className="h-10 w-10 mx-auto mb-3 opacity-30" />
321
321
  <p className="text-sm">No funnel data yet.</p>
322
322
  <p className="text-xs mt-1">Signals will appear here as they are processed.</p>
323
323
  </div>
@@ -364,7 +364,7 @@ export default function AccountDetailPage() {
364
364
  <TabsList className="h-9 bg-transparent p-0 gap-4">
365
365
  {[
366
366
  { value: 'people', label: 'People', icon: User },
367
- { value: 'funnel', label: 'Funnel', icon: Funnel },
367
+ { value: 'funnel', label: 'Funnel', icon: Filter },
368
368
  { value: 'tickets', label: 'Tickets', icon: Ticket },
369
369
  { value: 'deals', label: 'Deals', icon: Handshake },
370
370
  { value: 'health', label: 'Health', icon: Heart },
@@ -381,7 +381,7 @@ export default function AccountDetailPage() {
381
381
 
382
382
  <ScrollArea className="flex-1">
383
383
  <TabsContent value="people" className="mt-0"><PeopleTab accountId={id!} /></TabsContent>
384
- <TabsContent value="funnel" className="mt-0"><FunnelTab account={account} /></TabsContent>
384
+ <TabsContent value="funnel" className="mt-0"><FilterTab account={account} /></TabsContent>
385
385
  <TabsContent value="tickets" className="mt-0"><ItemsTab accountId={id!} typeSlug="support_ticket" emptyText="No tickets." /></TabsContent>
386
386
  <TabsContent value="deals" className="mt-0"><ItemsTab accountId={id!} typeSlug="deal" emptyText="No deals." /></TabsContent>
387
387
  <TabsContent value="health" className="mt-0"><ItemsTab accountId={id!} typeSlug="csm_health" emptyText="No health records." /></TabsContent>