import { useState, useRef, useEffect } from 'react';
import { GraphyAiSdk } from '@graphysdk/ai';
import type { GraphConfig } from '@graphysdk/core';
interface Props {
initialConfig: GraphConfig;
onUpdate: (config: GraphConfig) => void;
}
function ChartEditor({ initialConfig, onUpdate }: Props) {
const [progress, setProgress] = useState(0);
const [status, setStatus] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const abortRef = useRef<AbortController | null>(null);
const ai = useRef(
new GraphyAiSdk({
apiKey: process.env.NEXT_PUBLIC_GRAPHY_API_KEY,
baseUrl: 'https://agents.graphy.dev',
})
).current;
const handleGenerate = async (prompt: string) => {
// Cancel any in-flight request
abortRef.current?.abort();
abortRef.current = new AbortController();
setIsLoading(true);
setProgress(0);
setError(null);
try {
const stream = await ai.generateGraphStream(
{ config: initialConfig, userPrompt: prompt },
abortRef.current.signal
);
for await (const event of stream) {
if (event.type === 'progress') {
setProgress(event.percentage);
setStatus(event.message ?? '');
}
if (event.type === 'complete') {
onUpdate(event.data.config);
}
if (event.type === 'error') {
setError(event.error);
}
}
} catch (err) {
if (err instanceof Error && err.name !== 'AbortError') {
setError(err.message);
}
} finally {
setIsLoading(false);
}
};
// Cleanup on unmount
useEffect(() => {
return () => abortRef.current?.abort();
}, []);
return (
<div>
{isLoading && (
<div>
<progress value={progress} max={100} />
<span>{status}</span>
<button onClick={() => abortRef.current?.abort()}>Cancel</button>
</div>
)}
{error && <div className="error">{error}</div>}
<PromptInput onSubmit={handleGenerate} disabled={isLoading} />
</div>
);
}