feat(agent): add Projects + Compliance agents, webhook notifications
- Add AgentFAB to ProjectsPage and CompliancePage - Add AgentWebhookNotifier for agent events (activated, message, escalation, error) - Webhook URL configurable via AGENT_WEBHOOK_URL env var - Build fresh web-v2 dist
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -38,6 +38,7 @@ type AgentOrchestrator struct {
|
|||||||
anthropicKey string
|
anthropicKey string
|
||||||
apiEndpoint string
|
apiEndpoint string
|
||||||
dataProvider *AgentDataProvider
|
dataProvider *AgentDataProvider
|
||||||
|
webhook *AgentWebhookNotifier
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAgentOrchestrator creates a new agent orchestrator
|
// NewAgentOrchestrator creates a new agent orchestrator
|
||||||
@@ -51,6 +52,7 @@ func NewAgentOrchestrator(db, ledgerDB *sql.DB) *AgentOrchestrator {
|
|||||||
anthropicKey: key,
|
anthropicKey: key,
|
||||||
apiEndpoint: "https://api.anthropic.com/v1/messages",
|
apiEndpoint: "https://api.anthropic.com/v1/messages",
|
||||||
dataProvider: NewAgentDataProvider(db, ledgerDB),
|
dataProvider: NewAgentDataProvider(db, ledgerDB),
|
||||||
|
webhook: NewAgentWebhookNotifier(os.Getenv("AGENT_WEBHOOK_URL")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,6 +102,10 @@ func (o *AgentOrchestrator) HandleAgentChat(w http.ResponseWriter, r *http.Reque
|
|||||||
svar, err := o.callAnthropic(enhancedPrompt, req.Meddelanden)
|
svar, err := o.callAnthropic(enhancedPrompt, req.Meddelanden)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Str("rum", req.Rum).Msg("Agent chat failed")
|
log.Error().Err(err).Str("rum", req.Rum).Msg("Agent chat failed")
|
||||||
|
// Notify error
|
||||||
|
if o.webhook != nil {
|
||||||
|
o.webhook.NotifyAgentError(req.Rum, "", err.Error())
|
||||||
|
}
|
||||||
// Fallback to mock
|
// Fallback to mock
|
||||||
mockSvar := generateMockResponse(req.Rum, req.Meddelanden)
|
mockSvar := generateMockResponse(req.Rum, req.Meddelanden)
|
||||||
respondWithJSON(w, AgentChatResponse{
|
respondWithJSON(w, AgentChatResponse{
|
||||||
@@ -110,6 +116,11 @@ func (o *AgentOrchestrator) HandleAgentChat(w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify message sent
|
||||||
|
if o.webhook != nil {
|
||||||
|
o.webhook.NotifyAgentMessage(req.Rum, "", "response")
|
||||||
|
}
|
||||||
|
|
||||||
respondWithJSON(w, AgentChatResponse{
|
respondWithJSON(w, AgentChatResponse{
|
||||||
Svar: svar,
|
Svar: svar,
|
||||||
Rum: req.Rum,
|
Rum: req.Rum,
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AgentWebhookEvent representerar en agent-händelse
|
||||||
|
type AgentWebhookEvent struct {
|
||||||
|
EventType string `json:"event_type"`
|
||||||
|
AgentRum string `json:"agent_rum"`
|
||||||
|
Timestamp string `json:"timestamp"`
|
||||||
|
UserID string `json:"user_id,omitempty"`
|
||||||
|
Payload map[string]interface{} `json:"payload"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentWebhookNotifier skickar agent-händelser till konfigurerade webhooks
|
||||||
|
type AgentWebhookNotifier struct {
|
||||||
|
webhookURL string
|
||||||
|
client *http.Client
|
||||||
|
enabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAgentWebhookNotifier skapar en ny notifier
|
||||||
|
func NewAgentWebhookNotifier(webhookURL string) *AgentWebhookNotifier {
|
||||||
|
return &AgentWebhookNotifier{
|
||||||
|
webhookURL: webhookURL,
|
||||||
|
client: &http.Client{Timeout: 10 * time.Second},
|
||||||
|
enabled: webhookURL != "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify skickar en händelse till webhook
|
||||||
|
func (n *AgentWebhookNotifier) Notify(event AgentWebhookEvent) {
|
||||||
|
if !n.enabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event.Timestamp = time.Now().Format(time.RFC3339)
|
||||||
|
|
||||||
|
jsonData, err := json.Marshal(event)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("event", event.EventType).Msg("Failed to marshal webhook event")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
resp, err := n.client.Post(n.webhookURL, "application/json", bytes.NewBuffer(jsonData))
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Str("url", n.webhookURL).Msg("Webhook delivery failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
log.Warn().Int("status", resp.StatusCode).Str("url", n.webhookURL).Msg("Webhook returned error")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyAgentActivated skickas när en agent aktiveras
|
||||||
|
func (n *AgentWebhookNotifier) NotifyAgentActivated(rum, userID string) {
|
||||||
|
n.Notify(AgentWebhookEvent{
|
||||||
|
EventType: "agent.activated",
|
||||||
|
AgentRum: rum,
|
||||||
|
UserID: userID,
|
||||||
|
Payload: map[string]interface{}{
|
||||||
|
"rum": rum,
|
||||||
|
"user_id": userID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyAgentMessage skickas när en agent skickar ett meddelande
|
||||||
|
func (n *AgentWebhookNotifier) NotifyAgentMessage(rum, userID, messageType string) {
|
||||||
|
n.Notify(AgentWebhookEvent{
|
||||||
|
EventType: "agent.message",
|
||||||
|
AgentRum: rum,
|
||||||
|
UserID: userID,
|
||||||
|
Payload: map[string]interface{}{
|
||||||
|
"rum": rum,
|
||||||
|
"user_id": userID,
|
||||||
|
"message_type": messageType,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyAgentEscalation skickas när en agent eskalerar
|
||||||
|
func (n *AgentWebhookNotifier) NotifyAgentEscalation(rum, userID, reason string) {
|
||||||
|
n.Notify(AgentWebhookEvent{
|
||||||
|
EventType: "agent.escalation",
|
||||||
|
AgentRum: rum,
|
||||||
|
UserID: userID,
|
||||||
|
Payload: map[string]interface{}{
|
||||||
|
"rum": rum,
|
||||||
|
"user_id": userID,
|
||||||
|
"reason": reason,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyAgentError skickas när en agent stöter på ett fel
|
||||||
|
func (n *AgentWebhookNotifier) NotifyAgentError(rum, userID, errorMsg string) {
|
||||||
|
n.Notify(AgentWebhookEvent{
|
||||||
|
EventType: "agent.error",
|
||||||
|
AgentRum: rum,
|
||||||
|
UserID: userID,
|
||||||
|
Payload: map[string]interface{}{
|
||||||
|
"rum": rum,
|
||||||
|
"user_id": userID,
|
||||||
|
"error": errorMsg,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
Clock,
|
Clock,
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
import { AgentFAB } from '@/components/agent/AgentFAB'
|
||||||
|
|
||||||
export function CompliancePage() {
|
export function CompliancePage() {
|
||||||
const [activeTab, setActiveTab] = useState('iso')
|
const [activeTab, setActiveTab] = useState('iso')
|
||||||
@@ -514,6 +515,9 @@ export function CompliancePage() {
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Compliance Agent */}
|
||||||
|
<AgentFAB rum="compliance" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
X,
|
X,
|
||||||
GripVertical,
|
GripVertical,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
import { AgentFAB } from '@/components/agent/AgentFAB'
|
||||||
|
|
||||||
interface Task {
|
interface Task {
|
||||||
id: string
|
id: string
|
||||||
@@ -562,6 +563,9 @@ export function ProjectsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Projects Agent */}
|
||||||
|
<AgentFAB rum="projects" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user