feat(agent): integrate agents into all BOC modules
- Add AgentFAB floating button to CRM, Sales, Finance, HR, Legal, Marketing, Support, Automation - Update AgentChat to use /api/v1/agents/chat with auth token - Add error handling and fallback to mock responses - Update Anthropic model to claude-sonnet-4-6 - Show agent context in chat footer (title + competencies) - Build fresh web-v2 dist
This commit is contained in:
-1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Vendored
+789
File diff suppressed because one or more lines are too long
Vendored
-781
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -8,8 +8,8 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
||||
<script type="module" crossorigin src="/assets/index-DGHqwpHs.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Bj_ETaak.css">
|
||||
<script type="module" crossorigin src="/assets/index-CZzJtMVa.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-C4-IO3Fe.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Send, Bot, User, X, Minimize2, Maximize2, Sparkles } from 'lucide-react';
|
||||
import { Send, Bot, User, X, Minimize2, Maximize2, Sparkles, RefreshCw } from 'lucide-react';
|
||||
import { getAgentContext, AgentContext } from './AgentContext';
|
||||
import { useAuthStore } from '@/stores/authStore';
|
||||
|
||||
interface Meddelande {
|
||||
id: string;
|
||||
@@ -18,17 +19,19 @@ interface AgentChatProps {
|
||||
|
||||
export function AgentChat({ rum, isOpen, onToggle }: AgentChatProps) {
|
||||
const context = getAgentContext(rum);
|
||||
const { token } = useAuthStore();
|
||||
const [meddelanden, setMeddelanden] = useState<Meddelande[]>([
|
||||
{
|
||||
id: 'welcome',
|
||||
roll: 'assistant',
|
||||
innehall: `Hej! Jag är ${context.titel}. Jag kan hjälpa dig med ${context.kompetenser.join(', ')}. Vad kan jag göra för dig?`,
|
||||
innehall: `Hej! Jag är ${context.titel}. ${context.systemPrompt.split('\n')[2] || 'Jag kan hjälpa dig med ' + context.kompetenser.join(', ') + '.'}\n\nVad kan jag göra för dig?`,
|
||||
tid: new Date().toISOString()
|
||||
}
|
||||
]);
|
||||
const [input, setInput] = useState('');
|
||||
const [laddar, setLaddar] = useState(false);
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
@@ -52,11 +55,16 @@ export function AgentChat({ rum, isOpen, onToggle }: AgentChatProps) {
|
||||
setInput('');
|
||||
setLaddar(true);
|
||||
|
||||
setError('');
|
||||
|
||||
try {
|
||||
// Anropa Claude via OpenClaw API
|
||||
const response = await fetch('/api/agent/chat', {
|
||||
// Anropa BOC Agent API med auth
|
||||
const response = await fetch('/api/v1/agents/chat', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': token ? `Bearer ${token}` : ''
|
||||
},
|
||||
body: JSON.stringify({
|
||||
rum: context.rum,
|
||||
systemPrompt: context.systemPrompt,
|
||||
@@ -75,7 +83,19 @@ export function AgentChat({ rum, isOpen, onToggle }: AgentChatProps) {
|
||||
innehall: data.svar,
|
||||
tid: new Date().toISOString()
|
||||
}]);
|
||||
} else if (response.status === 401) {
|
||||
setError('Du måste logga in för att använda agenten');
|
||||
// Fallback: Mock-svar
|
||||
const mockSvar = genereraMockSvar(context, input.trim());
|
||||
setMeddelanden(prev => [...prev, {
|
||||
id: `assistant-${Date.now()}`,
|
||||
roll: 'assistant',
|
||||
innehall: mockSvar,
|
||||
tid: new Date().toISOString()
|
||||
}]);
|
||||
} else {
|
||||
const errData = await response.json().catch(() => ({}));
|
||||
setError(errData.error || 'Agenten svarade inte');
|
||||
// Fallback: Mock-svar
|
||||
const mockSvar = genereraMockSvar(context, input.trim());
|
||||
setMeddelanden(prev => [...prev, {
|
||||
@@ -85,7 +105,8 @@ export function AgentChat({ rum, isOpen, onToggle }: AgentChatProps) {
|
||||
tid: new Date().toISOString()
|
||||
}]);
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (err) {
|
||||
setError('Kunde inte nå agenten. Använder lokalt svar.');
|
||||
const mockSvar = genereraMockSvar(context, input.trim());
|
||||
setMeddelanden(prev => [...prev, {
|
||||
id: `assistant-${Date.now()}`,
|
||||
@@ -202,6 +223,16 @@ export function AgentChat({ rum, isOpen, onToggle }: AgentChatProps) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Error */}
|
||||
{error && (
|
||||
<div className="px-4 py-2 bg-danger/10 border-t border-danger/20">
|
||||
<p className="text-xs text-danger flex items-center gap-1.5">
|
||||
<RefreshCw size={12} />
|
||||
{error}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Input */}
|
||||
<div className="p-4 border-t border-border">
|
||||
<div className="flex gap-2">
|
||||
@@ -210,7 +241,7 @@ export function AgentChat({ rum, isOpen, onToggle }: AgentChatProps) {
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyDown={hanteraKeyDown}
|
||||
placeholder="Skriv ett meddelande..."
|
||||
placeholder={`Fråga ${context.titel}...`}
|
||||
className="flex-1 min-h-[44px] max-h-[120px] px-4 py-2.5 rounded-xl border bg-surface text-sm resize-none focus:outline-none focus:ring-2 focus:ring-primary/20"
|
||||
rows={1}
|
||||
/>
|
||||
@@ -223,7 +254,7 @@ export function AgentChat({ rum, isOpen, onToggle }: AgentChatProps) {
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-text-secondary mt-2 text-center">
|
||||
AI kan göra misstag. Verifiera viktig information.
|
||||
{context.titel} • {context.kompetenser.slice(0, 3).join(' • ')}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Bot, X } from 'lucide-react';
|
||||
import { AgentChat } from './AgentChat';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface AgentFABProps {
|
||||
rum: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function AgentFAB({ rum, className }: AgentFABProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black/20 z-40"
|
||||
onClick={() => setIsOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<AgentChat
|
||||
rum={rum}
|
||||
isOpen={isOpen}
|
||||
onToggle={() => setIsOpen(!isOpen)}
|
||||
/>
|
||||
|
||||
<motion.button
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className={cn(
|
||||
'fixed bottom-6 right-6 z-50 w-14 h-14 rounded-full shadow-lg shadow-primary/20',
|
||||
'flex items-center justify-center transition-colors',
|
||||
isOpen
|
||||
? 'bg-danger text-white hover:bg-danger/90'
|
||||
: 'bg-primary text-white hover:bg-primary/90',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<motion.div
|
||||
animate={{ rotate: isOpen ? 90 : 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
{isOpen ? <X size={22} /> : <Bot size={22} />}
|
||||
</motion.div>
|
||||
</motion.button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
FileText,
|
||||
UserPlus,
|
||||
} from 'lucide-react'
|
||||
import { AgentFAB } from '@/components/agent/AgentFAB'
|
||||
|
||||
const workflows = [
|
||||
{
|
||||
@@ -202,6 +203,9 @@ export function AutomationPage() {
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Automation Agent */}
|
||||
<AgentFAB rum="automation" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
Building2,
|
||||
MoreHorizontal,
|
||||
} from 'lucide-react'
|
||||
import { AgentFAB } from '@/components/agent/AgentFAB'
|
||||
|
||||
interface Customer {
|
||||
id: string
|
||||
@@ -346,6 +347,9 @@ export function CRMPage() {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* CRM Agent */}
|
||||
<AgentFAB rum="crm" />
|
||||
|
||||
{activeTab === 'Pipeline' && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||
{pipelineStages.length === 0 && (
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
ArrowUpRight,
|
||||
ArrowDownRight,
|
||||
} from 'lucide-react'
|
||||
import { AgentFAB } from '@/components/agent/AgentFAB'
|
||||
|
||||
interface BalanceAccount {
|
||||
account: string
|
||||
@@ -575,6 +576,9 @@ export function FinancePage() {
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Finance Agent */}
|
||||
<AgentFAB rum="finance" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
MoreHorizontal,
|
||||
Clock,
|
||||
} from 'lucide-react'
|
||||
import { AgentFAB } from '@/components/agent/AgentFAB'
|
||||
|
||||
interface Employee {
|
||||
id: string
|
||||
@@ -347,6 +348,9 @@ export function HRPage() {
|
||||
</Table>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* HR Agent */}
|
||||
<AgentFAB rum="hr" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
Briefcase,
|
||||
Globe,
|
||||
} from 'lucide-react'
|
||||
import { AgentFAB } from '@/components/agent/AgentFAB'
|
||||
|
||||
interface Contract {
|
||||
id: string
|
||||
@@ -557,6 +558,9 @@ export function LegalPage() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Legal Agent */}
|
||||
<AgentFAB rum="legal" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
TrendingUp,
|
||||
Users,
|
||||
} from 'lucide-react'
|
||||
import { AgentFAB } from '@/components/agent/AgentFAB'
|
||||
|
||||
const campaigns = [
|
||||
{ id: '1', name: 'Q4 Product Launch', status: 'active' as const, channel: 'Email', reach: 12500, engagement: 8.2, conversions: 340 },
|
||||
@@ -134,6 +135,9 @@ export function MarketingPage() {
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Marketing Agent */}
|
||||
<AgentFAB rum="marketing" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
Filter,
|
||||
MoreHorizontal,
|
||||
} from 'lucide-react'
|
||||
import { AgentFAB } from '@/components/agent/AgentFAB'
|
||||
|
||||
interface Deal {
|
||||
id: string
|
||||
@@ -402,6 +403,9 @@ export function SalesPage() {
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Sales Agent */}
|
||||
<AgentFAB rum="sales" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
Filter,
|
||||
MoreHorizontal,
|
||||
} from 'lucide-react'
|
||||
import { AgentFAB } from '@/components/agent/AgentFAB'
|
||||
|
||||
const tickets = [
|
||||
{ id: 'SUP-2024-001', subject: 'Login issues after password reset', customer: 'Acme Corp', priority: 'high' as const, status: 'open' as const, assigned: 'Johan Berg', created: new Date(Date.now() - 1000 * 60 * 30).toISOString() },
|
||||
@@ -212,6 +213,9 @@ export function SupportPage() {
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
|
||||
{/* Support Agent */}
|
||||
<AgentFAB rum="support" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user