Files
ComfyUI_frontend/src/services/customerEventsService.ts
Arjan Singh 0239a83da2 Update rh-test (as of 2025-10-11) (#6044)
## Summary

Tested these changes and confirmed that:
1. Feedback button shows.
2. You can run workflows and switch out models.
3. You can use the mask editor. (thank you @ric-yu for helping me
verify).

## Changes

A lot, please see commits.

Gets us up to date with `main` as of 10-11-2025.

---------

Co-authored-by: Simula_r <18093452+simula-r@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: snomiao <snomiao@gmail.com>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: DrJKL <DrJKL@users.noreply.github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Marwan Ahmed <155799754+marawan206@users.noreply.github.com>
Co-authored-by: DrJKL <DrJKL0424@gmail.com>
Co-authored-by: Rizumu Ayaka <rizumu@ayaka.moe>
Co-authored-by: Comfy Org PR Bot <snomiao+comfy-pr@gmail.com>
Co-authored-by: AustinMroz <4284322+AustinMroz@users.noreply.github.com>
Co-authored-by: Austin Mroz <austin@comfy.org>
Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: filtered <176114999+webfiltered@users.noreply.github.com>
Co-authored-by: Benjamin Lu <benceruleanlu@proton.me>
Co-authored-by: Jin Yi <jin12cc@gmail.com>
Co-authored-by: Robin Huang <robin.j.huang@gmail.com>
2025-10-14 15:59:26 -07:00

209 lines
5.3 KiB
TypeScript

import type { AxiosError, AxiosResponse } from 'axios'
import axios from 'axios'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { COMFY_API_BASE_URL } from '@/config/comfyApi'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
import type { components, operations } from '@/types/comfyRegistryTypes'
import { isAbortError } from '@/utils/typeGuardUtil'
export enum EventType {
CREDIT_ADDED = 'credit_added',
ACCOUNT_CREATED = 'account_created',
API_USAGE_STARTED = 'api_usage_started',
API_USAGE_COMPLETED = 'api_usage_completed'
}
type CustomerEventsResponse =
operations['GetCustomerEvents']['responses']['200']['content']['application/json']
type CustomerEventsResponseQuery =
operations['GetCustomerEvents']['parameters']['query']
export type AuditLog = components['schemas']['AuditLog']
const customerApiClient = axios.create({
baseURL: COMFY_API_BASE_URL,
headers: {
'Content-Type': 'application/json'
}
})
export const useCustomerEventsService = () => {
const isLoading = ref(false)
const error = ref<string | null>(null)
const { d } = useI18n()
const handleRequestError = (
err: unknown,
context: string,
routeSpecificErrors?: Record<number, string>
) => {
// Don't treat cancellation as an error
if (isAbortError(err)) return
let message: string
if (!axios.isAxiosError(err)) {
message = `${context} failed: ${err instanceof Error ? err.message : String(err)}`
} else {
const axiosError = err as AxiosError<{ message: string }>
const status = axiosError.response?.status
if (status && routeSpecificErrors?.[status]) {
message = routeSpecificErrors[status]
} else {
message =
axiosError.response?.data?.message ??
`${context} failed with status ${status}`
}
}
error.value = message
}
const executeRequest = async <T>(
requestCall: () => Promise<AxiosResponse<T>>,
options: {
errorContext: string
routeSpecificErrors?: Record<number, string>
}
): Promise<T | null> => {
const { errorContext, routeSpecificErrors } = options
isLoading.value = true
error.value = null
try {
const response = await requestCall()
return response.data
} catch (err) {
handleRequestError(err, errorContext, routeSpecificErrors)
return null
} finally {
isLoading.value = false
}
}
function formatEventType(eventType: string) {
switch (eventType) {
case 'credit_added':
return 'Credits Added'
case 'account_created':
return 'Account Created'
case 'api_usage_completed':
return 'API Usage'
default:
return eventType
}
}
function formatDate(dateString: string): string {
const date = new Date(dateString)
return d(date, {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})
}
function formatJsonKey(key: string) {
return key
.split('_')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')
}
function formatJsonValue(value: any) {
if (typeof value === 'number') {
// Format numbers with commas and decimals if needed
return value.toLocaleString()
}
if (typeof value === 'string' && value.match(/^\d{4}-\d{2}-\d{2}/)) {
// Format dates nicely
return new Date(value).toLocaleString()
}
return value
}
function getEventSeverity(eventType: string) {
switch (eventType) {
case 'credit_added':
return 'success'
case 'account_created':
return 'info'
case 'api_usage_completed':
return 'warning'
default:
return 'info'
}
}
function hasAdditionalInfo(event: AuditLog) {
const { amount, api_name, model, ...otherParams } = event.params || {}
return Object.keys(otherParams).length > 0
}
function getTooltipContent(event: AuditLog) {
const { ...params } = event.params || {}
return Object.entries(params)
.map(([key, value]) => {
const formattedKey = formatJsonKey(key)
const formattedValue = formatJsonValue(value)
return `<strong>${formattedKey}:</strong> ${formattedValue}`
})
.join('<br>')
}
function formatAmount(amountMicros?: number) {
if (!amountMicros) return '0.00'
return (amountMicros / 100).toFixed(2)
}
async function getMyEvents({
page = 1,
limit = 10
}: CustomerEventsResponseQuery = {}): Promise<CustomerEventsResponse | null> {
const errorContext = 'Fetching customer events'
const routeSpecificErrors = {
400: 'Invalid input, object invalid',
404: 'Not found'
}
// Get auth headers
const authHeaders = await useFirebaseAuthStore().getAuthHeader()
if (!authHeaders) {
error.value = 'Authentication header is missing'
return null
}
return executeRequest<CustomerEventsResponse>(
() =>
customerApiClient.get('/customers/events', {
params: { page, limit },
headers: authHeaders
}),
{ errorContext, routeSpecificErrors }
)
}
return {
// State
isLoading,
error,
// Methods
getMyEvents,
formatEventType,
getEventSeverity,
formatAmount,
hasAdditionalInfo,
formatDate,
formatJsonKey,
formatJsonValue,
getTooltipContent
}
}