@dickpy/dsh-imagegen 1.2.2 → 1.3.0
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.
- package/README.md +24 -11
- package/docs/images/community-qq.png +0 -0
- package/lib/client.js +2040 -906
- package/lib/client.js.map +1 -1
- package/lib/index.js +710 -109
- package/package.json +2 -2
- package/src/agent-image-tools.ts +132 -30
- package/src/client/ImageGenPanel.tsx +56 -20
- package/src/client/SettingsCard.tsx +505 -196
- package/src/client/channels-form.ts +263 -0
- package/src/client/image-toolview.tsx +19 -12
- package/src/client/locales.ts +112 -2
- package/src/client/settings-card.module.css +487 -0
- package/src/client/settings-scope.ts +56 -8
- package/src/engine.ts +107 -8
- package/src/gallery-store.ts +6 -0
- package/src/generation-runtime.ts +32 -5
- package/src/history-store.ts +6 -0
- package/src/image-format.ts +11 -0
- package/src/image-models.ts +1 -1
- package/src/index.ts +153 -47
- package/src/model-catalog.ts +98 -0
- package/src/presets.ts +63 -0
- package/src/protocol.ts +81 -6
- package/src/routes.ts +192 -24
|
@@ -534,3 +534,490 @@
|
|
|
534
534
|
transition: none;
|
|
535
535
|
}
|
|
536
536
|
}
|
|
537
|
+
|
|
538
|
+
/* --- channel list ------------------------------------------------------------------ */
|
|
539
|
+
|
|
540
|
+
.channelSection {
|
|
541
|
+
padding: 6px 0 2px;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.channelEmpty {
|
|
545
|
+
margin: 10px 0 0;
|
|
546
|
+
font-size: 12px;
|
|
547
|
+
line-height: 1.5;
|
|
548
|
+
color: var(--dsw-alias-label-tertiary);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
.channelList {
|
|
552
|
+
list-style: none;
|
|
553
|
+
margin: 10px 0 0;
|
|
554
|
+
padding: 0;
|
|
555
|
+
display: flex;
|
|
556
|
+
flex-direction: column;
|
|
557
|
+
gap: 6px;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
.channelRow {
|
|
561
|
+
display: flex;
|
|
562
|
+
align-items: center;
|
|
563
|
+
gap: 10px;
|
|
564
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
565
|
+
border-radius: 10px;
|
|
566
|
+
padding: 8px 10px;
|
|
567
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.channelRow[data-action] {
|
|
571
|
+
flex-wrap: wrap;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.channelDotReady,
|
|
575
|
+
.channelDotWarn {
|
|
576
|
+
flex: none;
|
|
577
|
+
width: 9px;
|
|
578
|
+
height: 9px;
|
|
579
|
+
border-radius: 50%;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
.channelDotReady {
|
|
583
|
+
background: var(--dsw-color-success, #2fbf71);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
.channelDotWarn {
|
|
587
|
+
background: var(--dsw-color-danger, #e5484d);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
.channelMain {
|
|
591
|
+
appearance: none;
|
|
592
|
+
flex: 1;
|
|
593
|
+
min-width: 0;
|
|
594
|
+
display: flex;
|
|
595
|
+
flex-direction: column;
|
|
596
|
+
gap: 3px;
|
|
597
|
+
padding: 0;
|
|
598
|
+
border: 0;
|
|
599
|
+
background: none;
|
|
600
|
+
font: inherit;
|
|
601
|
+
text-align: left;
|
|
602
|
+
cursor: pointer;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.channelName {
|
|
606
|
+
font-size: 13px;
|
|
607
|
+
font-weight: 600;
|
|
608
|
+
line-height: 1.4;
|
|
609
|
+
color: var(--dsw-alias-label-primary);
|
|
610
|
+
overflow: hidden;
|
|
611
|
+
text-overflow: ellipsis;
|
|
612
|
+
white-space: nowrap;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.channelMeta {
|
|
616
|
+
display: flex;
|
|
617
|
+
align-items: center;
|
|
618
|
+
flex-wrap: wrap;
|
|
619
|
+
gap: 4px 8px;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.channelHost {
|
|
623
|
+
font-size: 11px;
|
|
624
|
+
line-height: 1.5;
|
|
625
|
+
color: var(--dsw-alias-label-tertiary);
|
|
626
|
+
font-family: var(--dsw-font-family-mono, monospace);
|
|
627
|
+
overflow: hidden;
|
|
628
|
+
text-overflow: ellipsis;
|
|
629
|
+
white-space: nowrap;
|
|
630
|
+
max-width: 180px;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.channelBadge {
|
|
634
|
+
border-radius: 999px;
|
|
635
|
+
padding: 0 7px;
|
|
636
|
+
font-size: 11px;
|
|
637
|
+
line-height: 17px;
|
|
638
|
+
white-space: nowrap;
|
|
639
|
+
font-weight: 500;
|
|
640
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
641
|
+
color: var(--dsw-alias-label-secondary);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
.channelBadge[data-warn] {
|
|
645
|
+
color: var(--dsw-alias-label-error);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
.channelBadge[data-default] {
|
|
649
|
+
background: var(--dsw-alias-bg-module-poped, var(--dsw-alias-bg-module-platform));
|
|
650
|
+
color: var(--dsw-alias-brand-primary);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
.channelAction {
|
|
654
|
+
appearance: none;
|
|
655
|
+
flex: none;
|
|
656
|
+
border: 0;
|
|
657
|
+
background: none;
|
|
658
|
+
padding: 4px 8px;
|
|
659
|
+
border-radius: 6px;
|
|
660
|
+
font: inherit;
|
|
661
|
+
font-size: 12px;
|
|
662
|
+
line-height: 1.5;
|
|
663
|
+
color: var(--dsw-alias-label-secondary);
|
|
664
|
+
cursor: pointer;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.channelAction:hover:not(:disabled) {
|
|
668
|
+
color: var(--dsw-alias-label-primary);
|
|
669
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.channelAction[data-danger] {
|
|
673
|
+
color: var(--dsw-alias-label-error);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
.channelAction:disabled {
|
|
677
|
+
opacity: 0.45;
|
|
678
|
+
cursor: default;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
.channelDanger {
|
|
682
|
+
appearance: none;
|
|
683
|
+
flex: none;
|
|
684
|
+
border: 1px solid var(--dsw-alias-label-error);
|
|
685
|
+
border-radius: 6px;
|
|
686
|
+
padding: 3px 10px;
|
|
687
|
+
background: none;
|
|
688
|
+
color: var(--dsw-alias-label-error);
|
|
689
|
+
font: inherit;
|
|
690
|
+
font-size: 12px;
|
|
691
|
+
cursor: pointer;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
.channelDanger:hover:not(:disabled) {
|
|
695
|
+
background: color-mix(in srgb, var(--dsw-alias-label-error) 12%, transparent);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
.channelDanger:disabled {
|
|
699
|
+
opacity: 0.45;
|
|
700
|
+
cursor: default;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
.deleteConfirmText {
|
|
704
|
+
flex: 1;
|
|
705
|
+
min-width: 0;
|
|
706
|
+
font-size: 12px;
|
|
707
|
+
line-height: 1.5;
|
|
708
|
+
color: var(--dsw-alias-label-error);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
.channelAddRow {
|
|
712
|
+
display: flex;
|
|
713
|
+
gap: 8px;
|
|
714
|
+
margin-top: 10px;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
.channelAdd {
|
|
718
|
+
appearance: none;
|
|
719
|
+
min-height: 30px;
|
|
720
|
+
border: 1px dashed var(--dsw-alias-border-l2);
|
|
721
|
+
border-radius: 8px;
|
|
722
|
+
padding: 0 12px;
|
|
723
|
+
background: transparent;
|
|
724
|
+
color: var(--dsw-alias-label-secondary);
|
|
725
|
+
font: inherit;
|
|
726
|
+
font-size: 12px;
|
|
727
|
+
cursor: pointer;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
.channelAdd:hover:not(:disabled) {
|
|
731
|
+
color: var(--dsw-alias-brand-primary);
|
|
732
|
+
border-color: var(--dsw-alias-brand-primary);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
.channelAdd:disabled {
|
|
736
|
+
opacity: 0.45;
|
|
737
|
+
cursor: default;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
.spacer {
|
|
741
|
+
flex: 1;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/* --- editor dialog ------------------------------------------------------------------- */
|
|
745
|
+
|
|
746
|
+
.editorBackdrop {
|
|
747
|
+
position: fixed;
|
|
748
|
+
inset: 0;
|
|
749
|
+
z-index: 120;
|
|
750
|
+
display: flex;
|
|
751
|
+
align-items: flex-start;
|
|
752
|
+
justify-content: center;
|
|
753
|
+
padding: 9vh 16px 16px;
|
|
754
|
+
background: color-mix(in srgb, var(--dsw-alias-bg-layer-1, #000) 45%, transparent);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
.editorPanel {
|
|
758
|
+
width: min(560px, 100%);
|
|
759
|
+
max-height: 82vh;
|
|
760
|
+
overflow-y: auto;
|
|
761
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
762
|
+
border-radius: 14px;
|
|
763
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
764
|
+
box-shadow: 0 18px 50px rgb(0 0 0 / 0.35);
|
|
765
|
+
padding: 16px;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
.editorHeader {
|
|
769
|
+
display: flex;
|
|
770
|
+
align-items: flex-start;
|
|
771
|
+
justify-content: space-between;
|
|
772
|
+
gap: 12px;
|
|
773
|
+
padding-bottom: 10px;
|
|
774
|
+
border-bottom: 1px solid var(--dsw-alias-border-l2);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
.editorClose {
|
|
778
|
+
appearance: none;
|
|
779
|
+
flex: none;
|
|
780
|
+
width: 26px;
|
|
781
|
+
height: 26px;
|
|
782
|
+
border: 0;
|
|
783
|
+
border-radius: 6px;
|
|
784
|
+
background: none;
|
|
785
|
+
color: var(--dsw-alias-label-tertiary);
|
|
786
|
+
font: inherit;
|
|
787
|
+
font-size: 16px;
|
|
788
|
+
line-height: 26px;
|
|
789
|
+
cursor: pointer;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
.editorClose:hover {
|
|
793
|
+
color: var(--dsw-alias-label-primary);
|
|
794
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
.editorField {
|
|
798
|
+
display: flex;
|
|
799
|
+
flex-direction: column;
|
|
800
|
+
gap: 6px;
|
|
801
|
+
padding: 12px 0 0;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
.editorDivider {
|
|
805
|
+
height: 1px;
|
|
806
|
+
margin: 14px 0 4px;
|
|
807
|
+
background: var(--dsw-alias-border-l2);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
.editorSectionHeader {
|
|
811
|
+
display: flex;
|
|
812
|
+
align-items: center;
|
|
813
|
+
justify-content: space-between;
|
|
814
|
+
gap: 12px;
|
|
815
|
+
padding: 10px 0 4px;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
.editorSectionHeader .label {
|
|
819
|
+
flex: 1;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
.editorTools {
|
|
823
|
+
display: flex;
|
|
824
|
+
flex-direction: column;
|
|
825
|
+
gap: 8px;
|
|
826
|
+
margin-top: 10px;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
.editorFooter {
|
|
830
|
+
display: flex;
|
|
831
|
+
align-items: center;
|
|
832
|
+
gap: 8px;
|
|
833
|
+
padding-top: 12px;
|
|
834
|
+
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
.detectOk {
|
|
838
|
+
margin: 0;
|
|
839
|
+
font-size: 12px;
|
|
840
|
+
line-height: 1.5;
|
|
841
|
+
color: var(--dsw-color-success, var(--dsw-alias-label-secondary));
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/* --- model catalog rows ---------------------------------------------------------------- */
|
|
845
|
+
|
|
846
|
+
.modelRows {
|
|
847
|
+
list-style: none;
|
|
848
|
+
margin: 8px 0 0;
|
|
849
|
+
padding: 0;
|
|
850
|
+
display: flex;
|
|
851
|
+
flex-direction: column;
|
|
852
|
+
gap: 8px;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
.modelRow {
|
|
856
|
+
display: flex;
|
|
857
|
+
align-items: center;
|
|
858
|
+
justify-content: space-between;
|
|
859
|
+
gap: 10px;
|
|
860
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
861
|
+
border-radius: 10px;
|
|
862
|
+
padding: 8px 10px;
|
|
863
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
.modelRowInputs {
|
|
867
|
+
flex: 1;
|
|
868
|
+
min-width: 0;
|
|
869
|
+
display: flex;
|
|
870
|
+
align-items: center;
|
|
871
|
+
gap: 6px;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
.modelRowInputs .input {
|
|
875
|
+
flex: 1;
|
|
876
|
+
min-width: 0;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
.modelArrow {
|
|
880
|
+
flex: none;
|
|
881
|
+
color: var(--dsw-alias-label-tertiary);
|
|
882
|
+
font-size: 12px;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
.modelRowBadges {
|
|
886
|
+
flex: none;
|
|
887
|
+
display: flex;
|
|
888
|
+
align-items: center;
|
|
889
|
+
gap: 6px;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
.modelBadge {
|
|
893
|
+
border-radius: 999px;
|
|
894
|
+
padding: 0 7px;
|
|
895
|
+
font-size: 11px;
|
|
896
|
+
line-height: 17px;
|
|
897
|
+
white-space: nowrap;
|
|
898
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
899
|
+
color: var(--dsw-alias-label-tertiary);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
.modelBadge[data-verified] {
|
|
903
|
+
color: var(--dsw-color-success, var(--dsw-alias-label-secondary));
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
.modelBadge[data-warn] {
|
|
907
|
+
color: var(--dsw-alias-label-error);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
.modelRowRemove {
|
|
911
|
+
appearance: none;
|
|
912
|
+
flex: none;
|
|
913
|
+
width: 20px;
|
|
914
|
+
height: 20px;
|
|
915
|
+
border: 0;
|
|
916
|
+
border-radius: 4px;
|
|
917
|
+
padding: 0;
|
|
918
|
+
background: transparent;
|
|
919
|
+
color: var(--dsw-alias-label-tertiary);
|
|
920
|
+
font: inherit;
|
|
921
|
+
line-height: 20px;
|
|
922
|
+
cursor: pointer;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
.modelRowRemove:hover:not(:disabled) {
|
|
926
|
+
color: var(--dsw-alias-label-error);
|
|
927
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
.modelRowRemove:disabled {
|
|
931
|
+
opacity: 0.45;
|
|
932
|
+
cursor: default;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
.modelCandidate > .modelBadge {
|
|
936
|
+
flex: none;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/* --- preset picker ---------------------------------------------------------------------- */
|
|
940
|
+
|
|
941
|
+
.channelControls {
|
|
942
|
+
position: relative;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
.presetInline {
|
|
946
|
+
position: absolute;
|
|
947
|
+
left: 0;
|
|
948
|
+
bottom: calc(100% + 8px);
|
|
949
|
+
z-index: 10;
|
|
950
|
+
width: min(420px, calc(100vw - 48px));
|
|
951
|
+
max-height: min(440px, 60vh);
|
|
952
|
+
overflow-y: auto;
|
|
953
|
+
margin: 0;
|
|
954
|
+
padding: 12px;
|
|
955
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
956
|
+
border-radius: 10px;
|
|
957
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
958
|
+
box-shadow: 0 12px 30px rgb(0 0 0 / 0.3);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
.presetInlineHeader {
|
|
962
|
+
display: flex;
|
|
963
|
+
align-items: flex-start;
|
|
964
|
+
justify-content: space-between;
|
|
965
|
+
gap: 12px;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
.presetList {
|
|
969
|
+
display: flex;
|
|
970
|
+
flex-direction: column;
|
|
971
|
+
gap: 8px;
|
|
972
|
+
margin-top: 12px;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
.presetRow {
|
|
976
|
+
appearance: none;
|
|
977
|
+
display: flex;
|
|
978
|
+
flex-direction: column;
|
|
979
|
+
align-items: flex-start;
|
|
980
|
+
gap: 3px;
|
|
981
|
+
width: 100%;
|
|
982
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
983
|
+
border-radius: 10px;
|
|
984
|
+
padding: 10px 12px;
|
|
985
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
986
|
+
color: inherit;
|
|
987
|
+
font: inherit;
|
|
988
|
+
text-align: left;
|
|
989
|
+
cursor: pointer;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
.presetRow:hover:not(:disabled) {
|
|
993
|
+
border-color: var(--dsw-alias-brand-primary);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
.presetRow[data-custom] {
|
|
997
|
+
border-style: dashed;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
.presetRow:disabled {
|
|
1001
|
+
opacity: 0.5;
|
|
1002
|
+
cursor: default;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
.presetName {
|
|
1006
|
+
font-size: 13px;
|
|
1007
|
+
font-weight: 600;
|
|
1008
|
+
line-height: 1.4;
|
|
1009
|
+
color: var(--dsw-alias-label-primary);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
.presetMeta {
|
|
1013
|
+
font-size: 11px;
|
|
1014
|
+
line-height: 1.5;
|
|
1015
|
+
color: var(--dsw-alias-label-tertiary);
|
|
1016
|
+
font-family: var(--dsw-font-family-mono, monospace);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
.presetHint {
|
|
1020
|
+
font-size: 12px;
|
|
1021
|
+
line-height: 1.5;
|
|
1022
|
+
color: var(--dsw-alias-label-secondary);
|
|
1023
|
+
}
|
|
@@ -14,21 +14,32 @@ import {
|
|
|
14
14
|
type SettingsScopeSnapshot,
|
|
15
15
|
type SnapshotStore,
|
|
16
16
|
} from '@deepseek-ai/dsh-client-runtime/client'
|
|
17
|
-
import { SETTINGS_API } from '../protocol.ts'
|
|
17
|
+
import { SETTINGS_API, type ChannelConfig } from '../protocol.ts'
|
|
18
18
|
|
|
19
19
|
/** The fields this plugin's settings card edits. */
|
|
20
20
|
export interface ImageGenConfig {
|
|
21
21
|
enabled?: boolean
|
|
22
22
|
announceToAgent?: boolean
|
|
23
23
|
allowAgentImageGeneration?: boolean
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
/** Configured channels (each: name, endpoint, model catalog). */
|
|
25
|
+
channels?: ChannelConfig[]
|
|
26
|
+
/** Per-channel API keys, keyed by channel id. The redacted wire view returns
|
|
27
|
+
* this as an empty object; key presence comes from the secrets sidecar. */
|
|
28
|
+
channelSecrets?: Record<string, string>
|
|
29
|
+
/** Channel used when a request does not name one. */
|
|
30
|
+
defaultChannelId?: string
|
|
27
31
|
promptApiUrl?: string
|
|
28
32
|
promptApiKey?: string
|
|
29
33
|
promptModel?: string
|
|
34
|
+
/* ----- deprecated legacy single-endpoint fields (migrated to channels) ----- */
|
|
35
|
+
apiUrl?: string
|
|
36
|
+
apiKey?: string
|
|
37
|
+
imageModels?: string[]
|
|
30
38
|
}
|
|
31
39
|
|
|
40
|
+
/** One settings path-op as the bridge consumes it. */
|
|
41
|
+
export type SettingsOp = { op: 'set'; path: string[]; value: unknown } | { op: 'unset'; path: string[] }
|
|
42
|
+
|
|
32
43
|
/** Wire shape of one namespace view from the bridge. */
|
|
33
44
|
interface BridgeView {
|
|
34
45
|
ns: string
|
|
@@ -140,11 +151,18 @@ class BridgeScopeController<T> implements SettingsScope<T> {
|
|
|
140
151
|
}
|
|
141
152
|
|
|
142
153
|
set(field: string, value: unknown): Promise<void> {
|
|
143
|
-
return this.enqueue(() => this.
|
|
154
|
+
return this.enqueue(() => this.writeOps([{ op: 'set', path: [field], value }]))
|
|
144
155
|
}
|
|
145
156
|
|
|
146
157
|
unset(field: string): Promise<void> {
|
|
147
|
-
return this.enqueue(() => this.
|
|
158
|
+
return this.enqueue(() => this.writeOps([{ op: 'unset', path: [field] }]))
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Apply several path ops in one revision-fenced mutate call (atomic save).
|
|
162
|
+
* Path ops may address plain-object fields (e.g. `channelSecrets.<id>`),
|
|
163
|
+
* but never navigate *inside* arrays — write array fields wholesale. */
|
|
164
|
+
mutateOps(ops: SettingsOp[]): Promise<void> {
|
|
165
|
+
return this.enqueue(() => this.writeOps(ops))
|
|
148
166
|
}
|
|
149
167
|
|
|
150
168
|
async dispose(): Promise<void> {
|
|
@@ -188,13 +206,13 @@ class BridgeScopeController<T> implements SettingsScope<T> {
|
|
|
188
206
|
this.accept(view, writable)
|
|
189
207
|
}
|
|
190
208
|
|
|
191
|
-
private async
|
|
209
|
+
private async writeOps(ops: SettingsOp[]): Promise<void> {
|
|
192
210
|
const revision = this.getSnapshot().revision
|
|
193
211
|
let response
|
|
194
212
|
try {
|
|
195
213
|
response = await this.api.mutate({
|
|
196
214
|
ns: this.spec.namespace,
|
|
197
|
-
ops
|
|
215
|
+
ops,
|
|
198
216
|
...revision === undefined ? {} : { expectedRevision: revision },
|
|
199
217
|
})
|
|
200
218
|
} catch {
|
|
@@ -229,6 +247,8 @@ class BridgeScopeController<T> implements SettingsScope<T> {
|
|
|
229
247
|
export interface ImageGenScope extends SettingsScope<ImageGenConfig> {
|
|
230
248
|
/** Queue a bridge refresh (the invalidation path re-reads the namespace). */
|
|
231
249
|
load(): Promise<void>
|
|
250
|
+
/** Apply several path ops in one revision-fenced mutate call. */
|
|
251
|
+
mutateOps(ops: SettingsOp[]): Promise<void>
|
|
232
252
|
getKeySetSnapshot(): boolean
|
|
233
253
|
subscribeKeySet(listener: () => void): () => void
|
|
234
254
|
getSecretSetSnapshot(field: string): boolean
|
|
@@ -248,3 +268,31 @@ export function bindImageGenScope(fetchFn: typeof fetch = fetch): ImageGenScope
|
|
|
248
268
|
void controller.load()
|
|
249
269
|
return controller
|
|
250
270
|
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Flatten the configured channels into the model options the panel lists
|
|
274
|
+
* (aliases; the default channel's models first) plus the default channel id.
|
|
275
|
+
* Falls back to the legacy flat allow-list while no channels exist (upgrade
|
|
276
|
+
* path). Pure projection — no host calls.
|
|
277
|
+
*/
|
|
278
|
+
export function imageModelOptions(config: ImageGenConfig | undefined): { models: string[]; defaultChannelId?: string } {
|
|
279
|
+
const channels = config?.channels ?? []
|
|
280
|
+
if (channels.length === 0) {
|
|
281
|
+
const legacy = Array.isArray(config?.imageModels)
|
|
282
|
+
? config.imageModels.filter((model): model is string => typeof model === 'string' && model.trim() !== '')
|
|
283
|
+
: []
|
|
284
|
+
return { models: legacy }
|
|
285
|
+
}
|
|
286
|
+
const defaultId = config?.defaultChannelId !== undefined && channels.some(channel => channel.id === config.defaultChannelId)
|
|
287
|
+
? config.defaultChannelId
|
|
288
|
+
: channels[0]!.id
|
|
289
|
+
const ordered = [defaultId, ...channels.filter(channel => channel.id !== defaultId).map(channel => channel.id)]
|
|
290
|
+
const models: string[] = []
|
|
291
|
+
for (const id of ordered) {
|
|
292
|
+
const channel = channels.find(candidate => candidate.id === id)!
|
|
293
|
+
for (const model of channel.models) {
|
|
294
|
+
if (model.alias !== '' && !models.includes(model.alias)) models.push(model.alias)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return models.length > 0 ? { models, defaultChannelId: defaultId } : { models: [], defaultChannelId: defaultId }
|
|
298
|
+
}
|