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:
@@ -120,7 +120,7 @@ func (o *AgentOrchestrator) callAnthropic(systemPrompt string, meddelanden []Age
|
|||||||
}
|
}
|
||||||
|
|
||||||
payload := map[string]interface{}{
|
payload := map[string]interface{}{
|
||||||
"model": "claude-3-sonnet-20240229",
|
"model": "claude-sonnet-4-6",
|
||||||
"max_tokens": 1024,
|
"max_tokens": 1024,
|
||||||
"system": systemPrompt,
|
"system": systemPrompt,
|
||||||
"messages": messages,
|
"messages": messages,
|
||||||
|
|||||||
-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.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<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" />
|
<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>
|
<script type="module" crossorigin src="/assets/index-CZzJtMVa.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-Bj_ETaak.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-C4-IO3Fe.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { useState, useRef, useEffect } from 'react';
|
import { useState, useRef, useEffect } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
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 { getAgentContext, AgentContext } from './AgentContext';
|
||||||
|
import { useAuthStore } from '@/stores/authStore';
|
||||||
|
|
||||||
interface Meddelande {
|
interface Meddelande {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -18,17 +19,19 @@ interface AgentChatProps {
|
|||||||
|
|
||||||
export function AgentChat({ rum, isOpen, onToggle }: AgentChatProps) {
|
export function AgentChat({ rum, isOpen, onToggle }: AgentChatProps) {
|
||||||
const context = getAgentContext(rum);
|
const context = getAgentContext(rum);
|
||||||
|
const { token } = useAuthStore();
|
||||||
const [meddelanden, setMeddelanden] = useState<Meddelande[]>([
|
const [meddelanden, setMeddelanden] = useState<Meddelande[]>([
|
||||||
{
|
{
|
||||||
id: 'welcome',
|
id: 'welcome',
|
||||||
roll: 'assistant',
|
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()
|
tid: new Date().toISOString()
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
const [input, setInput] = useState('');
|
const [input, setInput] = useState('');
|
||||||
const [laddar, setLaddar] = useState(false);
|
const [laddar, setLaddar] = useState(false);
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
const [error, setError] = useState('');
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
|
||||||
@@ -52,11 +55,16 @@ export function AgentChat({ rum, isOpen, onToggle }: AgentChatProps) {
|
|||||||
setInput('');
|
setInput('');
|
||||||
setLaddar(true);
|
setLaddar(true);
|
||||||
|
|
||||||
|
setError('');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Anropa Claude via OpenClaw API
|
// Anropa BOC Agent API med auth
|
||||||
const response = await fetch('/api/agent/chat', {
|
const response = await fetch('/api/v1/agents/chat', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': token ? `Bearer ${token}` : ''
|
||||||
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
rum: context.rum,
|
rum: context.rum,
|
||||||
systemPrompt: context.systemPrompt,
|
systemPrompt: context.systemPrompt,
|
||||||
@@ -75,7 +83,19 @@ export function AgentChat({ rum, isOpen, onToggle }: AgentChatProps) {
|
|||||||
innehall: data.svar,
|
innehall: data.svar,
|
||||||
tid: new Date().toISOString()
|
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 {
|
} else {
|
||||||
|
const errData = await response.json().catch(() => ({}));
|
||||||
|
setError(errData.error || 'Agenten svarade inte');
|
||||||
// Fallback: Mock-svar
|
// Fallback: Mock-svar
|
||||||
const mockSvar = genereraMockSvar(context, input.trim());
|
const mockSvar = genereraMockSvar(context, input.trim());
|
||||||
setMeddelanden(prev => [...prev, {
|
setMeddelanden(prev => [...prev, {
|
||||||
@@ -85,7 +105,8 @@ export function AgentChat({ rum, isOpen, onToggle }: AgentChatProps) {
|
|||||||
tid: new Date().toISOString()
|
tid: new Date().toISOString()
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
|
setError('Kunde inte nå agenten. Använder lokalt svar.');
|
||||||
const mockSvar = genereraMockSvar(context, input.trim());
|
const mockSvar = genereraMockSvar(context, input.trim());
|
||||||
setMeddelanden(prev => [...prev, {
|
setMeddelanden(prev => [...prev, {
|
||||||
id: `assistant-${Date.now()}`,
|
id: `assistant-${Date.now()}`,
|
||||||
@@ -202,6 +223,16 @@ export function AgentChat({ rum, isOpen, onToggle }: AgentChatProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</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 */}
|
{/* Input */}
|
||||||
<div className="p-4 border-t border-border">
|
<div className="p-4 border-t border-border">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
@@ -210,7 +241,7 @@ export function AgentChat({ rum, isOpen, onToggle }: AgentChatProps) {
|
|||||||
value={input}
|
value={input}
|
||||||
onChange={(e) => setInput(e.target.value)}
|
onChange={(e) => setInput(e.target.value)}
|
||||||
onKeyDown={hanteraKeyDown}
|
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"
|
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}
|
rows={1}
|
||||||
/>
|
/>
|
||||||
@@ -223,7 +254,7 @@ export function AgentChat({ rum, isOpen, onToggle }: AgentChatProps) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-text-secondary mt-2 text-center">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</motion.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,
|
FileText,
|
||||||
UserPlus,
|
UserPlus,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
import { AgentFAB } from '@/components/agent/AgentFAB'
|
||||||
|
|
||||||
const workflows = [
|
const workflows = [
|
||||||
{
|
{
|
||||||
@@ -202,6 +203,9 @@ export function AutomationPage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Automation Agent */}
|
||||||
|
<AgentFAB rum="automation" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
Building2,
|
Building2,
|
||||||
MoreHorizontal,
|
MoreHorizontal,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
import { AgentFAB } from '@/components/agent/AgentFAB'
|
||||||
|
|
||||||
interface Customer {
|
interface Customer {
|
||||||
id: string
|
id: string
|
||||||
@@ -346,6 +347,9 @@ export function CRMPage() {
|
|||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* CRM Agent */}
|
||||||
|
<AgentFAB rum="crm" />
|
||||||
|
|
||||||
{activeTab === 'Pipeline' && (
|
{activeTab === 'Pipeline' && (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||||
{pipelineStages.length === 0 && (
|
{pipelineStages.length === 0 && (
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
ArrowUpRight,
|
ArrowUpRight,
|
||||||
ArrowDownRight,
|
ArrowDownRight,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
import { AgentFAB } from '@/components/agent/AgentFAB'
|
||||||
|
|
||||||
interface BalanceAccount {
|
interface BalanceAccount {
|
||||||
account: string
|
account: string
|
||||||
@@ -575,6 +576,9 @@ export function FinancePage() {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Finance Agent */}
|
||||||
|
<AgentFAB rum="finance" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
MoreHorizontal,
|
MoreHorizontal,
|
||||||
Clock,
|
Clock,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
import { AgentFAB } from '@/components/agent/AgentFAB'
|
||||||
|
|
||||||
interface Employee {
|
interface Employee {
|
||||||
id: string
|
id: string
|
||||||
@@ -347,6 +348,9 @@ export function HRPage() {
|
|||||||
</Table>
|
</Table>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* HR Agent */}
|
||||||
|
<AgentFAB rum="hr" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
Briefcase,
|
Briefcase,
|
||||||
Globe,
|
Globe,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
import { AgentFAB } from '@/components/agent/AgentFAB'
|
||||||
|
|
||||||
interface Contract {
|
interface Contract {
|
||||||
id: string
|
id: string
|
||||||
@@ -557,6 +558,9 @@ export function LegalPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Legal Agent */}
|
||||||
|
<AgentFAB rum="legal" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
TrendingUp,
|
TrendingUp,
|
||||||
Users,
|
Users,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
import { AgentFAB } from '@/components/agent/AgentFAB'
|
||||||
|
|
||||||
const campaigns = [
|
const campaigns = [
|
||||||
{ id: '1', name: 'Q4 Product Launch', status: 'active' as const, channel: 'Email', reach: 12500, engagement: 8.2, conversions: 340 },
|
{ 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>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Marketing Agent */}
|
||||||
|
<AgentFAB rum="marketing" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
Filter,
|
Filter,
|
||||||
MoreHorizontal,
|
MoreHorizontal,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
import { AgentFAB } from '@/components/agent/AgentFAB'
|
||||||
|
|
||||||
interface Deal {
|
interface Deal {
|
||||||
id: string
|
id: string
|
||||||
@@ -402,6 +403,9 @@ export function SalesPage() {
|
|||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Sales Agent */}
|
||||||
|
<AgentFAB rum="sales" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
Filter,
|
Filter,
|
||||||
MoreHorizontal,
|
MoreHorizontal,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
import { AgentFAB } from '@/components/agent/AgentFAB'
|
||||||
|
|
||||||
const tickets = [
|
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() },
|
{ 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>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Support Agent */}
|
||||||
|
<AgentFAB rum="support" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user