| import React, { useRef, useState } from 'react' |
| import { |
| Title, |
| Text, |
| Button, |
| Anchor, |
| Slider, |
| Switch, |
| Divider, |
| Stack, |
| Box, |
| Loader, |
| Select, |
| NumberInput, |
| Badge, |
| Alert, |
| Group, |
| } from '@mantine/core' |
| import { IconCloudUpload, IconChartBar, IconAlertCircle } from '@tabler/icons-react' |
| import { useXRD } from '../context/XRDContext' |
| import ExampleDataPanel from './ExampleDataPanel' |
|
|
| const Controls = () => { |
| const { |
| rawData, |
| isLoading, |
| detectedWavelength, |
| userWavelength, |
| setUserWavelength, |
| wavelengthSource, |
| dataWarnings, |
| baselineCorrection, |
| setBaselineCorrection, |
| interpolationEnabled, |
| setInterpolationEnabled, |
| scalingEnabled, |
| setScalingEnabled, |
| interpolationStrategy, |
| setInterpolationStrategy, |
| handleFileUpload, |
| runInference, |
| MODEL_WAVELENGTH, |
| MODEL_MIN_2THETA, |
| MODEL_MAX_2THETA, |
| } = useXRD() |
| |
| const fileInputRef = useRef(null) |
| const [showExamples, setShowExamples] = useState(true) |
| |
| const handleFileChange = async (event) => { |
| const file = event.target.files?.[0] |
| if (file) { |
| const success = await handleFileUpload(file) |
| if (success) setShowExamples(false) |
| |
| if (fileInputRef.current) { |
| fileInputRef.current.value = '' |
| } |
| } |
| } |
| |
| const handleUploadClick = () => { |
| fileInputRef.current?.click() |
| } |
| |
| return ( |
| <Stack gap="md"> |
| {/* File Upload */} |
| <Box> |
| <input |
| ref={fileInputRef} |
| type="file" |
| accept=".xy,.csv,.txt,.cif,.dif" |
| onChange={handleFileChange} |
| style={{ display: 'none' }} |
| /> |
| <Button |
| fullWidth |
| leftSection={<IconCloudUpload size={20} />} |
| onClick={handleUploadClick} |
| size="md" |
| > |
| Upload XRD Data |
| </Button> |
| <Group justify="space-between" mt="xs"> |
| <Text size="sm" c="dimmed"> |
| Formats: .xy, .cif, .dif |
| </Text> |
| <Anchor |
| size="sm" |
| component="button" |
| type="button" |
| onClick={() => setShowExamples((v) => !v)} |
| > |
| {showExamples ? 'Hide samples' : 'Try samples'} |
| </Anchor> |
| </Group> |
| </Box> |
| |
| {showExamples && <ExampleDataPanel onSelect={() => setShowExamples(false)} />} |
| |
| {rawData && ( |
| <> |
| <Divider /> |
| |
| {/* Data Info */} |
| <Box p="md" style={{ backgroundColor: '#f8f9fa', borderRadius: '8px' }}> |
| <Stack gap="xs"> |
| <Text size="sm" c="dimmed"> |
| Data Points: {rawData.x.length} |
| </Text> |
| <Text size="sm" c="dimmed"> |
| Range: {Math.min(...rawData.x).toFixed(2)}° - {Math.max(...rawData.x).toFixed(2)}° |
| </Text> |
| </Stack> |
| </Box> |
| |
| {/* Warnings */} |
| {dataWarnings.length > 0 && ( |
| <Alert icon={<IconAlertCircle size={16} />} color="yellow" variant="light"> |
| <Stack gap="xs"> |
| {dataWarnings.map((warning, idx) => ( |
| <Text key={idx} size="xs">{warning}</Text> |
| ))} |
| </Stack> |
| </Alert> |
| )} |
| |
| <Divider /> |
| |
| {/* Wavelength Configuration */} |
| <Title order={4}>Wavelength</Title> |
| |
| {detectedWavelength && ( |
| <Badge color="blue" variant="light" size="sm" mb="xs"> |
| Detected: {detectedWavelength.toFixed(4)} Å |
| </Badge> |
| )} |
| |
| <NumberInput |
| label="Wavelength (Å)" |
| description="Cu Kα=1.5406, Mo Kα=0.7107, Synch=0.6199" |
| value={userWavelength} |
| onChange={(value) => setUserWavelength(value || MODEL_WAVELENGTH)} |
| min={0.1} |
| max={3.0} |
| step={0.0001} |
| precision={4} |
| size="sm" |
| decimalScale={4} |
| /> |
| |
| <Group gap="xs" mt="xs"> |
| <Button size="xs" variant="light" onClick={() => setUserWavelength(1.5406)}>Cu Kα</Button> |
| <Button size="xs" variant="light" onClick={() => setUserWavelength(0.7107)}>Mo Kα</Button> |
| <Button size="xs" variant="light" onClick={() => setUserWavelength(0.6199)}>Synch</Button> |
| </Group> |
| |
| <Box mt="md" p="md" style={{ backgroundColor: '#e7f5ff', borderRadius: '8px', border: '1px solid #91a7ff' }}> |
| <Text size="sm" fw={600} c="#1864ab" mb={4}> |
| Training Data Specs: |
| </Text> |
| <Text size="sm" c="#364fc7" style={{ lineHeight: 1.6 }}> |
| • λ: {MODEL_WAVELENGTH} Å (20 keV)<br/> |
| • 2θ: {MODEL_MIN_2THETA}°-{MODEL_MAX_2THETA}° (8192 pts)<br/> |
| • Intensity: 0-100 scaled |
| </Text> |
| </Box> |
| |
| <Divider /> |
| |
| {/* Preprocessing Controls */} |
| <Title order={4}>Preprocessing</Title> |
| |
| <Switch |
| label="Baseline Correction" |
| checked={baselineCorrection} |
| onChange={(event) => setBaselineCorrection(event.currentTarget.checked)} |
| /> |
| |
| <Switch |
| label="Signal Scaling (0-100)" |
| description="Normalize to model input range" |
| checked={scalingEnabled} |
| onChange={(event) => setScalingEnabled(event.currentTarget.checked)} |
| /> |
| |
| <Select |
| label="Interpolation Strategy" |
| description="Resampling method for 8192 points" |
| value={interpolationStrategy} |
| onChange={(value) => { |
| if (value) setInterpolationStrategy(value) |
| }} |
| data={[ |
| { value: 'linear', label: 'Linear' }, |
| { value: 'cubic', label: 'Cubic Spline' }, |
| ]} |
| size="sm" |
| allowDeselect={false} |
| /> |
| |
| <Divider /> |
| </> |
| )} |
| |
| {} |
| {rawData && ( |
| <Button |
| fullWidth |
| color="violet" |
| leftSection={isLoading ? <Loader size={18} color="white" /> : <IconChartBar size={20} />} |
| onClick={runInference} |
| disabled={isLoading} |
| size="lg" |
| style={{ marginTop: 'auto' }} |
| > |
| {isLoading ? 'Analyzing...' : 'Run Analysis'} |
| </Button> |
| )} |
| |
|
|
| </Stack> |
| ) |
| } |
|
|
| export default Controls |
|
|