mirror of
https://github.com/aljazceru/enclava.git
synced 2025-12-17 07:24:34 +01:00
more cleanup
This commit is contained in:
@@ -164,196 +164,6 @@ def get_zammad_configuration_schema() -> Dict[str, Any]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_signal_configuration_schema() -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Returns the configuration schema for the Signal Bot plugin.
|
|
||||||
Based on the existing Signal module implementation.
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
"type": "object",
|
|
||||||
"title": "Signal Bot Configuration",
|
|
||||||
"description": "Configure AI-powered Signal messaging bot with role-based permissions",
|
|
||||||
"properties": {
|
|
||||||
# Basic Settings
|
|
||||||
"enable_signal_bot": {
|
|
||||||
"type": "boolean",
|
|
||||||
"title": "Enable Signal Bot",
|
|
||||||
"description": "Turn the Signal bot on/off",
|
|
||||||
"default": False,
|
|
||||||
"required": True
|
|
||||||
},
|
|
||||||
"signal_service_url": {
|
|
||||||
"type": "url",
|
|
||||||
"title": "Signal Service URL",
|
|
||||||
"description": "Signal service endpoint (e.g., signal-cli-rest-api)",
|
|
||||||
"required": True,
|
|
||||||
"placeholder": "http://localhost:8080",
|
|
||||||
"depends_on": {
|
|
||||||
"field": "enable_signal_bot",
|
|
||||||
"value": True
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"bot_phone_number": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Bot Phone Number",
|
|
||||||
"description": "Registered Signal phone number for the bot",
|
|
||||||
"required": True,
|
|
||||||
"placeholder": "+1234567890",
|
|
||||||
"pattern": "^\\+[1-9]\\d{1,14}$",
|
|
||||||
"depends_on": {
|
|
||||||
"field": "enable_signal_bot",
|
|
||||||
"value": True
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# AI Settings
|
|
||||||
"model": {
|
|
||||||
"type": "select",
|
|
||||||
"title": "AI Model",
|
|
||||||
"description": "Choose the AI model for responses",
|
|
||||||
"required": False,
|
|
||||||
"default": "privatemode-llama-3-70b",
|
|
||||||
"options": [], # Will be populated from available models
|
|
||||||
"depends_on": {
|
|
||||||
"field": "enable_signal_bot",
|
|
||||||
"value": True
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"temperature": {
|
|
||||||
"type": "number",
|
|
||||||
"title": "Response Creativity",
|
|
||||||
"description": "Control response creativity (0.0-1.0)",
|
|
||||||
"required": False,
|
|
||||||
"default": 0.7,
|
|
||||||
"minimum": 0.0,
|
|
||||||
"maximum": 1.0,
|
|
||||||
"depends_on": {
|
|
||||||
"field": "enable_signal_bot",
|
|
||||||
"value": True
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"max_tokens": {
|
|
||||||
"type": "integer",
|
|
||||||
"title": "Max Response Length",
|
|
||||||
"description": "Maximum tokens in AI responses",
|
|
||||||
"required": False,
|
|
||||||
"default": 500,
|
|
||||||
"minimum": 50,
|
|
||||||
"maximum": 2000,
|
|
||||||
"depends_on": {
|
|
||||||
"field": "enable_signal_bot",
|
|
||||||
"value": True
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"memory_length": {
|
|
||||||
"type": "integer",
|
|
||||||
"title": "Conversation Memory",
|
|
||||||
"description": "Number of message pairs to remember per user",
|
|
||||||
"required": False,
|
|
||||||
"default": 10,
|
|
||||||
"minimum": 1,
|
|
||||||
"maximum": 50,
|
|
||||||
"depends_on": {
|
|
||||||
"field": "enable_signal_bot",
|
|
||||||
"value": True
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# Permission Settings
|
|
||||||
"default_role": {
|
|
||||||
"type": "select",
|
|
||||||
"title": "Default User Role",
|
|
||||||
"description": "Role assigned to new Signal users",
|
|
||||||
"required": False,
|
|
||||||
"default": "user",
|
|
||||||
"options": [
|
|
||||||
{"value": "admin", "label": "Admin"},
|
|
||||||
{"value": "user", "label": "User"},
|
|
||||||
{"value": "disabled", "label": "Disabled"}
|
|
||||||
],
|
|
||||||
"depends_on": {
|
|
||||||
"field": "enable_signal_bot",
|
|
||||||
"value": True
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"auto_register": {
|
|
||||||
"type": "boolean",
|
|
||||||
"title": "Auto-Register New Users",
|
|
||||||
"description": "Automatically register new Signal users",
|
|
||||||
"default": True,
|
|
||||||
"required": False,
|
|
||||||
"depends_on": {
|
|
||||||
"field": "enable_signal_bot",
|
|
||||||
"value": True
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"admin_phone_numbers": {
|
|
||||||
"type": "textarea",
|
|
||||||
"title": "Admin Phone Numbers",
|
|
||||||
"description": "Phone numbers with admin privileges (one per line)",
|
|
||||||
"required": False,
|
|
||||||
"placeholder": "+1234567890\n+0987654321",
|
|
||||||
"rows": 3,
|
|
||||||
"depends_on": {
|
|
||||||
"field": "enable_signal_bot",
|
|
||||||
"value": True
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# Bot Behavior
|
|
||||||
"command_prefix": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Command Prefix",
|
|
||||||
"description": "Prefix for bot commands",
|
|
||||||
"required": False,
|
|
||||||
"default": "!",
|
|
||||||
"placeholder": "!",
|
|
||||||
"depends_on": {
|
|
||||||
"field": "enable_signal_bot",
|
|
||||||
"value": True
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"log_conversations": {
|
|
||||||
"type": "boolean",
|
|
||||||
"title": "Log Conversations",
|
|
||||||
"description": "Enable conversation logging for analytics",
|
|
||||||
"default": False,
|
|
||||||
"required": False,
|
|
||||||
"depends_on": {
|
|
||||||
"field": "enable_signal_bot",
|
|
||||||
"value": True
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["enable_signal_bot"],
|
|
||||||
"field_groups": [
|
|
||||||
{
|
|
||||||
"title": "Basic Settings",
|
|
||||||
"fields": ["enable_signal_bot", "signal_service_url", "bot_phone_number"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "AI Configuration",
|
|
||||||
"fields": ["model", "temperature", "max_tokens", "memory_length"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Permission Settings",
|
|
||||||
"fields": ["default_role", "auto_register", "admin_phone_numbers"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Bot Behavior",
|
|
||||||
"fields": ["command_prefix", "log_conversations"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"validation": {
|
|
||||||
"signal_test": {
|
|
||||||
"endpoint": "/api/v1/signal/test-connection",
|
|
||||||
"method": "POST",
|
|
||||||
"fields": ["signal_service_url", "bot_phone_number"],
|
|
||||||
"success_message": "Signal service connection successful",
|
|
||||||
"error_field": "Signal connection failed"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_email_assistant_configuration_schema() -> Dict[str, Any]:
|
def get_email_assistant_configuration_schema() -> Dict[str, Any]:
|
||||||
@@ -471,7 +281,6 @@ def get_plugin_configuration_schema(plugin_id: str) -> Optional[Dict[str, Any]]:
|
|||||||
"""
|
"""
|
||||||
schemas = {
|
schemas = {
|
||||||
"zammad": get_zammad_configuration_schema,
|
"zammad": get_zammad_configuration_schema,
|
||||||
"signal": get_signal_configuration_schema,
|
|
||||||
"email-assistant": get_email_assistant_configuration_schema
|
"email-assistant": get_email_assistant_configuration_schema
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,137 +0,0 @@
|
|||||||
"""
|
|
||||||
Test TEE API endpoints.
|
|
||||||
"""
|
|
||||||
import pytest
|
|
||||||
from httpx import AsyncClient
|
|
||||||
from unittest.mock import patch, AsyncMock
|
|
||||||
|
|
||||||
|
|
||||||
class TestTEEEndpoints:
|
|
||||||
"""Test TEE API endpoints."""
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_tee_health_check(self, client: AsyncClient):
|
|
||||||
"""Test TEE health check endpoint."""
|
|
||||||
mock_health = {
|
|
||||||
"status": "healthy",
|
|
||||||
"timestamp": "2024-01-01T00:00:00Z",
|
|
||||||
"version": "1.0.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
with patch("app.services.tee_service.TEEService.health_check") as mock_check:
|
|
||||||
mock_check.return_value = mock_health
|
|
||||||
|
|
||||||
response = await client.get(
|
|
||||||
"/api/v1/tee/health",
|
|
||||||
headers={"Authorization": "Bearer test-api-key"}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
data = response.json()
|
|
||||||
assert data["status"] == "healthy"
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_tee_capabilities(self, client: AsyncClient):
|
|
||||||
"""Test TEE capabilities endpoint."""
|
|
||||||
mock_capabilities = {
|
|
||||||
"hardware_security": True,
|
|
||||||
"encryption_at_rest": True,
|
|
||||||
"memory_protection": True,
|
|
||||||
"supported_models": ["gpt-3.5-turbo", "claude-3-haiku"]
|
|
||||||
}
|
|
||||||
|
|
||||||
with patch("app.services.tee_service.TEEService.get_tee_capabilities") as mock_caps:
|
|
||||||
mock_caps.return_value = mock_capabilities
|
|
||||||
|
|
||||||
response = await client.get(
|
|
||||||
"/api/v1/tee/capabilities",
|
|
||||||
headers={"Authorization": "Bearer test-api-key"}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
data = response.json()
|
|
||||||
assert data["hardware_security"] is True
|
|
||||||
assert "supported_models" in data
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_tee_attestation(self, client: AsyncClient):
|
|
||||||
"""Test TEE attestation endpoint."""
|
|
||||||
mock_attestation = {
|
|
||||||
"attestation_document": "base64_encoded_document",
|
|
||||||
"signature": "signature_data",
|
|
||||||
"timestamp": "2024-01-01T00:00:00Z",
|
|
||||||
"valid": True
|
|
||||||
}
|
|
||||||
|
|
||||||
with patch("app.services.tee_service.TEEService.get_attestation") as mock_att:
|
|
||||||
mock_att.return_value = mock_attestation
|
|
||||||
|
|
||||||
response = await client.get(
|
|
||||||
"/api/v1/tee/attestation",
|
|
||||||
headers={"Authorization": "Bearer test-api-key"}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
data = response.json()
|
|
||||||
assert data["valid"] is True
|
|
||||||
assert "attestation_document" in data
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_tee_session_creation(self, client: AsyncClient):
|
|
||||||
"""Test TEE secure session creation."""
|
|
||||||
mock_session = {
|
|
||||||
"session_id": "secure-session-123",
|
|
||||||
"public_key": "public_key_data",
|
|
||||||
"expires_at": "2024-01-01T01:00:00Z"
|
|
||||||
}
|
|
||||||
|
|
||||||
with patch("app.services.tee_service.TEEService.create_secure_session") as mock_session_create:
|
|
||||||
mock_session_create.return_value = mock_session
|
|
||||||
|
|
||||||
response = await client.post(
|
|
||||||
"/api/v1/tee/session",
|
|
||||||
json={"model": "gpt-3.5-turbo"},
|
|
||||||
headers={"Authorization": "Bearer test-api-key"}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
data = response.json()
|
|
||||||
assert "session_id" in data
|
|
||||||
assert "public_key" in data
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_tee_metrics(self, client: AsyncClient):
|
|
||||||
"""Test TEE metrics endpoint."""
|
|
||||||
mock_metrics = {
|
|
||||||
"total_requests": 1000,
|
|
||||||
"successful_requests": 995,
|
|
||||||
"failed_requests": 5,
|
|
||||||
"avg_response_time": 0.125,
|
|
||||||
"privacy_score": 95.8,
|
|
||||||
"security_level": "high"
|
|
||||||
}
|
|
||||||
|
|
||||||
with patch("app.services.tee_service.TEEService.get_privacy_metrics") as mock_metrics_get:
|
|
||||||
mock_metrics_get.return_value = mock_metrics
|
|
||||||
|
|
||||||
response = await client.get(
|
|
||||||
"/api/v1/tee/metrics",
|
|
||||||
headers={"Authorization": "Bearer test-api-key"}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
data = response.json()
|
|
||||||
assert data["privacy_score"] == 95.8
|
|
||||||
assert data["security_level"] == "high"
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_tee_unauthorized(self, client: AsyncClient):
|
|
||||||
"""Test TEE endpoints without authentication."""
|
|
||||||
response = await client.get("/api/v1/tee/health")
|
|
||||||
assert response.status_code == 401
|
|
||||||
|
|
||||||
response = await client.get("/api/v1/tee/capabilities")
|
|
||||||
assert response.status_code == 401
|
|
||||||
|
|
||||||
response = await client.get("/api/v1/tee/attestation")
|
|
||||||
assert response.status_code == 401
|
|
||||||
@@ -29,7 +29,6 @@ import {
|
|||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { apiClient } from "@/lib/api-client";
|
import { apiClient } from "@/lib/api-client";
|
||||||
import ConfidentialityDashboard from "@/components/settings/ConfidentialityDashboard";
|
|
||||||
|
|
||||||
interface SystemSettings {
|
interface SystemSettings {
|
||||||
// Security Settings
|
// Security Settings
|
||||||
@@ -262,11 +261,10 @@ function SettingsPageContent() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Tabs defaultValue="security" className="space-y-6">
|
<Tabs defaultValue="security" className="space-y-6">
|
||||||
<TabsList className="grid w-full grid-cols-4">
|
<TabsList className="grid w-full grid-cols-3">
|
||||||
<TabsTrigger value="security">Security</TabsTrigger>
|
<TabsTrigger value="security">Security</TabsTrigger>
|
||||||
<TabsTrigger value="api">API</TabsTrigger>
|
<TabsTrigger value="api">API</TabsTrigger>
|
||||||
<TabsTrigger value="notifications">Notifications</TabsTrigger>
|
<TabsTrigger value="notifications">Notifications</TabsTrigger>
|
||||||
<TabsTrigger value="confidentiality">Confidentiality</TabsTrigger>
|
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="security" className="space-y-6">
|
<TabsContent value="security" className="space-y-6">
|
||||||
@@ -852,9 +850,6 @@ function SettingsPageContent() {
|
|||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
|
||||||
<TabsContent value="confidentiality" className="space-y-6">
|
|
||||||
<ConfidentialityDashboard />
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { ProtectedRoute } from "@/components/auth/ProtectedRoute"
|
|
||||||
import { SignalConfig } from "@/components/modules/SignalConfig"
|
|
||||||
|
|
||||||
export default function SignalPage() {
|
|
||||||
return (
|
|
||||||
<ProtectedRoute>
|
|
||||||
<div className="container mx-auto px-4 py-8">
|
|
||||||
<SignalConfig />
|
|
||||||
</div>
|
|
||||||
</ProtectedRoute>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,465 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { useState, useEffect } from "react"
|
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import { Input } from "@/components/ui/input"
|
|
||||||
import { Label } from "@/components/ui/label"
|
|
||||||
import { Switch } from "@/components/ui/switch"
|
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
|
||||||
import { Textarea } from "@/components/ui/textarea"
|
|
||||||
import { Slider } from "@/components/ui/slider"
|
|
||||||
import { Badge } from "@/components/ui/badge"
|
|
||||||
import { Alert, AlertDescription } from "@/components/ui/alert"
|
|
||||||
import { useToast } from "@/hooks/use-toast"
|
|
||||||
import { Settings, Save, RefreshCw, Phone, Bot, Zap } from "lucide-react"
|
|
||||||
import { config } from "@/lib/config"
|
|
||||||
import { apiClient } from "@/lib/api-client"
|
|
||||||
|
|
||||||
interface SignalConfig {
|
|
||||||
enabled: boolean
|
|
||||||
signal_service: string
|
|
||||||
signal_phone_number: string
|
|
||||||
model: string
|
|
||||||
temperature: number
|
|
||||||
max_tokens: number
|
|
||||||
memory_length: number
|
|
||||||
command_prefix: string
|
|
||||||
default_role: string
|
|
||||||
auto_register: boolean
|
|
||||||
admin_phone_numbers: string[]
|
|
||||||
fallback_responses: string[]
|
|
||||||
log_conversations: boolean
|
|
||||||
connection_timeout: number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ConfigSchema {
|
|
||||||
title: string
|
|
||||||
properties: Record<string, any>
|
|
||||||
required: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SignalConfig() {
|
|
||||||
const { toast } = useToast()
|
|
||||||
|
|
||||||
// Get default Signal service URL from environment or use localhost fallback
|
|
||||||
const getDefaultSignalService = () => {
|
|
||||||
return process.env.NEXT_PUBLIC_DEFAULT_SIGNAL_SERVICE || "localhost:8080"
|
|
||||||
}
|
|
||||||
const [config, setConfig] = useState<SignalConfig | null>(null)
|
|
||||||
const [schema, setSchema] = useState<ConfigSchema | null>(null)
|
|
||||||
const [loading, setLoading] = useState(true)
|
|
||||||
const [saving, setSaving] = useState(false)
|
|
||||||
const [newAdminPhone, setNewAdminPhone] = useState("")
|
|
||||||
const [newFallbackResponse, setNewFallbackResponse] = useState("")
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchConfig()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const fetchConfig = async () => {
|
|
||||||
try {
|
|
||||||
setLoading(true)
|
|
||||||
const data = await apiClient.get("/api-internal/v1/modules/signal/config")
|
|
||||||
setSchema(data.schema)
|
|
||||||
|
|
||||||
// Set default config if none exists
|
|
||||||
const defaultConfig: SignalConfig = {
|
|
||||||
enabled: false,
|
|
||||||
signal_service: getDefaultSignalService(),
|
|
||||||
signal_phone_number: "",
|
|
||||||
model: "gpt-3.5-turbo",
|
|
||||||
temperature: 0.7,
|
|
||||||
max_tokens: 1000,
|
|
||||||
memory_length: 10,
|
|
||||||
command_prefix: "!",
|
|
||||||
default_role: "disabled",
|
|
||||||
auto_register: false,
|
|
||||||
admin_phone_numbers: [],
|
|
||||||
fallback_responses: [
|
|
||||||
"I'm not sure how to help with that. Could you please rephrase your question?",
|
|
||||||
"I don't have enough information to answer that question accurately.",
|
|
||||||
"That's outside my knowledge area. Is there something else I can help you with?"
|
|
||||||
],
|
|
||||||
log_conversations: true,
|
|
||||||
connection_timeout: 30
|
|
||||||
}
|
|
||||||
|
|
||||||
setConfig({ ...defaultConfig, ...data.current_config })
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching Signal config:", error)
|
|
||||||
toast({
|
|
||||||
title: "Error",
|
|
||||||
description: "Failed to load Signal bot configuration",
|
|
||||||
variant: "destructive"
|
|
||||||
})
|
|
||||||
} finally {
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveConfig = async () => {
|
|
||||||
if (!config) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
setSaving(true)
|
|
||||||
await apiClient.post("/api-internal/v1/modules/signal/config", config)
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "Success",
|
|
||||||
description: "Signal bot configuration saved successfully"
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error saving config:", error)
|
|
||||||
toast({
|
|
||||||
title: "Error",
|
|
||||||
description: error instanceof Error ? error.message : "Failed to save configuration",
|
|
||||||
variant: "destructive"
|
|
||||||
})
|
|
||||||
} finally {
|
|
||||||
setSaving(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateConfig = (key: keyof SignalConfig, value: any) => {
|
|
||||||
if (!config) return
|
|
||||||
setConfig({ ...config, [key]: value })
|
|
||||||
}
|
|
||||||
|
|
||||||
const addAdminPhone = () => {
|
|
||||||
if (!config || !newAdminPhone.trim()) return
|
|
||||||
if (!newAdminPhone.match(/^\+[1-9]\d{1,14}$/)) {
|
|
||||||
toast({
|
|
||||||
title: "Invalid Format",
|
|
||||||
description: "Phone number must be in international format (e.g., +1234567890)",
|
|
||||||
variant: "destructive"
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
updateConfig("admin_phone_numbers", [...config.admin_phone_numbers, newAdminPhone.trim()])
|
|
||||||
setNewAdminPhone("")
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeAdminPhone = (index: number) => {
|
|
||||||
if (!config) return
|
|
||||||
const phones = [...config.admin_phone_numbers]
|
|
||||||
phones.splice(index, 1)
|
|
||||||
updateConfig("admin_phone_numbers", phones)
|
|
||||||
}
|
|
||||||
|
|
||||||
const addFallbackResponse = () => {
|
|
||||||
if (!config || !newFallbackResponse.trim()) return
|
|
||||||
updateConfig("fallback_responses", [...config.fallback_responses, newFallbackResponse.trim()])
|
|
||||||
setNewFallbackResponse("")
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeFallbackResponse = (index: number) => {
|
|
||||||
if (!config) return
|
|
||||||
const responses = [...config.fallback_responses]
|
|
||||||
responses.splice(index, 1)
|
|
||||||
updateConfig("fallback_responses", responses)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<CardContent className="flex items-center justify-center py-8">
|
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config || !schema) {
|
|
||||||
return (
|
|
||||||
<Alert>
|
|
||||||
<AlertDescription>
|
|
||||||
Failed to load Signal bot configuration. Please try refreshing the page.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
{/* Header */}
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-bold">Signal Bot Configuration</h2>
|
|
||||||
<p className="text-muted-foreground">Configure your AI-powered Signal messaging bot</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button onClick={fetchConfig} variant="outline" disabled={loading}>
|
|
||||||
<RefreshCw className="h-4 w-4 mr-2" />
|
|
||||||
Refresh
|
|
||||||
</Button>
|
|
||||||
<Button onClick={saveConfig} disabled={saving}>
|
|
||||||
<Save className="h-4 w-4 mr-2" />
|
|
||||||
{saving ? "Saving..." : "Save"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Enable/Disable Switch */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<CardTitle className="flex items-center">
|
|
||||||
<Bot className="h-5 w-5 mr-2" />
|
|
||||||
Signal Bot Status
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>Enable or disable the Signal bot</CardDescription>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
checked={config.enabled}
|
|
||||||
onCheckedChange={(enabled) => updateConfig("enabled", enabled)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Basic Configuration */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center">
|
|
||||||
<Settings className="h-5 w-5 mr-2" />
|
|
||||||
Basic Configuration
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="signal_service">Signal Service URL *</Label>
|
|
||||||
<Input
|
|
||||||
id="signal_service"
|
|
||||||
value={config.signal_service}
|
|
||||||
onChange={(e) => updateConfig("signal_service", e.target.value)}
|
|
||||||
placeholder={getDefaultSignalService()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="signal_phone_number">Bot Phone Number *</Label>
|
|
||||||
<Input
|
|
||||||
id="signal_phone_number"
|
|
||||||
value={config.signal_phone_number}
|
|
||||||
onChange={(e) => updateConfig("signal_phone_number", e.target.value)}
|
|
||||||
placeholder="+1234567890"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* AI Configuration */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center">
|
|
||||||
<Zap className="h-5 w-5 mr-2" />
|
|
||||||
AI Configuration
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="model">AI Model</Label>
|
|
||||||
<Select value={config.model} onValueChange={(value) => updateConfig("model", value)}>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{schema?.properties?.model?.enum ? (
|
|
||||||
schema.properties.model.enum.map((modelId: string) => (
|
|
||||||
<SelectItem key={modelId} value={modelId}>
|
|
||||||
{modelId}
|
|
||||||
</SelectItem>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
// Fallback if schema not loaded yet
|
|
||||||
<>
|
|
||||||
<SelectItem value="gpt-3.5-turbo">GPT-3.5 Turbo</SelectItem>
|
|
||||||
<SelectItem value="gpt-4">GPT-4</SelectItem>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="command_prefix">Command Prefix</Label>
|
|
||||||
<Input
|
|
||||||
id="command_prefix"
|
|
||||||
value={config.command_prefix}
|
|
||||||
onChange={(e) => updateConfig("command_prefix", e.target.value)}
|
|
||||||
placeholder="!"
|
|
||||||
maxLength={5}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Response Creativity: {config.temperature}</Label>
|
|
||||||
<Slider
|
|
||||||
value={[config.temperature]}
|
|
||||||
onValueChange={(value) => updateConfig("temperature", value[0])}
|
|
||||||
max={1}
|
|
||||||
min={0}
|
|
||||||
step={0.1}
|
|
||||||
className="w-full"
|
|
||||||
/>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
0.0 = focused and deterministic, 1.0 = creative and diverse
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Max Response Length: {config.max_tokens}</Label>
|
|
||||||
<Slider
|
|
||||||
value={[config.max_tokens]}
|
|
||||||
onValueChange={(value) => updateConfig("max_tokens", value[0])}
|
|
||||||
max={4000}
|
|
||||||
min={50}
|
|
||||||
step={50}
|
|
||||||
className="w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Conversation Memory: {config.memory_length}</Label>
|
|
||||||
<Slider
|
|
||||||
value={[config.memory_length]}
|
|
||||||
onValueChange={(value) => updateConfig("memory_length", value[0])}
|
|
||||||
max={50}
|
|
||||||
min={1}
|
|
||||||
step={1}
|
|
||||||
className="w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* User Management */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center">
|
|
||||||
<Phone className="h-5 w-5 mr-2" />
|
|
||||||
User Management
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<Label>Auto-Register New Users</Label>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Automatically register new Signal contacts
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
checked={config.auto_register}
|
|
||||||
onCheckedChange={(enabled) => updateConfig("auto_register", enabled)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="default_role">Default Role for New Users</Label>
|
|
||||||
<Select value={config.default_role} onValueChange={(value) => updateConfig("default_role", value)}>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="user">User</SelectItem>
|
|
||||||
<SelectItem value="admin">Admin</SelectItem>
|
|
||||||
<SelectItem value="disabled">Disabled</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Admin Phone Numbers</Label>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Input
|
|
||||||
value={newAdminPhone}
|
|
||||||
onChange={(e) => setNewAdminPhone(e.target.value)}
|
|
||||||
placeholder="+1234567890"
|
|
||||||
/>
|
|
||||||
<Button onClick={addAdminPhone} variant="outline">
|
|
||||||
Add
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
{config.admin_phone_numbers.map((phone, index) => (
|
|
||||||
<Badge key={index} variant="secondary" className="cursor-pointer" onClick={() => removeAdminPhone(index)}>
|
|
||||||
{phone} ×
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Advanced Settings */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Advanced Settings</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Fallback Responses</Label>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Textarea
|
|
||||||
value={newFallbackResponse}
|
|
||||||
onChange={(e) => setNewFallbackResponse(e.target.value)}
|
|
||||||
placeholder="Add a fallback response..."
|
|
||||||
rows={2}
|
|
||||||
/>
|
|
||||||
<Button onClick={addFallbackResponse} variant="outline">
|
|
||||||
Add
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
{config.fallback_responses.map((response, index) => (
|
|
||||||
<div key={index} className="flex items-start gap-2 p-2 bg-muted rounded">
|
|
||||||
<span className="flex-1 text-sm">{response}</span>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="ghost"
|
|
||||||
onClick={() => removeFallbackResponse(index)}
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<Label>Log Conversations</Label>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Save conversations for debugging and analytics
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
checked={config.log_conversations}
|
|
||||||
onCheckedChange={(enabled) => updateConfig("log_conversations", enabled)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Connection Timeout: {config.connection_timeout}s</Label>
|
|
||||||
<Slider
|
|
||||||
value={[config.connection_timeout]}
|
|
||||||
onValueChange={(value) => updateConfig("connection_timeout", value[0])}
|
|
||||||
max={120}
|
|
||||||
min={5}
|
|
||||||
step={5}
|
|
||||||
className="w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user