202 lines
6.3 KiB
Vue
202 lines
6.3 KiB
Vue
<template>
|
|
<NuxtLayout name="default">
|
|
<main class="container mx-auto px-4 py-8">
|
|
<div class="flex items-center justify-between mb-8">
|
|
<div>
|
|
<h1 class="text-3xl font-bold">Integrations</h1>
|
|
<p class="text-muted-foreground mt-2">
|
|
Configure third-party service integrations for your tenant
|
|
</p>
|
|
</div>
|
|
<Button @click="saveConfig" :disabled="saving">
|
|
<Save class="mr-2 h-4 w-4" />
|
|
{{ saving ? 'Saving...' : 'Save Configuration' }}
|
|
</Button>
|
|
</div>
|
|
|
|
<!-- Services Grid -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<!-- Twilio Configuration -->
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle class="flex items-center gap-2">
|
|
<Phone class="w-5 h-5" />
|
|
Twilio Voice
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Configure Twilio for voice calling
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent class="space-y-4">
|
|
<div class="space-y-2">
|
|
<Label for="twilio-account-sid">Account SID</Label>
|
|
<Input
|
|
id="twilio-account-sid"
|
|
v-model="twilioConfig.accountSid"
|
|
placeholder="ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
/>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="twilio-auth-token">Auth Token</Label>
|
|
<Input
|
|
id="twilio-auth-token"
|
|
v-model="twilioConfig.authToken"
|
|
type="password"
|
|
placeholder="Enter your Twilio auth token"
|
|
/>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="twilio-phone-number">Phone Number</Label>
|
|
<Input
|
|
id="twilio-phone-number"
|
|
v-model="twilioConfig.phoneNumber"
|
|
placeholder="+1234567890"
|
|
/>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="twilio-api-key">API Key SID (for browser calls)</Label>
|
|
<Input
|
|
id="twilio-api-key"
|
|
v-model="twilioConfig.apiKey"
|
|
placeholder="SKxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
/>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="twilio-api-secret">API Secret</Label>
|
|
<Input
|
|
id="twilio-api-secret"
|
|
v-model="twilioConfig.apiSecret"
|
|
type="password"
|
|
placeholder="Enter your API Key Secret"
|
|
/>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="twilio-twiml-app">TwiML App SID</Label>
|
|
<Input
|
|
id="twilio-twiml-app"
|
|
v-model="twilioConfig.twimlAppSid"
|
|
placeholder="APxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<!-- OpenAI Configuration -->
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle class="flex items-center gap-2">
|
|
<Bot class="w-5 h-5" />
|
|
OpenAI Realtime
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Configure OpenAI for AI features
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent class="space-y-4">
|
|
<div class="space-y-2">
|
|
<Label for="openai-api-key">API Key</Label>
|
|
<Input
|
|
id="openai-api-key"
|
|
v-model="openaiConfig.apiKey"
|
|
type="password"
|
|
placeholder="sk-..."
|
|
/>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="openai-model">Model</Label>
|
|
<Input
|
|
id="openai-model"
|
|
v-model="openaiConfig.model"
|
|
placeholder="gpt-4o-realtime-preview"
|
|
/>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="openai-voice">Voice</Label>
|
|
<select
|
|
id="openai-voice"
|
|
v-model="openaiConfig.voice"
|
|
class="w-full px-3 py-2 border rounded-md bg-background"
|
|
>
|
|
<option value="alloy">Alloy</option>
|
|
<option value="echo">Echo</option>
|
|
<option value="fable">Fable</option>
|
|
<option value="onyx">Onyx</option>
|
|
<option value="nova">Nova</option>
|
|
<option value="shimmer">Shimmer</option>
|
|
</select>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</main>
|
|
</NuxtLayout>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue';
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '~/components/ui/card';
|
|
import { Input } from '~/components/ui/input';
|
|
import { Label } from '~/components/ui/label';
|
|
import { Button } from '~/components/ui/button';
|
|
import { Phone, Bot, Save } from 'lucide-vue-next';
|
|
import { useApi } from '~/composables/useApi';
|
|
import { toast } from 'vue-sonner';
|
|
|
|
const { api } = useApi();
|
|
|
|
const twilioConfig = ref({
|
|
accountSid: '',
|
|
authToken: '',
|
|
phoneNumber: '',
|
|
apiKey: '',
|
|
apiSecret: '',
|
|
twimlAppSid: '',
|
|
});
|
|
|
|
const openaiConfig = ref({
|
|
apiKey: '',
|
|
model: 'gpt-4o-realtime-preview',
|
|
voice: 'alloy',
|
|
});
|
|
|
|
const saving = ref(false);
|
|
const loading = ref(true);
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
const response = await api.get('/tenant/integrations');
|
|
if (response.data) {
|
|
if (response.data.twilio) {
|
|
twilioConfig.value = { ...twilioConfig.value, ...response.data.twilio };
|
|
}
|
|
if (response.data.openai) {
|
|
openaiConfig.value = { ...openaiConfig.value, ...response.data.openai };
|
|
}
|
|
}
|
|
} catch (error: any) {
|
|
console.error('Failed to load configuration:', error);
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
});
|
|
|
|
const saveConfig = async () => {
|
|
saving.value = true;
|
|
|
|
try {
|
|
const integrationsConfig = {
|
|
twilio: twilioConfig.value,
|
|
openai: openaiConfig.value,
|
|
};
|
|
|
|
await api.put('/tenant/integrations', { integrationsConfig });
|
|
|
|
toast.success('Configuration saved successfully');
|
|
} catch (error: any) {
|
|
toast.error(error.message || 'Failed to save configuration');
|
|
} finally {
|
|
saving.value = false;
|
|
}
|
|
};
|
|
</script>
|