page.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. "use client";
  2. import { useState, useEffect, useRef, useCallback } from "react";
  3. import { Header } from "@/components/layout";
  4. import { AppLayout } from "@/components/layout";
  5. import { Button } from "@/components/ui/button";
  6. import { Card, CardContent } from "@/components/ui/card";
  7. import { Loader2, RefreshCw, AlertCircle } from "lucide-react";
  8. import { EnhancedQueueList } from "@/components/features/queue/enhanced-queue-list";
  9. import {
  10. apiClient,
  11. type QueueStatus,
  12. type JobInfo,
  13. } from "@/lib/api";
  14. export default function QueuePage() {
  15. const [queueStatus, setQueueStatus] = useState<QueueStatus | null>(null);
  16. const [loading, setLoading] = useState(true);
  17. const [actionLoading, setActionLoading] = useState(false);
  18. const [error, setError] = useState<string | null>(null);
  19. const intervalRef = useRef<NodeJS.Timeout | null>(null);
  20. const isMountedRef = useRef(true);
  21. // Fetch queue status from the API
  22. const fetchQueueStatus = useCallback(async () => {
  23. if (!isMountedRef.current) return;
  24. try {
  25. const status = await apiClient.getQueueStatus();
  26. if (isMountedRef.current) {
  27. setQueueStatus(status);
  28. setError(null);
  29. }
  30. } catch (err) {
  31. if (isMountedRef.current) {
  32. const errorMessage = err instanceof Error ? err.message : "Failed to fetch queue status";
  33. setError(errorMessage);
  34. console.error("Error fetching queue status:", err);
  35. }
  36. } finally {
  37. if (isMountedRef.current) {
  38. setLoading(false);
  39. }
  40. }
  41. }, []);
  42. // Set up auto-refresh with proper cleanup
  43. useEffect(() => {
  44. // Initial fetch
  45. fetchQueueStatus();
  46. // Set up interval for auto-refresh (every 3 seconds)
  47. intervalRef.current = setInterval(() => {
  48. fetchQueueStatus();
  49. }, 3000);
  50. // Cleanup function
  51. return () => {
  52. isMountedRef.current = false;
  53. if (intervalRef.current) {
  54. clearInterval(intervalRef.current);
  55. intervalRef.current = null;
  56. }
  57. };
  58. }, [fetchQueueStatus]);
  59. // Handle manual refresh
  60. const handleRefresh = useCallback(() => {
  61. setLoading(true);
  62. fetchQueueStatus();
  63. }, [fetchQueueStatus]);
  64. // Handle job cancellation
  65. const handleCancelJob = useCallback(async (jobId: string) => {
  66. setActionLoading(true);
  67. try {
  68. await apiClient.cancelJob(jobId);
  69. console.log(`Job ${jobId} cancelled successfully`);
  70. // Refresh queue status after cancellation
  71. fetchQueueStatus();
  72. } catch (err) {
  73. const errorMessage = err instanceof Error ? err.message : "Failed to cancel job";
  74. console.error(`Failed to cancel job: ${errorMessage}`, err);
  75. } finally {
  76. setActionLoading(false);
  77. }
  78. }, [fetchQueueStatus]);
  79. // Handle queue clearing
  80. const handleClearQueue = useCallback(async () => {
  81. if (!queueStatus?.jobs.length) return;
  82. if (!confirm("Are you sure you want to clear all jobs from the queue? This action cannot be undone.")) {
  83. return;
  84. }
  85. setActionLoading(true);
  86. try {
  87. await apiClient.clearQueue();
  88. console.log("Queue cleared successfully");
  89. // Refresh queue status after clearing
  90. fetchQueueStatus();
  91. } catch (err) {
  92. const errorMessage = err instanceof Error ? err.message : "Failed to clear queue";
  93. console.error(`Failed to clear queue: ${errorMessage}`, err);
  94. } finally {
  95. setActionLoading(false);
  96. }
  97. }, [queueStatus?.jobs.length, fetchQueueStatus]);
  98. // Handle copying job parameters
  99. const handleCopyParameters = useCallback((job: JobInfo) => {
  100. try {
  101. const params = {
  102. prompt: job.prompt || "",
  103. negative_prompt: job.message || "",
  104. // Add other relevant parameters as needed
  105. };
  106. const paramsText = JSON.stringify(params, null, 2);
  107. navigator.clipboard.writeText(paramsText);
  108. console.log("Job parameters copied to clipboard");
  109. } catch (err) {
  110. console.error("Failed to copy parameters:", err);
  111. }
  112. }, []);
  113. return (
  114. <AppLayout>
  115. <Header
  116. title="Queue Management"
  117. description="Monitor and manage generation jobs in the queue"
  118. />
  119. <div className="container mx-auto p-6">
  120. {error && (
  121. <Card className="mb-6 border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-950/20">
  122. <CardContent className="pt-6">
  123. <div className="flex items-center gap-3 text-red-700 dark:text-red-300">
  124. <AlertCircle className="h-5 w-5" />
  125. <div>
  126. <h3 className="font-medium">Error loading queue</h3>
  127. <p className="text-sm">{error}</p>
  128. </div>
  129. <Button
  130. variant="outline"
  131. size="sm"
  132. onClick={handleRefresh}
  133. className="ml-auto"
  134. >
  135. <RefreshCw className="h-4 w-4 mr-2" />
  136. Retry
  137. </Button>
  138. </div>
  139. </CardContent>
  140. </Card>
  141. )}
  142. {loading && !queueStatus ? (
  143. <Card>
  144. <CardContent className="pt-6">
  145. <div className="flex items-center justify-center py-12">
  146. <Loader2 className="h-8 w-8 animate-spin mr-3" />
  147. <span>Loading queue status...</span>
  148. </div>
  149. </CardContent>
  150. </Card>
  151. ) : (
  152. <EnhancedQueueList
  153. queueStatus={queueStatus}
  154. loading={loading}
  155. onRefresh={handleRefresh}
  156. onCancelJob={handleCancelJob}
  157. onClearQueue={handleClearQueue}
  158. actionLoading={actionLoading}
  159. onCopyParameters={handleCopyParameters}
  160. />
  161. )}
  162. </div>
  163. </AppLayout>
  164. );
  165. }