SurfSense/surfsense_web/components/new-chat/chat-header.tsx

172 lines
5.4 KiB
TypeScript
Raw Normal View History

2025-12-23 01:16:25 -08:00
"use client";
import { useCallback, useEffect, useState } from "react";
Epic 5 Complete: Billing, Subscriptions, and Admin Features Resolve all 5 deferred items from Epic 5 adversarial code review: - Migration 124: Add CASCADE to subscriptionstatus enum drop (prevent orphaned references) - Stripe rate limiting: In-memory per-user limiter (20 calls/60s) on verify-checkout-session - Subscription request cooldown: 24h cooldown before resubmitting rejected requests - Token reset date: Initialize on first subscription activation - Checkout URL validation: Confirmed HTTPS-only (Stripe always returns HTTPS) Implement Story 5.4 (Usage Tracking & Rate Limit Enforcement): - Page quota pre-check at HTTP upload layer - Extend UserRead schema with token quota fields - Frontend 402 error handling in document upload - Quota indicator in dashboard sidebar Story 5.5 (Admin Seed & Approval Flow): - Seed admin user migration with default credentials warning - Subscription approval/rejection routes with admin guard - 24h rejection cooldown enforcement Story 5.6 (Admin-Only Model Config): - Global model config visible across all search spaces - Per-search-space model configs with user access control - Superuser CRUD for global configs Additional fixes from code review: - PageLimitService: PAST_DUE subscriptions enforce free-tier limits - TokenQuotaService: PAST_DUE subscriptions enforce free-tier limits - Config routes: Fixed user_id.is_(None) filter on mutation endpoints - Stripe webhook: Added guard against silent plan downgrade on unrecognized price_id All changes formatted with Ruff (Python) and Biome (TypeScript). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 03:54:45 +07:00
import { useAtomValue, useSetAtom } from "jotai";
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
import { selectedSystemModelIdAtom } from "@/atoms/new-llm-config/system-models-query.atoms";
2026-03-30 01:50:41 +05:30
import { ImageConfigDialog } from "@/components/shared/image-config-dialog";
import { ModelConfigDialog } from "@/components/shared/model-config-dialog";
import { VisionConfigDialog } from "@/components/shared/vision-config-dialog";
2025-12-23 01:16:25 -08:00
import type {
GlobalImageGenConfig,
2025-12-23 01:16:25 -08:00
GlobalNewLLMConfig,
GlobalVisionLLMConfig,
ImageGenerationConfig,
2025-12-23 01:16:25 -08:00
NewLLMConfigPublic,
VisionLLMConfig,
2025-12-23 01:16:25 -08:00
} from "@/contracts/types/new-llm-config.types";
import { isCloud } from "@/lib/env-config";
2025-12-23 01:16:25 -08:00
import { ModelSelector } from "./model-selector";
import { SystemModelSelector } from "./system-model-selector";
2025-12-23 01:16:25 -08:00
interface ChatHeaderProps {
searchSpaceId: number;
className?: string;
2025-12-23 01:16:25 -08:00
}
export function ChatHeader({ searchSpaceId, className }: ChatHeaderProps) {
Epic 5 Complete: Billing, Subscriptions, and Admin Features Resolve all 5 deferred items from Epic 5 adversarial code review: - Migration 124: Add CASCADE to subscriptionstatus enum drop (prevent orphaned references) - Stripe rate limiting: In-memory per-user limiter (20 calls/60s) on verify-checkout-session - Subscription request cooldown: 24h cooldown before resubmitting rejected requests - Token reset date: Initialize on first subscription activation - Checkout URL validation: Confirmed HTTPS-only (Stripe always returns HTTPS) Implement Story 5.4 (Usage Tracking & Rate Limit Enforcement): - Page quota pre-check at HTTP upload layer - Extend UserRead schema with token quota fields - Frontend 402 error handling in document upload - Quota indicator in dashboard sidebar Story 5.5 (Admin Seed & Approval Flow): - Seed admin user migration with default credentials warning - Subscription approval/rejection routes with admin guard - 24h rejection cooldown enforcement Story 5.6 (Admin-Only Model Config): - Global model config visible across all search spaces - Per-search-space model configs with user access control - Superuser CRUD for global configs Additional fixes from code review: - PageLimitService: PAST_DUE subscriptions enforce free-tier limits - TokenQuotaService: PAST_DUE subscriptions enforce free-tier limits - Config routes: Fixed user_id.is_(None) filter on mutation endpoints - Stripe webhook: Added guard against silent plan downgrade on unrecognized price_id All changes formatted with Ruff (Python) and Biome (TypeScript). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 03:54:45 +07:00
const { data: currentUser } = useAtomValue(currentUserAtom);
const isAdmin = !!currentUser?.is_superuser;
// Reset system model selection when search space changes
const setSelectedSystemModelId = useSetAtom(selectedSystemModelIdAtom);
useEffect(() => {
setSelectedSystemModelId(null);
}, [searchSpaceId, setSelectedSystemModelId]);
// LLM config dialog state
const [dialogOpen, setDialogOpen] = useState(false);
2025-12-23 01:16:25 -08:00
const [selectedConfig, setSelectedConfig] = useState<
NewLLMConfigPublic | GlobalNewLLMConfig | null
>(null);
const [isGlobal, setIsGlobal] = useState(false);
const [dialogMode, setDialogMode] = useState<"create" | "edit" | "view">("view");
2025-12-23 01:16:25 -08:00
// Image config dialog state
const [imageDialogOpen, setImageDialogOpen] = useState(false);
const [selectedImageConfig, setSelectedImageConfig] = useState<
ImageGenerationConfig | GlobalImageGenConfig | null
>(null);
const [isImageGlobal, setIsImageGlobal] = useState(false);
const [imageDialogMode, setImageDialogMode] = useState<"create" | "edit" | "view">("view");
// Vision config dialog state
const [visionDialogOpen, setVisionDialogOpen] = useState(false);
const [selectedVisionConfig, setSelectedVisionConfig] = useState<
VisionLLMConfig | GlobalVisionLLMConfig | null
>(null);
const [isVisionGlobal, setIsVisionGlobal] = useState(false);
const [visionDialogMode, setVisionDialogMode] = useState<"create" | "edit" | "view">("view");
// LLM handlers
const handleEditLLMConfig = useCallback(
2025-12-23 01:16:25 -08:00
(config: NewLLMConfigPublic | GlobalNewLLMConfig, global: boolean) => {
setSelectedConfig(config);
setIsGlobal(global);
setDialogMode(global ? "view" : "edit");
setDialogOpen(true);
2025-12-23 01:16:25 -08:00
},
[]
);
const handleAddNewLLM = useCallback(() => {
2025-12-23 01:16:25 -08:00
setSelectedConfig(null);
setIsGlobal(false);
setDialogMode("create");
setDialogOpen(true);
2025-12-23 01:16:25 -08:00
}, []);
const handleDialogClose = useCallback((open: boolean) => {
setDialogOpen(open);
if (!open) setSelectedConfig(null);
2025-12-23 01:16:25 -08:00
}, []);
// Image model handlers
2026-02-05 16:43:48 -08:00
const handleAddImageModel = useCallback(() => {
setSelectedImageConfig(null);
setIsImageGlobal(false);
setImageDialogMode("create");
setImageDialogOpen(true);
}, []);
const handleEditImageConfig = useCallback(
(config: ImageGenerationConfig | GlobalImageGenConfig, global: boolean) => {
setSelectedImageConfig(config);
setIsImageGlobal(global);
setImageDialogMode(global ? "view" : "edit");
setImageDialogOpen(true);
},
[]
);
const handleImageDialogClose = useCallback((open: boolean) => {
setImageDialogOpen(open);
if (!open) setSelectedImageConfig(null);
}, []);
2026-02-05 16:43:48 -08:00
// Vision model handlers
const handleAddVisionModel = useCallback(() => {
setSelectedVisionConfig(null);
setIsVisionGlobal(false);
setVisionDialogMode("create");
setVisionDialogOpen(true);
}, []);
const handleEditVisionConfig = useCallback(
(config: VisionLLMConfig | GlobalVisionLLMConfig, global: boolean) => {
setSelectedVisionConfig(config);
setIsVisionGlobal(global);
setVisionDialogMode(global ? "view" : "edit");
setVisionDialogOpen(true);
},
[]
);
const handleVisionDialogClose = useCallback((open: boolean) => {
setVisionDialogOpen(open);
if (!open) setSelectedVisionConfig(null);
}, []);
2025-12-23 01:16:25 -08:00
return (
2026-01-13 00:17:12 -08:00
<div className="flex items-center gap-2">
{isCloud() ? (
<SystemModelSelector className={className} />
) : (
<ModelSelector
Epic 5 Complete: Billing, Subscriptions, and Admin Features Resolve all 5 deferred items from Epic 5 adversarial code review: - Migration 124: Add CASCADE to subscriptionstatus enum drop (prevent orphaned references) - Stripe rate limiting: In-memory per-user limiter (20 calls/60s) on verify-checkout-session - Subscription request cooldown: 24h cooldown before resubmitting rejected requests - Token reset date: Initialize on first subscription activation - Checkout URL validation: Confirmed HTTPS-only (Stripe always returns HTTPS) Implement Story 5.4 (Usage Tracking & Rate Limit Enforcement): - Page quota pre-check at HTTP upload layer - Extend UserRead schema with token quota fields - Frontend 402 error handling in document upload - Quota indicator in dashboard sidebar Story 5.5 (Admin Seed & Approval Flow): - Seed admin user migration with default credentials warning - Subscription approval/rejection routes with admin guard - 24h rejection cooldown enforcement Story 5.6 (Admin-Only Model Config): - Global model config visible across all search spaces - Per-search-space model configs with user access control - Superuser CRUD for global configs Additional fixes from code review: - PageLimitService: PAST_DUE subscriptions enforce free-tier limits - TokenQuotaService: PAST_DUE subscriptions enforce free-tier limits - Config routes: Fixed user_id.is_(None) filter on mutation endpoints - Stripe webhook: Added guard against silent plan downgrade on unrecognized price_id All changes formatted with Ruff (Python) and Biome (TypeScript). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 03:54:45 +07:00
onEditLLM={isAdmin ? handleEditLLMConfig : undefined}
onAddNewLLM={isAdmin ? handleAddNewLLM : undefined}
onEditImage={isAdmin ? handleEditImageConfig : undefined}
onAddNewImage={isAdmin ? handleAddImageModel : undefined}
onEditVision={isAdmin ? handleEditVisionConfig : undefined}
onAddNewVision={isAdmin ? handleAddVisionModel : undefined}
className={className}
/>
)}
<ModelConfigDialog
open={dialogOpen}
onOpenChange={handleDialogClose}
2025-12-23 01:16:25 -08:00
config={selectedConfig}
isGlobal={isGlobal}
searchSpaceId={searchSpaceId}
mode={dialogMode}
2025-12-23 01:16:25 -08:00
/>
<ImageConfigDialog
open={imageDialogOpen}
onOpenChange={handleImageDialogClose}
config={selectedImageConfig}
isGlobal={isImageGlobal}
searchSpaceId={searchSpaceId}
mode={imageDialogMode}
/>
<VisionConfigDialog
open={visionDialogOpen}
onOpenChange={handleVisionDialogClose}
config={selectedVisionConfig}
isGlobal={isVisionGlobal}
searchSpaceId={searchSpaceId}
mode={visionDialogMode}
/>
2026-01-13 00:17:12 -08:00
</div>
2025-12-23 01:16:25 -08:00
);
}