transcription-cost-usage-re.../frontend/components/model-prices-table.tsx

423 lines
15 KiB
TypeScript
Raw Normal View History

2025-06-09 11:13:05 +00:00
"use client"
import { useState, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Alert, AlertDescription } from "@/components/ui/alert"
import { Badge } from "@/components/ui/badge"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Edit2, Save, X, Loader2, RefreshCw } from "lucide-react"
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000/api/v1"
// Atualizar a interface ModelPrice para refletir a nova estrutura da API:
interface ModelPrice {
_id: {
$oid: string
}
provider: string
product: string
type: string
billingBy: string
billingUnit: number
currency: string
price: string
2025-06-09 11:13:05 +00:00
createdAt: {
$date: string
}
updatedAt: {
$date: string
}
__v: number
}
// Interface para a resposta da API:
interface ApiResponse {
success: boolean
data: ModelPrice[]
}
export default function ModelPricesTable() {
const [data, setData] = useState<ModelPrice[]>([])
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState("")
const [editingId, setEditingId] = useState<string | null>(null)
// Atualizar editValues para incluir todos os campos editáveis:
const [editValues, setEditValues] = useState<{
product: string
provider: string
type: string
billingBy: string
billingUnit: string
currency: string
2025-06-09 11:13:05 +00:00
price: string
}>({
product: "",
provider: "",
type: "",
billingBy: "",
billingUnit: "",
currency: "",
2025-06-09 11:13:05 +00:00
price: "",
})
const [isSaving, setIsSaving] = useState(false)
// Filtros
const [typeFilter, setTypeFilter] = useState("")
2025-06-09 11:13:05 +00:00
const [providerFilter, setProviderFilter] = useState("")
const getAuthHeaders = () => {
const token = localStorage.getItem("access_token")
return {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
}
}
const fetchData = async () => {
setIsLoading(true)
setError("")
try {
const params = new URLSearchParams()
if (typeFilter) params.append("type", typeFilter)
if (providerFilter) params.append("provider", providerFilter)
const response = await fetch(`${API_BASE_URL}/usage/model/prices?${params}`, {
headers: getAuthHeaders(),
})
if (response.ok) {
// Atualizar o processamento da resposta para a nova estrutura:
const result: ApiResponse = await response.json()
if (result.success && Array.isArray(result.data)) {
setData(result.data)
} else {
setData([])
}
} else {
const errorData = await response.json()
setError(errorData.message || "Erro ao buscar dados")
}
} catch (err) {
2025-06-09 13:32:46 +00:00
console.log("====> Erro de conexão com o servidor: ", err)
2025-06-09 11:13:05 +00:00
setError("Erro de conexão com o servidor")
} finally {
setIsLoading(false)
}
}
// Atualizar a função startEdit para trabalhar com todos os campos:
const startEdit = (item: ModelPrice) => {
const id = item._id.$oid
setEditingId(id)
setEditValues({
product: item.product,
provider: item.provider,
type: item.type,
billingBy: item.billingBy,
billingUnit: item.billingUnit.toString(),
currency: item.currency,
2025-06-09 11:13:05 +00:00
price: item.price,
})
}
const cancelEdit = () => {
setEditingId(null)
setEditValues({
product: "",
provider: "",
type: "",
billingBy: "",
billingUnit: "",
currency: "",
2025-06-09 11:13:05 +00:00
price: "",
})
}
// Atualizar a função saveEdit para enviar todos os campos:
const saveEdit = async (id: string) => {
setIsSaving(true)
setError("")
try {
const response = await fetch(`${API_BASE_URL}/usage/model/prices/${id}`, {
method: "PATCH",
headers: getAuthHeaders(),
body: JSON.stringify({
product: editValues.product,
provider: editValues.provider,
type: editValues.type,
billingBy: editValues.billingBy,
billingUnit: Number.parseInt(editValues.billingUnit),
currency: editValues.currency,
2025-06-09 11:13:05 +00:00
price: editValues.price,
}),
})
if (response.ok) {
await fetchData() // Recarrega os dados
setEditingId(null)
setEditValues({
product: "",
provider: "",
type: "",
billingBy: "",
billingUnit: "",
currency: "",
2025-06-09 11:13:05 +00:00
price: "",
})
} else {
const errorData = await response.json()
setError(errorData.message || "Erro ao salvar alterações")
}
} catch (err) {
2025-06-09 13:32:46 +00:00
console.log("====> Erro de conexão com o servidor: ", err)
2025-06-09 11:13:05 +00:00
setError("Erro de conexão com o servidor")
} finally {
setIsSaving(false)
}
}
useEffect(() => {
fetchData()
}, [])
// Opções para os selects
const typeOptions = ["stt", "tts", "llm", "embedding", "input", "output", "vision"]
2025-06-09 11:13:05 +00:00
const providerOptions = ["openai", "aws", "google", "anthropic", "mistral", "azure"]
const currencyOptions = ["dollar", "real", "euro"]
const billingByOptions = ["second", "minute", "character", "token", "image", "request"]
return (
<div className="space-y-6">
{/* Filtros */}
<Card>
<CardHeader>
<CardTitle>Filtros</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="space-y-2">
<Label htmlFor="typeFilter">Tipo</Label>
<Input
id="typeFilter"
value={typeFilter}
onChange={(e) => setTypeFilter(e.target.value)}
placeholder="stt,tts"
/>
</div>
<div className="space-y-2">
<Label htmlFor="providerFilter">Provedor</Label>
<Input
id="providerFilter"
value={providerFilter}
onChange={(e) => setProviderFilter(e.target.value)}
placeholder="openai,anthropic"
2025-06-09 11:13:05 +00:00
/>
</div>
<div className="space-y-2">
<Label>&nbsp;</Label>
<Button onClick={fetchData} disabled={isLoading} className="w-full">
{isLoading ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <RefreshCw className="mr-2 h-4 w-4" />}
Atualizar
</Button>
</div>
</div>
</CardContent>
</Card>
{error && (
<Alert variant="destructive">
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
{/* Tabela de Preços */}
<Card>
<CardHeader>
<CardTitle>Preços dos Modelos ({data.length} modelos)</CardTitle>
</CardHeader>
<CardContent>
{data.length > 0 ? (
<div className="overflow-x-auto">
{/* Atualizar a tabela para exibir os novos campos: */}
<Table>
<TableHeader>
<TableRow>
<TableHead>Produto</TableHead>
<TableHead>Provedor</TableHead>
<TableHead>Tipo</TableHead>
<TableHead>Cobrança Por</TableHead>
<TableHead>Unidade</TableHead>
<TableHead>Moeda</TableHead>
<TableHead>Preço</TableHead>
2025-06-09 11:13:05 +00:00
<TableHead>Ações</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((item) => {
const itemId = item._id.$oid
const isEditing = editingId === itemId
return (
<TableRow key={itemId}>
<TableCell>
{isEditing ? (
<Input
value={editValues.product}
onChange={(e) => setEditValues((prev) => ({ ...prev, product: e.target.value }))}
className="w-32"
/>
) : (
item.product || "-"
)}
</TableCell>
<TableCell>
{isEditing ? (
<Select
value={editValues.provider}
onValueChange={(value) => setEditValues((prev) => ({ ...prev, provider: value }))}
>
<SelectTrigger className="w-28">
<SelectValue />
</SelectTrigger>
<SelectContent>
{providerOptions.map((option) => (
<SelectItem key={option} value={option}>
{option}
</SelectItem>
))}
</SelectContent>
</Select>
) : (
<Badge variant="secondary">{item.provider}</Badge>
)}
</TableCell>
<TableCell>
{isEditing ? (
<Select
value={editValues.type}
onValueChange={(value) => setEditValues((prev) => ({ ...prev, type: value }))}
>
<SelectTrigger className="w-28">
<SelectValue />
</SelectTrigger>
<SelectContent>
{typeOptions.map((option) => (
<SelectItem key={option} value={option}>
{option}
</SelectItem>
))}
</SelectContent>
</Select>
) : (
<Badge variant="outline">{item.type}</Badge>
)}
</TableCell>
<TableCell>
{isEditing ? (
<Select
value={editValues.billingBy}
onValueChange={(value) => setEditValues((prev) => ({ ...prev, billingBy: value }))}
>
<SelectTrigger className="w-28">
<SelectValue />
</SelectTrigger>
<SelectContent>
{billingByOptions.map((option) => (
<SelectItem key={option} value={option}>
{option}
</SelectItem>
))}
</SelectContent>
</Select>
) : (
item.billingBy
)}
</TableCell>
<TableCell>
{isEditing ? (
<Input
type="number"
value={editValues.billingUnit}
onChange={(e) => setEditValues((prev) => ({ ...prev, billingUnit: e.target.value }))}
className="w-20"
/>
) : (
item.billingUnit
)}
</TableCell>
<TableCell>
{isEditing ? (
<Select
value={editValues.currency}
onValueChange={(value) => setEditValues((prev) => ({ ...prev, currency: value }))}
>
<SelectTrigger className="w-28">
<SelectValue />
</SelectTrigger>
<SelectContent>
{currencyOptions.map((option) => (
<SelectItem key={option} value={option}>
{option}
</SelectItem>
))}
</SelectContent>
</Select>
) : (
<Badge variant="outline">{item.currency}</Badge>
)}
</TableCell>
<TableCell>
{isEditing ? (
<Input
type="number"
step="0.0001"
value={editValues.price}
onChange={(e) => setEditValues((prev) => ({ ...prev, price: e.target.value }))}
className="w-24"
/>
) : (
`$${item.price}`
)}
</TableCell>
2025-06-09 11:13:05 +00:00
<TableCell>
{isEditing ? (
<div className="flex gap-2">
<Button size="sm" onClick={() => saveEdit(itemId)} disabled={isSaving}>
{isSaving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
</Button>
<Button size="sm" variant="outline" onClick={cancelEdit} disabled={isSaving}>
<X className="h-4 w-4" />
</Button>
</div>
) : (
<Button size="sm" variant="outline" onClick={() => startEdit(item)}>
<Edit2 className="h-4 w-4" />
</Button>
)}
</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
</div>
) : (
<div className="text-center py-8 text-gray-500">
{isLoading ? "Carregando..." : "Nenhum modelo encontrado."}
</div>
)}
</CardContent>
</Card>
</div>
)
}