@tangle-network/agent-app 0.46.8 → 0.46.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ import type { ModelOptionValue } from '../studio';
2
+ import type { ComposerType } from './studio-composer';
3
+ export interface PersistedComposerSelections {
4
+ v: 1;
5
+ type: ComposerType;
6
+ selectedModels: Partial<Record<ComposerType, string>>;
7
+ /** Option values are model-specific: two models in one lane may publish
8
+ * different enums for the same parameter. */
9
+ optionsByModel: Record<string, Record<string, ModelOptionValue>>;
10
+ }
11
+ export declare function loadComposerSelections(workspaceId: string): PersistedComposerSelections | null;
12
+ export declare function saveComposerSelections(workspaceId: string, snapshot: PersistedComposerSelections): void;
@@ -685,6 +685,58 @@ function optionValueLabel(param, value) {
685
685
  return text.charAt(0).toUpperCase() + text.slice(1);
686
686
  }
687
687
 
688
+ // src/studio-react/composer-persistence.ts
689
+ var COMPOSER_TYPES = ["image", "video", "speech"];
690
+ function storageKey(workspaceId) {
691
+ return `studio-composer:${workspaceId}`;
692
+ }
693
+ function isPlainObject(value) {
694
+ if (value === null || typeof value !== "object") return false;
695
+ const prototype = Object.getPrototypeOf(value);
696
+ return prototype === Object.prototype || prototype === null;
697
+ }
698
+ function isComposerType(value) {
699
+ return COMPOSER_TYPES.some((type) => type === value);
700
+ }
701
+ function isOptionValue(value) {
702
+ return typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
703
+ }
704
+ function parseSelections(value) {
705
+ if (!isPlainObject(value) || value.v !== 1 || !isComposerType(value.type)) return null;
706
+ if (!isPlainObject(value.selectedModels) || !isPlainObject(value.optionsByModel)) return null;
707
+ const selectedModels = {};
708
+ for (const type of COMPOSER_TYPES) {
709
+ const modelId = value.selectedModels[type];
710
+ if (typeof modelId === "string") selectedModels[type] = modelId;
711
+ }
712
+ const optionsByModel = {};
713
+ for (const [modelId, rawOptions] of Object.entries(value.optionsByModel)) {
714
+ if (!isPlainObject(rawOptions)) continue;
715
+ const options = {};
716
+ for (const [param, optionValue] of Object.entries(rawOptions)) {
717
+ if (isOptionValue(optionValue)) options[param] = optionValue;
718
+ }
719
+ optionsByModel[modelId] = options;
720
+ }
721
+ return { v: 1, type: value.type, selectedModels, optionsByModel };
722
+ }
723
+ function loadComposerSelections(workspaceId) {
724
+ if (typeof window === "undefined") return null;
725
+ try {
726
+ const raw = window.localStorage.getItem(storageKey(workspaceId));
727
+ return raw === null ? null : parseSelections(JSON.parse(raw));
728
+ } catch {
729
+ return null;
730
+ }
731
+ }
732
+ function saveComposerSelections(workspaceId, snapshot) {
733
+ if (typeof window === "undefined") return;
734
+ try {
735
+ window.localStorage.setItem(storageKey(workspaceId), JSON.stringify(snapshot));
736
+ } catch {
737
+ }
738
+ }
739
+
688
740
  // src/studio-react/studio-composer.tsx
689
741
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
690
742
  var SEGMENTS = [
@@ -760,8 +812,26 @@ function StudioComposer({
760
812
  const [referenceImageUrl, setReferenceImageUrl] = useState3(null);
761
813
  const [isSubmitting, setIsSubmitting] = useState3(false);
762
814
  const [error, setError] = useState3(null);
815
+ const [hydratedWorkspaceId, setHydratedWorkspaceId] = useState3(null);
763
816
  const submitLockRef = useRef2(false);
764
817
  const bandRef = useRef2(null);
818
+ const hydratedRef = useRef2(false);
819
+ const persistedOptionsRef = useRef2({});
820
+ useEffect3(() => {
821
+ hydratedRef.current = false;
822
+ setHydratedWorkspaceId(null);
823
+ if (!workspaceId) return;
824
+ const persisted = loadComposerSelections(workspaceId);
825
+ if (persisted) {
826
+ setType(persisted.type);
827
+ setSelectedModels(persisted.selectedModels);
828
+ persistedOptionsRef.current = persisted.optionsByModel;
829
+ } else {
830
+ persistedOptionsRef.current = {};
831
+ }
832
+ hydratedRef.current = true;
833
+ setHydratedWorkspaceId(workspaceId);
834
+ }, [workspaceId]);
765
835
  useEffect3(() => {
766
836
  if (!workspaceId) return;
767
837
  let cancelled = false;
@@ -814,13 +884,35 @@ function StudioComposer({
814
884
  );
815
885
  useEffect3(() => {
816
886
  setOptionValues((current) => {
817
- const next = reconcileOptionValues(options, current[type], {
887
+ const seed = { ...persistedOptionsRef.current[modelId], ...current[type] };
888
+ const next = reconcileOptionValues(options, seed, {
818
889
  allowCustomSize: supportsCustomImageSize(modelId)
819
890
  });
820
891
  const unchanged = Object.keys(next).length === Object.keys(current[type]).length && Object.entries(next).every(([key, value]) => current[type][key] === value);
821
892
  return unchanged ? current : { ...current, [type]: next };
822
893
  });
823
894
  }, [modelId, options, type]);
895
+ useEffect3(() => {
896
+ if (!workspaceId || !hydratedRef.current || hydratedWorkspaceId !== workspaceId) return;
897
+ if (!catalog && modelId && persistedOptionsRef.current[modelId] !== void 0 && !options) return;
898
+ const seed = { ...persistedOptionsRef.current[modelId], ...optionValues[type] };
899
+ const reconciled = reconcileOptionValues(options, seed, {
900
+ allowCustomSize: supportsCustomImageSize(modelId)
901
+ });
902
+ const reconciledReady = Object.keys(reconciled).length === Object.keys(optionValues[type]).length && Object.entries(reconciled).every(([key, value]) => optionValues[type][key] === value);
903
+ if (!reconciledReady) return;
904
+ const optionsByModel = {
905
+ ...persistedOptionsRef.current,
906
+ ...modelId ? { [modelId]: optionValues[type] } : {}
907
+ };
908
+ saveComposerSelections(workspaceId, {
909
+ v: 1,
910
+ type,
911
+ selectedModels,
912
+ optionsByModel
913
+ });
914
+ persistedOptionsRef.current = optionsByModel;
915
+ }, [catalog, hydratedWorkspaceId, modelId, optionValues, options, selectedModels, type, workspaceId]);
824
916
  const values = optionValues[type];
825
917
  const params = visibleParams(type, options);
826
918
  const audioMeta = type === "video" ? options?.audio : void 0;