Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
89ab10a
Fixed run button state reset on route change and state stale issue wh…
ParamThakkar123 Nov 6, 2025
1212164
Fixed the error when model was appearing on the foundation page even …
ParamThakkar123 Nov 6, 2025
5715b60
Fixes
ParamThakkar123 Nov 7, 2025
21606dc
Merge branch 'main' of https://github.com/transformerlab/transformerl…
ParamThakkar123 Nov 7, 2025
5682cbf
Fixes
ParamThakkar123 Nov 8, 2025
3781b62
Rebase
ParamThakkar123 Nov 13, 2025
b36e867
Fixed undefined variables
ParamThakkar123 Nov 14, 2025
67cd9b3
Merge branch 'main' of https://github.com/transformerlab/transformerl…
ParamThakkar123 Nov 14, 2025
1c6a638
Merge branch 'main' of https://github.com/transformerlab/transformerl…
ParamThakkar123 Nov 16, 2025
ecb88c2
Merge branch 'main' of https://github.com/transformerlab/transformerl…
ParamThakkar123 Nov 18, 2025
90cf407
Merge branch 'main' of https://github.com/transformerlab/transformerl…
ParamThakkar123 Nov 18, 2025
4845123
Merge branch 'main' of https://github.com/transformerlab/transformerl…
ParamThakkar123 Nov 21, 2025
827d47c
Rebase
ParamThakkar123 Nov 28, 2025
8d8e324
Merge branch 'main' of https://github.com/transformerlab/transformerl…
ParamThakkar123 Nov 29, 2025
13d364a
Merge branch 'main' of https://github.com/transformerlab/transformerl…
ParamThakkar123 Dec 2, 2025
82d2b45
Merge branch 'main' of https://github.com/transformerlab/transformerl…
ParamThakkar123 Dec 3, 2025
fca0049
Merge branch 'main' into fix/foundation-page-state
deep1401 Dec 5, 2025
9fce33b
Merge branch 'main' into fix/foundation-page-state
dadmobile Dec 11, 2025
564d51a
Merge branch 'main' into fix/foundation-page-state
deep1401 Dec 12, 2025
56398a2
Merge branch 'main' of https://github.com/transformerlab/transformerl…
ParamThakkar123 Dec 13, 2025
fa0e706
Merge branch 'fix/foundation-page-state' of https://github.com/transf…
ParamThakkar123 Dec 13, 2025
f737dd6
Merge branch 'main' into fix/foundation-page-state
dadmobile Dec 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 181 additions & 0 deletions src/renderer/components/Experiment/Foundation/RunModelButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,173 @@ export default function RunModelButton({
setLogsDrawerOpen = null,
}) {
const [jobId, setJobId] = useState(null);
const storageKey = experimentInfo?.id
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Putting this in local storage seems like overkill? i'm guessing there was a problem with just using state?

? `RUN_MODEL_JOB.${experimentInfo.id}`
: null;
const [showRunSettings, setShowRunSettings] = useState(false);
const [inferenceSettings, setInferenceSettings] = useState({
inferenceEngine: null,
inferenceEngineFriendlyName: '',
});

useEffect(() => {
if (!storageKey || !window.storage) return;
(async () => {
try {
const stored = await window.storage.get(storageKey);
if (stored !== undefined && stored !== null) {
setJobId(stored);
}
} catch (e) {
// ignore storage errors
}
})();
}, [storageKey]);

useEffect(() => {
if (!storageKey || !window.storage) return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is so much new code for this one condition! I feel confident there must be a way to simplify/refactor this so it's not so complex? I feel we've already made it too complex and that's why we are having these bugs in the first place.

Let me take a closer look when I'm not on a plane and see if I can make some suggestions.

if (!experimentInfo?.id) return;

let cancelled = false;
let intervalId: number | null = null;

const pollJob = async () => {
if (jobId === null) return;

let currentJobId = jobId;
if (jobId === -1) {
try {
if (storageKey && window.storage) {
const stored = await window.storage.get(storageKey);
if (stored === null || stored === undefined) {
if (!cancelled) setJobId(null);
return;
}
if (stored !== -1) {
currentJobId = stored;
if (!cancelled) setJobId(stored);
} else {
try {
const listRes = await fetch(
getAPIFullPath('jobs', ['list'], {
experimentId: experimentInfo.id,
}),
{ method: 'GET' },
);
if (listRes.ok) {
const jobsList = await listRes.json();
const recentFailure = Array.isArray(jobsList)
? jobsList.find((j) =>
['FAILED', 'ERROR', 'STOPPED', 'UNAUTHORIZED'].includes(
(j?.status || '').toString().toUpperCase(),
),
)
: null;
if (recentFailure) {
try {
if (storageKey && window.storage)
await window.storage.set(storageKey, null);
} catch {}
if (!cancelled) {
setJobId(null);
if (setLogsDrawerOpen) setLogsDrawerOpen(true);
alert(
`Model start failed: ${recentFailure?.message || recentFailure?.job_data?.error_msg || recentFailure?.status}`,
);
}
return;
}
}
} catch (err) {
// ignore list errors
}
return;
}
}
} catch (e) {
// ignore storage errors and fall back to regular polling
}
}

try {
const res = await fetch(
getAPIFullPath('jobs', ['get'], {
id: currentJobId,
experimentId: experimentInfo.id,
}),
{ method: 'GET' },
);
if (!res.ok) {
try {
if (storageKey && window.storage)
await window.storage.set(storageKey, null);
} catch {}
if (!cancelled) setJobId(null);
return;
}
const job = await res.json();
const status = (job?.status || '').toString().toUpperCase();

// If model is running or completed, clear pending marker and stop buffering
if (
status === 'RUNNING' ||
status === 'COMPLETE' ||
status === 'SUCCESS'
) {
try {
await window.storage.set(storageKey, null);
} catch {}
if (!cancelled) setJobId(null);
return;
}

if (
status === 'FAILED' ||
status === 'STOPPED' ||
status === 'ERROR' ||
status === 'UNAUTHORIZED'
) {
try {
await window.storage.set(storageKey, null);
} catch {}
if (!cancelled) {
setJobId(null);
if (setLogsDrawerOpen) setLogsDrawerOpen(true);
alert(
`Model start failed: ${job?.message || job?.job_data?.error_msg || status}`,
);
}
return;
}
} catch (e) {
// ignore transient poll errors
}
};

// Start polling every 2s and poll immediately
intervalId = window.setInterval(pollJob, 2000) as unknown as number;
pollJob();

return () => {
cancelled = true;
if (intervalId) clearInterval(intervalId);
};
}, [jobId, storageKey, experimentInfo?.id, setLogsDrawerOpen]);

useEffect(() => {
if (!storageKey || !window.storage) return;
(async () => {
try {
if (Array.isArray(models) && models.length > 0) {
await window.storage.set(storageKey, null);
setJobId(null);
}
} catch (e) {
// ignore storage errors
}
})();
}, [models, storageKey]);

const { data, error, isLoading } = useAPI(
'experiment',
['getScriptsOfTypeWithoutFilter'],
Expand Down Expand Up @@ -296,6 +457,14 @@ export default function RunModelButton({
return;
}

if (storageKey && window.storage) {
try {
await window.storage.set(storageKey, -1);
} catch (e) {
// ignore storage errors
}
}

setJobId(-1);

const inferenceEngine = inferenceSettings?.inferenceEngine;
Expand All @@ -310,6 +479,11 @@ export default function RunModelButton({
experimentInfo?.id,
);
if (response?.status == 'error') {
if (storageKey && window.storage) {
try {
await window.storage.set(storageKey, null);
} catch (e) {}
}
if (setLogsDrawerOpen) {
setLogsDrawerOpen(true);
}
Expand All @@ -328,6 +502,13 @@ export default function RunModelButton({
return;
}
const job_id = response?.job_id;
if (storageKey && window.storage) {
try {
await window.storage.set(storageKey, job_id);
} catch (e) {
// ignore storage errors
}
}
setJobId(job_id);
mutate();
}}
Expand Down
19 changes: 13 additions & 6 deletions src/renderer/components/ModelZoo/LocalModelsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ import { filterByFilters, licenseTypes, modelTypes } from '../../lib/utils';
import TinyMLXLogo from '../Shared/TinyMLXLogo';
import SelectButton from '../Experiment/SelectButton';
import { RiChatAiLine, RiImageAiLine } from 'react-icons/ri';
import { useExperimentInfo } from 'renderer/lib/ExperimentInfoContext';

type Order = 'asc' | 'desc';

const LocalModelsTable = ({
export default function LocalModelsTable({
models,
isLoading,
mutateModels,
Expand All @@ -44,13 +45,14 @@ const LocalModelsTable = ({
pickAModelMode = false,
showOnlyGeneratedModels = false,
isEmbeddingMode = false,
experimentInfo = null,
}) => {
experimentInfo: experimentInfoProp = null,
}) {
const [order, setOrder] = useState<Order>('desc');
const [searchText, setSearchText] = useState('');
const [filters, setFilters] = useState({});

const navigate = useNavigate();
const { experimentInfo, experimentInfoMutate } = useExperimentInfo();

const renderFilters = () => (
<>
Expand Down Expand Up @@ -433,6 +435,13 @@ const LocalModelsTable = ({
}),
},
);

if (
typeof experimentInfoMutate ===
'function'
) {
experimentInfoMutate();
}
}
} catch (err) {
console.error(
Expand Down Expand Up @@ -481,6 +490,4 @@ const LocalModelsTable = ({
</Typography>
</>
);
};

export default LocalModelsTable;
}