toto10 commited on
Commit
b36180c
·
1 Parent(s): 395def2

a34e80e8b4b20b9452d240a5dfb1810a082bf12e800fb36d96f61768dfebfe44

Browse files
Files changed (50) hide show
  1. Auto-Photoshop-StableDiffusion-Plugin/thumbnail.js +40 -0
  2. Auto-Photoshop-StableDiffusion-Plugin/ultimate_sd_upscaler/.gitignore +1 -0
  3. Auto-Photoshop-StableDiffusion-Plugin/ultimate_sd_upscaler/src/config.ts +134 -0
  4. Auto-Photoshop-StableDiffusion-Plugin/ultimate_sd_upscaler/src/elements.tsx +255 -0
  5. Auto-Photoshop-StableDiffusion-Plugin/ultimate_sd_upscaler/src/scripts.tsx +165 -0
  6. Auto-Photoshop-StableDiffusion-Plugin/ultimate_sd_upscaler/src/ultimate_sd_upscaler.tsx +292 -0
  7. Auto-Photoshop-StableDiffusion-Plugin/ultimate_sd_upscaler/tsconfig.json +109 -0
  8. Auto-Photoshop-StableDiffusion-Plugin/ultimate_sd_upscaler/types/sdapi_py_re.d.ts +5 -0
  9. Auto-Photoshop-StableDiffusion-Plugin/ultimate_sd_upscaler/types/uxp.d.ts +5 -0
  10. Auto-Photoshop-StableDiffusion-Plugin/ultimate_sd_upscaler/webpack.config.js +65 -0
  11. Auto-Photoshop-StableDiffusion-Plugin/update_plugin.bat +1 -0
  12. Auto-Photoshop-StableDiffusion-Plugin/update_plugin.sh +2 -0
  13. Auto-Photoshop-StableDiffusion-Plugin/utility/api.js +80 -0
  14. Auto-Photoshop-StableDiffusion-Plugin/utility/dummy.js +0 -0
  15. Auto-Photoshop-StableDiffusion-Plugin/utility/event.js +13 -0
  16. Auto-Photoshop-StableDiffusion-Plugin/utility/general.js +160 -0
  17. Auto-Photoshop-StableDiffusion-Plugin/utility/html_manip.js +1177 -0
  18. Auto-Photoshop-StableDiffusion-Plugin/utility/io.js +836 -0
  19. Auto-Photoshop-StableDiffusion-Plugin/utility/layer.js +357 -0
  20. Auto-Photoshop-StableDiffusion-Plugin/utility/notification.js +102 -0
  21. Auto-Photoshop-StableDiffusion-Plugin/utility/online_data.json +4 -0
  22. Auto-Photoshop-StableDiffusion-Plugin/utility/presets/controlnet_preset.js +198 -0
  23. Auto-Photoshop-StableDiffusion-Plugin/utility/presets/preset.js +408 -0
  24. Auto-Photoshop-StableDiffusion-Plugin/utility/sampler.js +117 -0
  25. Auto-Photoshop-StableDiffusion-Plugin/utility/sd_scripts/horde.js +107 -0
  26. Auto-Photoshop-StableDiffusion-Plugin/utility/sdapi/config.js +83 -0
  27. Auto-Photoshop-StableDiffusion-Plugin/utility/sdapi/horde_native.js +731 -0
  28. Auto-Photoshop-StableDiffusion-Plugin/utility/sdapi/metadata_to_json.js +76 -0
  29. Auto-Photoshop-StableDiffusion-Plugin/utility/sdapi/options.js +37 -0
  30. Auto-Photoshop-StableDiffusion-Plugin/utility/sdapi/prompt_shortcut.js +42 -0
  31. Auto-Photoshop-StableDiffusion-Plugin/utility/sdapi/python_replacement.js +594 -0
  32. Auto-Photoshop-StableDiffusion-Plugin/utility/session.js +270 -0
  33. Auto-Photoshop-StableDiffusion-Plugin/utility/tab/control_net.js +1144 -0
  34. Auto-Photoshop-StableDiffusion-Plugin/utility/tab/history_tab.js +101 -0
  35. Auto-Photoshop-StableDiffusion-Plugin/utility/tab/image_search_tab.js +51 -0
  36. Auto-Photoshop-StableDiffusion-Plugin/utility/tab/lexica_tab.js +239 -0
  37. Auto-Photoshop-StableDiffusion-Plugin/utility/tab/sd.js +370 -0
  38. Auto-Photoshop-StableDiffusion-Plugin/utility/tab/settings.js +137 -0
  39. Auto-Photoshop-StableDiffusion-Plugin/utility/tab/share_tab.js +17 -0
  40. Auto-Photoshop-StableDiffusion-Plugin/utility/tips.js +40 -0
  41. Auto-Photoshop-StableDiffusion-Plugin/utility/ui.js +404 -0
  42. Auto-Photoshop-StableDiffusion-Plugin/viewer.js +861 -0
  43. Stable-Diffusion-Webui-Civitai-Helper/.github/ISSUE_TEMPLATE/simple-issue-template.md +20 -0
  44. Stable-Diffusion-Webui-Civitai-Helper/.gitignore +3 -0
  45. Stable-Diffusion-Webui-Civitai-Helper/README.cn.md +241 -0
  46. Stable-Diffusion-Webui-Civitai-Helper/README.jp.md +224 -0
  47. Stable-Diffusion-Webui-Civitai-Helper/README.kr.md +206 -0
  48. Stable-Diffusion-Webui-Civitai-Helper/README.md +329 -0
  49. Stable-Diffusion-Webui-Civitai-Helper/claim_wall.md +91 -0
  50. Stable-Diffusion-Webui-Civitai-Helper/icon/.keep +0 -0
Auto-Photoshop-StableDiffusion-Plugin/thumbnail.js ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class Thumbnail {
2
+ static wrapImgInContainer(img, container_style_class) {
3
+ const container = document.createElement('div')
4
+ container.className = container_style_class
5
+ container.appendChild(img)
6
+ return container
7
+ }
8
+
9
+ static addSPButtonToContainer(
10
+ container,
11
+ button_id,
12
+ title,
13
+ callbackFunction,
14
+ param1
15
+ ) {
16
+ const elem = document.getElementById(button_id)
17
+ const clone = elem.cloneNode(true)
18
+ const button = clone
19
+ button.style.display = null
20
+ button.removeAttribute('id')
21
+ button.setAttribute('title', title)
22
+
23
+ // Create button element
24
+ button.className = 'thumbnail-image-button'
25
+ if (callbackFunction.constructor.name === 'AsyncFunction') {
26
+ button.addEventListener(
27
+ 'click',
28
+ async () => await callbackFunction(param1)
29
+ )
30
+ } else {
31
+ button.addEventListener('click', () => callbackFunction(param1))
32
+ }
33
+
34
+ container.appendChild(button)
35
+ }
36
+ }
37
+
38
+ module.exports = {
39
+ Thumbnail,
40
+ }
Auto-Photoshop-StableDiffusion-Plugin/ultimate_sd_upscaler/.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ # dist/*.bundle.js
Auto-Photoshop-StableDiffusion-Plugin/ultimate_sd_upscaler/src/config.ts ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export let ui_config = {
2
+ tile_width: {
3
+ minimum: 0,
4
+ maximum: 2048,
5
+ step: 64,
6
+ label: 'Tile width',
7
+ value: 512,
8
+ },
9
+ tile_height: {
10
+ minimum: 0,
11
+ maximum: 2048,
12
+ step: 64,
13
+ label: 'Tile height',
14
+ value: 0,
15
+ },
16
+ mask_blur: {
17
+ minimum: 0,
18
+ maximum: 64,
19
+ step: 1,
20
+ label: 'Mask blur',
21
+ value: 8,
22
+ },
23
+ padding: { minimum: 0, maximum: 128, step: 1, label: 'Padding', value: 32 },
24
+
25
+ seams_fix_denoise: {
26
+ label: 'Denoise',
27
+ minimum: 0,
28
+ maximum: 1,
29
+ step: 0.01,
30
+ value: 0.35,
31
+ visible: false,
32
+ interactive: true,
33
+ },
34
+
35
+ seams_fix_width: {
36
+ label: 'Width',
37
+ minimum: 0,
38
+ maximum: 128,
39
+ step: 1,
40
+ value: 64,
41
+ visible: false,
42
+ interactive: true,
43
+ },
44
+ seams_fix_mask_blur: {
45
+ label: 'Mask blur',
46
+ minimum: 0,
47
+ maximum: 64,
48
+ step: 1,
49
+ value: 4,
50
+ visible: false,
51
+ interactive: true,
52
+ },
53
+ seams_fix_padding: {
54
+ label: 'Padding',
55
+ minimum: 0,
56
+ maximum: 128,
57
+ step: 1,
58
+ value: 16,
59
+ visible: false,
60
+ interactive: true,
61
+ },
62
+ redraw_mode: {
63
+ label: 'Type',
64
+ choices: ['Linear', 'Chess', 'None'],
65
+ type: 'index',
66
+ value: 0,
67
+ },
68
+ save_upscaled_image: {
69
+ label: 'Upscaled',
70
+ value: true,
71
+ },
72
+ save_seams_fix_image: {
73
+ label: 'Seams fix',
74
+ value: false,
75
+ },
76
+
77
+ seams_fix_type: {
78
+ label: 'Type',
79
+ choices: [
80
+ 'None',
81
+ 'Band pass',
82
+ 'Half tile offset pass',
83
+ 'Half tile offset pass + intersections',
84
+ ],
85
+ type: 'index',
86
+ value: 3,
87
+ },
88
+
89
+ target_size_type: {
90
+ label: 'Target size type',
91
+ choices: [
92
+ 'From img2img2 settings',
93
+ 'Custom size',
94
+ 'Scale from image size',
95
+ ],
96
+ type: 'index',
97
+ value: 2,
98
+ },
99
+
100
+ custom_width: {
101
+ label: 'Custom width',
102
+ minimum: 64,
103
+ maximum: 8192,
104
+ step: 64,
105
+ value: 2048,
106
+ visible: false,
107
+ interactive: true,
108
+ },
109
+ custom_height: {
110
+ label: 'Custom height',
111
+ minimum: 64,
112
+ maximum: 8192,
113
+ step: 64,
114
+ value: 2048,
115
+ visible: false,
116
+ interactive: true,
117
+ },
118
+ custom_scale: {
119
+ label: 'Scale',
120
+ minimum: 1,
121
+ maximum: 16,
122
+ step: 0.01,
123
+ value: 2,
124
+ visible: false,
125
+ interactive: true,
126
+ },
127
+
128
+ upscaler_index: {
129
+ label: 'Upscaler',
130
+ choices: [],
131
+ type: 'index',
132
+ value: 0,
133
+ },
134
+ }
Auto-Photoshop-StableDiffusion-Plugin/ultimate_sd_upscaler/src/elements.tsx ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { ReactEventHandler, useState } from 'react'
2
+ // import ReactDOM from 'react-dom'
3
+ import ReactDOM from 'react-dom/client'
4
+ import { versions } from 'uxp'
5
+ declare global {
6
+ namespace JSX {
7
+ interface IntrinsicElements {
8
+ 'sp-picker': any
9
+ 'sp-menu': any
10
+ 'sp-menu-item': any
11
+ 'sp-label': any
12
+ 'sp-checkbox': any
13
+ 'sp-slider': any
14
+ 'sp-radio-group': any
15
+ 'sp-radio': any
16
+ 'sp-divider': any
17
+ 'sp-detail': any
18
+ }
19
+ }
20
+ }
21
+ function mapRange(
22
+ x: number,
23
+ in_min: number,
24
+ in_max: number,
25
+ out_min: number,
26
+ out_max: number
27
+ ) {
28
+ const mappedValue =
29
+ ((x - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min
30
+ return mappedValue
31
+ }
32
+ export enum SliderType {
33
+ Integer = 'integer',
34
+ Float = 'float',
35
+ }
36
+ export class SpSliderWithLabel extends React.Component<{
37
+ onSliderChange?: any
38
+ id?: string
39
+ 'show-value'?: boolean
40
+ steps?: number
41
+ in_min?: number
42
+ in_max?: number
43
+ out_min: number
44
+ out_max: number
45
+ // value?: number
46
+ title?: string
47
+ label?: string
48
+ output_value?: number // can be use to represent sd value
49
+ // slider_value?: number // it's slider value can be from 1 to 100
50
+ slider_type?: SliderType
51
+ }> {
52
+ // const [sliderValue,setSliderValue] = useState<number>(0)
53
+ state = { output_value: this.props.output_value || 0, slider_value: 0 }
54
+ steps: number
55
+ in_min: number
56
+ in_max: number
57
+ out_min: number
58
+ out_max: number
59
+ slider_type: SliderType
60
+ constructor(props: any) {
61
+ super(props)
62
+ this.steps = this.props.steps || 1
63
+ // this.out_min = this.props.out_min || this.in_min
64
+ this.out_min = this.props.out_min
65
+ this.out_max = this.props.out_max
66
+
67
+ // const temp_out_max = this.props.out_max || this.props.in_max || 99
68
+ this.in_min = this.props.in_min || 0
69
+ this.in_max = Math.round((this.out_max - this.out_min) / this.steps)
70
+ this.slider_type = this.props.slider_type || SliderType.Integer
71
+ }
72
+
73
+ componentDidMount(): void {
74
+ const slider_value = this.outputValueToStep(this.state.output_value)
75
+ this.setState({ slider_value: slider_value })
76
+ }
77
+ stepToOutputValue(slider_step: number) {
78
+ let to_value = mapRange(
79
+ slider_step,
80
+ this.in_min,
81
+ this.in_max,
82
+ this.out_min,
83
+ this.out_max
84
+ )
85
+ if (this.slider_type === SliderType.Integer)
86
+ to_value = Math.round(to_value)
87
+
88
+ return to_value
89
+ }
90
+ outputValueToStep(output_value: number) {
91
+ let slider_step = mapRange(
92
+ output_value,
93
+ this.out_min,
94
+ this.out_max,
95
+ this.in_min,
96
+ this.in_max
97
+ )
98
+ // if (this.slider_type === SliderType.Integer)
99
+ slider_step = Math.round(slider_step)
100
+ return slider_step
101
+ }
102
+ setSliderValue(newValue: any) {
103
+ let to_value = mapRange(
104
+ newValue,
105
+ this.in_min,
106
+ this.in_max,
107
+ this.out_min,
108
+ this.out_max
109
+ )
110
+
111
+ if (this.slider_type === SliderType.Integer)
112
+ to_value = Math.round(to_value)
113
+
114
+ this.setState({ output_value: to_value })
115
+ }
116
+
117
+ onSliderValueChangeHandler(event: React.ChangeEvent<HTMLInputElement>) {
118
+ const newValue: string = event.target.value
119
+ console.log('onSliderValueChangeHandler value: ', newValue)
120
+ this.setState({ output_value: newValue })
121
+
122
+ console.log({
123
+ in_min: this.in_min,
124
+ in_max: this.in_max,
125
+ out_min: this.out_min,
126
+ out_max: this.out_max,
127
+ })
128
+
129
+ let output_value = this.stepToOutputValue(parseInt(newValue))
130
+ this.setState({ output_value: output_value })
131
+ if (this.props.onSliderChange && this.props.id) {
132
+ this.props.onSliderChange(this.props.id, output_value)
133
+ }
134
+ }
135
+
136
+ handleNum2Change = (event: React.ChangeEvent<HTMLInputElement>) => {}
137
+
138
+ render() {
139
+ return (
140
+ // <div>
141
+ // <
142
+ // <sp-detail>VERSIONS</sp-detail>
143
+ // <div>{versions.plugin}</div>
144
+ // </div>
145
+ <div>
146
+ <sp-slider
147
+ show-value="false"
148
+ // id="slControlNetWeight_0"
149
+ class="slControlNetWeight_"
150
+ min={this.in_min}
151
+ max={this.in_max}
152
+ value={this.state.slider_value}
153
+ title="2 will keep the composition; 0 will allow composition to change"
154
+ onInput={this.onSliderValueChangeHandler.bind(this)}
155
+ >
156
+ <sp-label slot="label">{this.props.label}:</sp-label>
157
+ <sp-label
158
+ slot="label"
159
+ // id="lControlNetWeight_0"
160
+ class="lControlNetWeight_"
161
+ >
162
+ {this.state.output_value}
163
+ </sp-label>
164
+ </sp-slider>
165
+ </div>
166
+ )
167
+ }
168
+ }
169
+
170
+ export class SpMenu extends React.Component<{
171
+ id?: string
172
+
173
+ title?: string
174
+ style?: string
175
+ items?: string[]
176
+ disabled?: boolean[]
177
+ label_item?: string
178
+ onChange?: any
179
+ selected_index?: number
180
+ }> {
181
+ state = {
182
+ selectedItem: this.props.items ? this.props.items[0] : undefined,
183
+ }
184
+
185
+ componentDidUpdate(prevProps: any) {
186
+ // console.log('prevProps.items: ', prevProps.items)
187
+ // console.log('this.props.items: ', this.props.items)
188
+ // if (prevProps.items !== this.props.items) {
189
+ // const spMenu = this.spMenuRef.current
190
+ // if (spMenu) {
191
+ // spMenu.innerHTML = ''
192
+ // }
193
+ // }
194
+ }
195
+ handleItemClick = (item: string, index: number) => {
196
+ console.log('clicked item: ', item)
197
+ console.log('clicked index: ', index)
198
+ this.setState({ selectedItem: item })
199
+ if (this.props.onChange && this.props.id) {
200
+ this.props.onChange(this.props.id, { index: index, item: item })
201
+ }
202
+ }
203
+ handleMakeSelection = (item: string) => {
204
+ console.log('handleMakeSelection: item ', item)
205
+ this.setState({ selectedItem: item })
206
+ }
207
+
208
+ render() {
209
+ return (
210
+ <div>
211
+ <sp-picker
212
+ title={this.props.title}
213
+ size="m"
214
+ style={{ width: '199px', marginRight: '5px' }}
215
+ >
216
+ <sp-menu id={this.props.id} slot="options">
217
+ {this.props.label_item && (
218
+ <sp-menu-item
219
+ disabled="disabled"
220
+ key={-1}
221
+ data-index={-1}
222
+ selected
223
+ >
224
+ {this.props.label_item}
225
+ </sp-menu-item>
226
+ )}
227
+ {this.props.items?.map((item, index: number) => (
228
+ <sp-menu-item
229
+ key={item}
230
+ data-index={index}
231
+ selected={
232
+ this.props.selected_index !== undefined &&
233
+ this.props.selected_index !== null &&
234
+ this.props.selected_index === index
235
+ ? 'selected'
236
+ : undefined
237
+ }
238
+ disabled={
239
+ this.props.disabled?.[index]
240
+ ? 'disabled'
241
+ : undefined
242
+ }
243
+ onClick={() => {
244
+ this.handleItemClick(item, index)
245
+ }}
246
+ >
247
+ {item}
248
+ </sp-menu-item>
249
+ ))}
250
+ </sp-menu>
251
+ </sp-picker>
252
+ </div>
253
+ )
254
+ }
255
+ }
Auto-Photoshop-StableDiffusion-Plugin/ultimate_sd_upscaler/src/scripts.tsx ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { ReactEventHandler, useState } from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+
4
+ import { action, makeAutoObservable, reaction, toJS } from 'mobx'
5
+ import { Provider, inject, observer } from 'mobx-react'
6
+
7
+ import { SpMenu } from './elements'
8
+ import * as ultimate_sd_upscale_script from './ultimate_sd_upscaler'
9
+ import { ScriptMode } from './ultimate_sd_upscaler'
10
+ export function toJsFunc(store: any) {
11
+ return toJS(store)
12
+ }
13
+
14
+ class ScriptStore {
15
+ scripts_list
16
+ disabled: boolean[]
17
+ selected_script_name
18
+ is_selected_script_available: boolean
19
+ selected_store: any
20
+ is_active: boolean
21
+ selected_args_name: string[]
22
+ mode: ScriptMode
23
+ scripts: any = {
24
+ None: { store: null, args_names: [], mode: [] },
25
+ 'Ultimate SD upscale': {
26
+ store: ultimate_sd_upscale_script.ultimate_sd_upscaler_store,
27
+ args_names: ultimate_sd_upscale_script.script_args_ordered,
28
+ mode: ultimate_sd_upscale_script.script_mode,
29
+ },
30
+ }
31
+
32
+ constructor() {
33
+ this.scripts_list = ['None', ultimate_sd_upscale_script.script_name]
34
+ this.disabled = [false, true]
35
+ this.selected_script_name = 'None'
36
+ this.is_selected_script_available = true
37
+ this.selected_store = null
38
+ this.is_active = true
39
+ this.selected_args_name = []
40
+ this.mode = ScriptMode.Txt2Img
41
+ makeAutoObservable(this)
42
+ }
43
+ setSelectedScript(name: string) {
44
+ this.selected_script_name = name
45
+ this.selected_store = this.scripts[name].store
46
+ this.selected_args_name = this.scripts[name].args_names
47
+ this.is_selected_script_available = true
48
+ }
49
+ setIsActive(new_value: boolean) {
50
+ this.is_active = new_value
51
+ }
52
+ updateProperty(id: any, value: any) {}
53
+
54
+ orderedValues() {
55
+ const values: any = []
56
+ if (!this.selected_store) return values
57
+
58
+ for (const key of this.selected_args_name) {
59
+ // console.log(key, this.data[key])
60
+ values.push((this.selected_store.data as any)[key])
61
+ }
62
+ return values
63
+ }
64
+ setDisabled(newDisabled: boolean[]) {
65
+ console.log('this.disabled:', this.disabled)
66
+ console.log('newDisabled:', newDisabled)
67
+
68
+ this.disabled = newDisabled
69
+ }
70
+ setMode(newMode: ScriptMode) {
71
+ this.mode = newMode
72
+
73
+ // let index = 0
74
+ // Object.keys(this.scripts).forEach((key, index) => {
75
+ // const script = this.scripts[key]
76
+ // this.disabled[index] = script.mode.includes(newMode) ? false : true
77
+
78
+ // // console.log(key, script)
79
+ // })
80
+ const names = Object.keys(this.scripts)
81
+ let index = 0
82
+ for (let name of names) {
83
+ const script = this.scripts[name]
84
+ this.disabled[index] = script.mode.includes(newMode) ? false : true
85
+ index += 1
86
+ }
87
+
88
+ this.disabled[0] = false // None is always enabled
89
+ const selected_index = this.scripts_list.indexOf(
90
+ this.selected_script_name
91
+ )
92
+ this.is_selected_script_available = !this.disabled?.[selected_index]
93
+ this.setDisabled([...this.disabled])
94
+ }
95
+ }
96
+
97
+ export const script_store = new ScriptStore()
98
+ @observer
99
+ class ScriptComponent extends React.Component<{}> {
100
+ render(): React.ReactNode {
101
+ // const script_message = index !== -1 ? script_store.disabled[index] : undefined;
102
+ const script_message =
103
+ script_store.is_selected_script_available ? undefined : (
104
+ <span style={{ color: '#ff595e' }}>
105
+ {'the script is not available in the current Mode'}
106
+ </span>
107
+ )
108
+
109
+ return (
110
+ <>
111
+ <SpMenu
112
+ title="Scripts"
113
+ items={script_store.scripts_list}
114
+ disabled={script_store.disabled}
115
+ // style="width: 199px; margin-right: 5px"
116
+ label_item="Select A Script"
117
+ id={'script_list'}
118
+ onChange={(id: any, value: any) => {
119
+ script_store.setSelectedScript(value.item)
120
+ }}
121
+ />
122
+ <div>
123
+ {script_message}
124
+
125
+ {/* {script_store.disabled.map((value, index) => (
126
+ <li key={index}> {value ? 'true' : 'false'}</li>
127
+ ))} */}
128
+ </div>
129
+ <sp-checkbox
130
+ checked={script_store.is_active ? true : undefined}
131
+ onClick={(event: React.ChangeEvent<HTMLInputElement>) => {
132
+ script_store.setIsActive(event.target.checked)
133
+ }}
134
+ >
135
+ {'Activate'}
136
+ </sp-checkbox>
137
+ <>
138
+ {script_store.selected_script_name === 'None' && <></>}
139
+ {script_store.selected_script_name ===
140
+ ultimate_sd_upscale_script.script_name && (
141
+ <ultimate_sd_upscale_script.UltimateSDUpscalerForm
142
+ store={
143
+ // ultimate_sd_upscale_script.ultimate_sd_upscaler_store
144
+ script_store.scripts[
145
+ script_store.selected_script_name
146
+ ].store
147
+ }
148
+ />
149
+ )}
150
+ {/* ... other conditions for other components */}
151
+ </>
152
+ </>
153
+ )
154
+ }
155
+ }
156
+ const domNode = document.getElementById('scriptsContainer')!
157
+ const root = ReactDOM.createRoot(domNode)
158
+
159
+ root.render(
160
+ <React.StrictMode>
161
+ <ScriptComponent></ScriptComponent>
162
+
163
+ {/* <SliderValuesDisplay /> */}
164
+ </React.StrictMode>
165
+ )
Auto-Photoshop-StableDiffusion-Plugin/ultimate_sd_upscaler/src/ultimate_sd_upscaler.tsx ADDED
@@ -0,0 +1,292 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { ReactEventHandler } from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+
4
+ import { action, makeAutoObservable, reaction, toJS } from 'mobx'
5
+ import { Provider, inject, observer } from 'mobx-react'
6
+
7
+ import { SliderType, SpMenu, SpSliderWithLabel } from './elements'
8
+
9
+ import * as sdapi from '../../sdapi_py_re'
10
+
11
+ import { ui_config } from './config'
12
+
13
+ export let script_name: string = 'Ultimate SD upscale'
14
+ export enum ScriptMode {
15
+ Txt2Img = 'txt2img',
16
+ Img2Img = 'img2img',
17
+ Inpaint = 'inpaint',
18
+ Outpaint = 'outpaint',
19
+ }
20
+
21
+ export let script_mode = [
22
+ ScriptMode.Img2Img,
23
+ ScriptMode.Inpaint,
24
+ ScriptMode.Outpaint,
25
+ ]
26
+ interface UltimateSDUpscalerData {
27
+ _: string
28
+ tile_width: number
29
+ tile_height: number
30
+ mask_blur: number
31
+ padding: number
32
+ seams_fix_width: number
33
+ seams_fix_denoise: number
34
+ seams_fix_padding: number
35
+ upscaler_index: number
36
+ save_upscaled_image: boolean
37
+ redraw_mode: number
38
+ save_seams_fix_image: boolean
39
+ seams_fix_mask_blur: number
40
+ seams_fix_type: number
41
+ target_size_type: number
42
+ custom_width: number
43
+ custom_height: number
44
+ custom_scale: number
45
+ }
46
+ export const script_args_ordered = [
47
+ '_',
48
+ 'tile_width',
49
+ 'tile_height',
50
+ 'mask_blur',
51
+ 'padding',
52
+ 'seams_fix_width',
53
+ 'seams_fix_denoise',
54
+ 'seams_fix_padding',
55
+ 'upscaler_index',
56
+ 'save_upscaled_image',
57
+ 'redraw_mode',
58
+ 'save_seams_fix_image',
59
+ 'seams_fix_mask_blur',
60
+ 'seams_fix_type',
61
+ 'target_size_type',
62
+ 'custom_width',
63
+ 'custom_height',
64
+ 'custom_scale',
65
+ ]
66
+
67
+ class UltimateSDUpscalerStore {
68
+ data: UltimateSDUpscalerData
69
+ // test_value: number = 10
70
+ // test_value_2: number = 2
71
+ test_value: number
72
+ test_value_2: number
73
+ is_active: boolean
74
+ constructor(data: UltimateSDUpscalerData) {
75
+ this.data = data
76
+ this.test_value = 10
77
+ this.test_value_2 = 2
78
+ this.is_active = false
79
+ makeAutoObservable(this)
80
+
81
+ // reaction(
82
+ // () => [this.test_value],
83
+ // () => {
84
+ // this.test_value_2 = this.test_value * 2
85
+ // console.log('reaction to test_value change:', this.test_value)
86
+ // console.log('this.test_value_2:', this.test_value_2)
87
+ // }
88
+ // )
89
+ }
90
+ setIsActive(b_value: boolean) {
91
+ this.is_active = b_value
92
+ }
93
+ setTestValue(new_value: number) {
94
+ this.test_value = new_value
95
+ console.log('setTestValue: new_value ', new_value)
96
+ console.log('setTestValue: this.test_value: ', this.test_value)
97
+ }
98
+
99
+ updateProperty(key: keyof UltimateSDUpscalerData, value: any) {
100
+ ;(this.data as any)[key] = value
101
+ }
102
+
103
+ toJsFunc() {
104
+ return toJS(this)
105
+ }
106
+ }
107
+
108
+ const configValues = Object.entries(ui_config).reduce(
109
+ (acc, [key, value]) => ({ ...acc, [key]: value.value }),
110
+ {}
111
+ )
112
+ const default_values: any = {
113
+ _: '',
114
+ ...configValues,
115
+ }
116
+ export const ultimate_sd_upscaler_store = new UltimateSDUpscalerStore(
117
+ default_values
118
+ )
119
+
120
+ @observer
121
+ export class UltimateSDUpscalerForm extends React.Component<{
122
+ store: UltimateSDUpscalerStore
123
+ }> {
124
+ // slider1Ref = React.createRef<SpSliderWithLabel>()
125
+ // slider2Ref = React.createRef<SpSliderWithLabel>()
126
+ state = {
127
+ items: ['Item 1', 'Item 2', 'Item 3'],
128
+ sd_upscalers: [],
129
+ }
130
+ componentDidMount(): void {
131
+ this.getUpscalers()
132
+ }
133
+ handleUpdateItems = () => {
134
+ this.setState({
135
+ items: ['New Item 1', 'New Item 2', 'New Item 3'],
136
+ })
137
+ }
138
+ handleSlider1ValueChange = (newValue: any) => {
139
+ // this.props.store.setTestValue(newValue)
140
+ this.props.store.test_value = newValue
141
+
142
+ // this.props.store.
143
+ console.log('store.test_value: ', this.props.store.test_value)
144
+ console.log('newValue: ', newValue)
145
+ }
146
+
147
+ handleSlider2ValueChange = (newValue: any) => {
148
+ // scriptFormStore.setSlider2Value(newValue)
149
+ }
150
+ handleSliderChange = (key: any, newValue: any) => {
151
+ this.props.store.updateProperty(key, newValue)
152
+ }
153
+ handleMenuChange = (key: any, new_index_value_pair: any) => {
154
+ let config = ui_config[key as keyof typeof ui_config] as any
155
+ if ('type' in config) {
156
+ let value =
157
+ config.type === 'index'
158
+ ? new_index_value_pair['index']
159
+ : new_index_value_pair['item']
160
+ this.props.store.updateProperty(key, value)
161
+ }
162
+ }
163
+
164
+ async getUpscalers() {
165
+ const sd_upscalers_json = await sdapi.requestGetUpscalers()
166
+ const sd_upscalers = sd_upscalers_json.map(
167
+ (upscaler: any) => upscaler.name
168
+ )
169
+ this.setState({ sd_upscalers: sd_upscalers })
170
+ return sd_upscalers
171
+ }
172
+
173
+ render() {
174
+ const ids = [
175
+ 'tile_width',
176
+ 'tile_height',
177
+ 'mask_blur',
178
+ 'padding',
179
+ ] as const
180
+ // let config = ui_config[ids as keyof typeof ui_config] as any
181
+ const group_1_sliders = ids.map((id) => (
182
+ <SpSliderWithLabel
183
+ key={id}
184
+ id={id}
185
+ show-value={false}
186
+ steps={ui_config[id].step}
187
+ out_min={ui_config[id].minimum}
188
+ out_max={ui_config[id].maximum}
189
+ output_value={this.props.store.data[id]}
190
+ title={ui_config[id].label}
191
+ label={ui_config[id].label}
192
+ onSliderChange={this.handleSliderChange}
193
+ />
194
+ ))
195
+ const seamfix_ids = [
196
+ 'seams_fix_denoise',
197
+ 'seams_fix_width',
198
+ 'seams_fix_mask_blur',
199
+ 'seams_fix_padding',
200
+ ] as const
201
+ const seamfix_sliders = seamfix_ids.map((id) => (
202
+ <SpSliderWithLabel
203
+ key={id}
204
+ id={id}
205
+ show-value={false}
206
+ steps={ui_config[id].step}
207
+ out_min={ui_config[id].minimum}
208
+ out_max={ui_config[id].maximum}
209
+ output_value={this.props.store.data[id]}
210
+ title={ui_config[id].label}
211
+ label={ui_config[id].label}
212
+ onSliderChange={this.handleSliderChange}
213
+ slider_type={
214
+ Number.isInteger(ui_config[id].step)
215
+ ? SliderType.Integer
216
+ : SliderType.Float
217
+ }
218
+ />
219
+ ))
220
+ return (
221
+ <div>
222
+ <SpMenu
223
+ title="Stable Diffusion Upscalers"
224
+ items={this.state.sd_upscalers}
225
+ label_item="Select Upscaler"
226
+ id={'upscaler_index'}
227
+ onChange={this.handleMenuChange}
228
+ selected_index={this.props.store.data.upscaler_index}
229
+ />
230
+ <SpMenu
231
+ title={ui_config.target_size_type.label}
232
+ id={'target_size_type'}
233
+ items={ui_config.target_size_type.choices}
234
+ label_item={'Select ' + ui_config.target_size_type.label}
235
+ onChange={this.handleMenuChange}
236
+ selected_index={this.props.store.data.target_size_type}
237
+ />
238
+ <SpSliderWithLabel
239
+ label={ui_config.custom_scale.label}
240
+ output_value={this.props.store.data.custom_scale}
241
+ id={'custom_scale'}
242
+ out_min={ui_config.custom_scale.minimum}
243
+ out_max={ui_config.custom_scale.maximum}
244
+ onSliderChange={this.handleSliderChange}
245
+ steps={0.01}
246
+ slider_type={SliderType.Float}
247
+ />
248
+
249
+ <sp-checkbox
250
+ checked={
251
+ this.props.store.data.save_upscaled_image
252
+ ? true
253
+ : undefined
254
+ }
255
+ onClick={(event: React.ChangeEvent<HTMLInputElement>) => {
256
+ this.props.store.updateProperty(
257
+ 'save_upscaled_image',
258
+ event.target.checked
259
+ )
260
+ }}
261
+ >
262
+ {ui_config.save_upscaled_image.label}
263
+ </sp-checkbox>
264
+ <sp-checkbox
265
+ checked={
266
+ this.props.store.data.save_seams_fix_image
267
+ ? true
268
+ : undefined
269
+ }
270
+ onClick={(event: React.ChangeEvent<HTMLInputElement>) => {
271
+ this.props.store.updateProperty(
272
+ 'save_seams_fix_image',
273
+ event.target.checked
274
+ )
275
+ }}
276
+ >
277
+ {ui_config.save_seams_fix_image.label}
278
+ </sp-checkbox>
279
+ {group_1_sliders}
280
+ <SpMenu
281
+ title={'Seams Fix Type'}
282
+ id={'seams_fix_type'}
283
+ items={ui_config.seams_fix_type.choices}
284
+ label_item="Select Seams Fix Type"
285
+ onChange={this.handleMenuChange}
286
+ selected_index={this.props.store.data.seams_fix_type}
287
+ />
288
+ {seamfix_sliders}
289
+ </div>
290
+ )
291
+ }
292
+ }
Auto-Photoshop-StableDiffusion-Plugin/ultimate_sd_upscaler/tsconfig.json ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ /* Visit https://aka.ms/tsconfig to read more about this file */
4
+
5
+ /* Projects */
6
+ // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
7
+ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8
+ // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
9
+ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
10
+ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11
+ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12
+
13
+ /* Language and Environment */
14
+ "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15
+ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16
+ "jsx": "react", /* Specify what JSX code is generated. */
17
+ "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
18
+ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19
+ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
20
+ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21
+ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
22
+ // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
23
+ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24
+ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25
+ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
26
+
27
+ /* Modules */
28
+ "module": "commonjs", /* Specify what module code is generated. */
29
+ // "rootDir": "./", /* Specify the root folder within your source files. */
30
+ // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
31
+ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
32
+ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
33
+ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
34
+ "typeRoots": ["./types", "../node_modules/@types"], /* Specify multiple folders that act like './node_modules/@types'. */
35
+ // "types": [], /* Specify type package names to be included without being referenced in a source file. */
36
+ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
37
+ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
38
+ // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
39
+ // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
40
+ // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
41
+ // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
42
+ // "resolveJsonModule": true, /* Enable importing .json files. */
43
+ // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
44
+ // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
45
+
46
+ /* JavaScript Support */
47
+ "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
48
+ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
49
+ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
50
+
51
+ /* Emit */
52
+ // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
53
+ // "declarationMap": true, /* Create sourcemaps for d.ts files. */
54
+ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
55
+ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
56
+ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
57
+ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
58
+ "outDir": "./dist", /* Specify an output folder for all emitted files. */
59
+ // "removeComments": true, /* Disable emitting comments. */
60
+ // "noEmit": true, /* Disable emitting files from a compilation. */
61
+ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
62
+ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
63
+ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
64
+ // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
65
+ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
66
+ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
67
+ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
68
+ // "newLine": "crlf", /* Set the newline character for emitting files. */
69
+ // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
70
+ // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
71
+ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
72
+ // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
73
+ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
74
+ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
75
+
76
+ /* Interop Constraints */
77
+ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
78
+ // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
79
+ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
80
+ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
81
+ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
82
+ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
83
+
84
+ /* Type Checking */
85
+ "strict": true, /* Enable all strict type-checking options. */
86
+ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
87
+ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
88
+ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
89
+ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
90
+ // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
91
+ // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
92
+ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
93
+ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
94
+ // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
95
+ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
96
+ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
97
+ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
98
+ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
99
+ // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
100
+ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
101
+ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
102
+ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
103
+ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
104
+
105
+ /* Completeness */
106
+ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
107
+ "skipLibCheck": true /* Skip type checking all .d.ts files. */
108
+ }
109
+ }
Auto-Photoshop-StableDiffusion-Plugin/ultimate_sd_upscaler/types/sdapi_py_re.d.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+
2
+ declare module 'sdapi_py_re' {
3
+ const exports: any;
4
+ export = exports;
5
+ }
Auto-Photoshop-StableDiffusion-Plugin/ultimate_sd_upscaler/types/uxp.d.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ declare module 'uxp' {
2
+ // Add type declarations for the uxp module here
3
+ export const storage: any;
4
+ export const versions: any;
5
+ }
Auto-Photoshop-StableDiffusion-Plugin/ultimate_sd_upscaler/webpack.config.js ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const path = require('path')
2
+ // const CleanWebpackPlugin = require('clean-webpack-plugin')
3
+ const CopyPlugin = require('copy-webpack-plugin')
4
+
5
+ module.exports = {
6
+ entry: {
7
+ ultimate_sd_upscaler: './src/ultimate_sd_upscaler.tsx',
8
+ scripts: './src/scripts.tsx',
9
+ },
10
+ output: {
11
+ path: path.resolve(__dirname, 'dist'),
12
+ filename: '[name].bundle.js',
13
+ libraryTarget: 'commonjs2',
14
+ },
15
+ // mode: 'development',
16
+ mode: 'production',
17
+ devtool: 'inline-source-map', // won't work on XD due to lack of eval
18
+ externals: {
19
+ uxp: 'commonjs2 uxp',
20
+ photoshop: 'commonjs2 photoshop',
21
+ os: 'commonjs2 os',
22
+ },
23
+ resolve: {
24
+ extensions: ['.tsx', '.ts', '.js', '.jsx'],
25
+ },
26
+ module: {
27
+ rules: [
28
+ {
29
+ test: /\.tsx?$/,
30
+ loader: 'ts-loader',
31
+ exclude: /node_modules/,
32
+ options: {
33
+ configFile: 'tsconfig.json',
34
+ },
35
+ },
36
+ {
37
+ test: /\.jsx?$/,
38
+ exclude: /node_modules/,
39
+ loader: 'babel-loader',
40
+ options: {
41
+ plugins: [
42
+ '@babel/transform-react-jsx',
43
+ '@babel/proposal-object-rest-spread',
44
+ '@babel/plugin-syntax-class-properties',
45
+ ],
46
+ },
47
+ },
48
+ {
49
+ test: /\.png$/,
50
+ exclude: /node_modules/,
51
+ loader: 'file-loader',
52
+ },
53
+ {
54
+ test: /\.css$/,
55
+ use: ['style-loader', 'css-loader'],
56
+ },
57
+ ],
58
+ },
59
+ plugins: [
60
+ //new CleanWebpackPlugin(),
61
+ // new CopyPlugin(['plugin'], {
62
+ // copyUnmodified: true,
63
+ // }),
64
+ ],
65
+ }
Auto-Photoshop-StableDiffusion-Plugin/update_plugin.bat ADDED
@@ -0,0 +1 @@
 
 
1
+ git pull
Auto-Photoshop-StableDiffusion-Plugin/update_plugin.sh ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ #!/bin/bash
2
+ git pull
Auto-Photoshop-StableDiffusion-Plugin/utility/api.js ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ async function requestGet(url) {
2
+ let json = null
3
+
4
+ const full_url = url
5
+ try {
6
+ let request = await fetch(full_url)
7
+ if (request.status === 404) {
8
+ return null
9
+ }
10
+
11
+ json = await request.json()
12
+
13
+ console.log('json: ', json)
14
+ } catch (e) {
15
+ console.warn(`issues requesting from ${full_url}`, e)
16
+ }
17
+ return json
18
+ }
19
+ async function requestPost(url, payload) {
20
+ let json = null
21
+
22
+ const full_url = url
23
+ try {
24
+ let request = await fetch(full_url, {
25
+ method: 'POST',
26
+ headers: {
27
+ Accept: 'application/json',
28
+ 'Content-Type': 'application/json',
29
+ },
30
+ body: JSON.stringify(payload),
31
+ })
32
+
33
+ if (request.status === 404) {
34
+ return null
35
+ }
36
+
37
+ json = await request.json()
38
+
39
+ console.log('json: ', json)
40
+ } catch (e) {
41
+ console.warn(`issues requesting from ${full_url}`, e)
42
+ }
43
+ return json
44
+ }
45
+ async function requestFormDataPost(url, payload) {
46
+ try {
47
+ var myHeaders = new Headers()
48
+ myHeaders.append('Cookie', 'PHPSESSID=n70fa2vmvm6tfmktf4jmstmd1i')
49
+
50
+ var formdata = new FormData()
51
+
52
+ for ([key, value] of Object.entries(payload)) {
53
+ formdata.append(key, value)
54
+ }
55
+ // formdata.append(
56
+ // 'source',
57
+ // 'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAApgAAAKYB3X3/OAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANCSURBVEiJtZZPbBtFFMZ/M7ubXdtdb1xSFyeilBapySVU8h8OoFaooFSqiihIVIpQBKci6KEg9Q6H9kovIHoCIVQJJCKE1ENFjnAgcaSGC6rEnxBwA04Tx43t2FnvDAfjkNibxgHxnWb2e/u992bee7tCa00YFsffekFY+nUzFtjW0LrvjRXrCDIAaPLlW0nHL0SsZtVoaF98mLrx3pdhOqLtYPHChahZcYYO7KvPFxvRl5XPp1sN3adWiD1ZAqD6XYK1b/dvE5IWryTt2udLFedwc1+9kLp+vbbpoDh+6TklxBeAi9TL0taeWpdmZzQDry0AcO+jQ12RyohqqoYoo8RDwJrU+qXkjWtfi8Xxt58BdQuwQs9qC/afLwCw8tnQbqYAPsgxE1S6F3EAIXux2oQFKm0ihMsOF71dHYx+f3NND68ghCu1YIoePPQN1pGRABkJ6Bus96CutRZMydTl+TvuiRW1m3n0eDl0vRPcEysqdXn+jsQPsrHMquGeXEaY4Yk4wxWcY5V/9scqOMOVUFthatyTy8QyqwZ+kDURKoMWxNKr2EeqVKcTNOajqKoBgOE28U4tdQl5p5bwCw7BWquaZSzAPlwjlithJtp3pTImSqQRrb2Z8PHGigD4RZuNX6JYj6wj7O4TFLbCO/Mn/m8R+h6rYSUb3ekokRY6f/YukArN979jcW+V/S8g0eT/N3VN3kTqWbQ428m9/8k0P/1aIhF36PccEl6EhOcAUCrXKZXXWS3XKd2vc/TRBG9O5ELC17MmWubD2nKhUKZa26Ba2+D3P+4/MNCFwg59oWVeYhkzgN/JDR8deKBoD7Y+ljEjGZ0sosXVTvbc6RHirr2reNy1OXd6pJsQ+gqjk8VWFYmHrwBzW/n+uMPFiRwHB2I7ih8ciHFxIkd/3Omk5tCDV1t+2nNu5sxxpDFNx+huNhVT3/zMDz8usXC3ddaHBj1GHj/As08fwTS7Kt1HBTmyN29vdwAw+/wbwLVOJ3uAD1wi/dUH7Qei66PfyuRj4Ik9is+hglfbkbfR3cnZm7chlUWLdwmprtCohX4HUtlOcQjLYCu+fzGJH2QRKvP3UNz8bWk1qMxjGTOMThZ3kvgLI5AzFfo379UAAAAASUVORK5CYII='
58
+ // )
59
+ // formdata.append('key', '6d207e02198a847aa98d0a2a901485a5')
60
+
61
+ var requestOptions = {
62
+ method: 'POST',
63
+ headers: myHeaders,
64
+ body: formdata,
65
+ redirect: 'follow',
66
+ }
67
+
68
+ const response = await fetch(url, requestOptions)
69
+ const result_json = response.json()
70
+ return result_json
71
+ } catch (e) {
72
+ console.warn(e)
73
+ }
74
+ }
75
+
76
+ module.exports = {
77
+ requestGet,
78
+ requestPost,
79
+ requestFormDataPost,
80
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/dummy.js ADDED
The diff for this file is too large to render. See raw diff
 
Auto-Photoshop-StableDiffusion-Plugin/utility/event.js ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const updatePresetMenuEvent = new CustomEvent('updatePresetMenuEvent', {
2
+ detail: {},
3
+ bubbles: true,
4
+ cancelable: true,
5
+ composed: false,
6
+ })
7
+ function triggerEvent(query_selector, event) {
8
+ document.querySelector(query_selector).dispatchEvent(event)
9
+ }
10
+ module.exports = {
11
+ updatePresetMenuEvent,
12
+ triggerEvent,
13
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/general.js ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { requestGet } = require('./api')
2
+
3
+ function newOutputImageName(format = 'png') {
4
+ const random_id = Math.floor(Math.random() * 100000000000 + 1) // Date.now() doesn't have enough resolution to avoid duplicate
5
+ const image_name = `output- ${Date.now()}-${random_id}.${format}`
6
+ console.log('generated image name:', image_name)
7
+ return image_name
8
+ }
9
+
10
+ function makeImagePath(format = 'png') {
11
+ const image_name = newOutputImageName(format)
12
+ const image_path = `${uniqueDocumentId}/${image_name}`
13
+ return image_path
14
+ }
15
+ function convertImageNameToPng(image_name) {
16
+ const image_png_name = image_name.split('.')[0] + '.png'
17
+ return image_png_name
18
+ }
19
+ function fixNativePath(native_path) {
20
+ const fixed_native_path = native_path.replaceAll('\\', '/')
21
+
22
+ return fixed_native_path
23
+ }
24
+ function base64ToBase64Url(base64_image) {
25
+ return 'data:image/png;base64,' + base64_image
26
+ }
27
+ function base64UrlToBase64(base64_url) {
28
+ const base64_image = base64_url.replace('data:image/png;base64,', '')
29
+ return base64_image
30
+ }
31
+ const timer = (ms) => new Promise((res) => setTimeout(res, ms)) //Todo: move this line to it's own utilit function
32
+
33
+ function scaleToClosestKeepRatio(
34
+ original_width,
35
+ original_height,
36
+ min_width,
37
+ min_height
38
+ ) {
39
+ const { finalWidthHeight } = require('../selection')
40
+ //better naming than finalWidthHeight()
41
+ //scale an image to the closest dimension while keeping the ratio intact
42
+ const [final_width, final_height] = finalWidthHeight(
43
+ original_width,
44
+ original_height,
45
+ min_width,
46
+ min_height
47
+ )
48
+ return [final_width, final_height]
49
+ }
50
+
51
+ function mapRange(x, in_min, in_max, out_min, out_max) {
52
+ return ((x - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min
53
+ }
54
+ function scaleToRatio(
55
+ new_value_1,
56
+ old_value_1,
57
+ new_value_2, //get ignored
58
+ old_value_2,
59
+ max_value,
60
+ min_value
61
+ ) {
62
+ const ratio = new_value_1 / old_value_1 // 1000/500 = 2
63
+ let final_new_value_2 = old_value_2 * ratio // 500 * 2 = 1000
64
+ let final_new_value_1 = new_value_1
65
+ if (final_new_value_2 > max_value) {
66
+ ;[_, final_new_value_1] = scaleToRatio(
67
+ max_value,
68
+ old_value_2,
69
+ new_value_1, //get ignored
70
+ old_value_1,
71
+ max_value,
72
+ min_value
73
+ )
74
+ final_new_value_2 = max_value
75
+ } else if (final_new_value_2 < min_value) {
76
+ ;[_, final_new_value_1] = scaleToRatio(
77
+ min_value,
78
+ old_value_2,
79
+ new_value_1, //get ignored
80
+ old_value_1,
81
+ max_value,
82
+ min_value
83
+ )
84
+ final_new_value_2 = min_value
85
+ }
86
+
87
+ return [final_new_value_1, final_new_value_2]
88
+ }
89
+
90
+ function compareVersions(version_1, version_2) {
91
+ //remove the first character v
92
+ version_1 = version_1.slice(1)
93
+ const increments_1 = version_1.split('.').map((sn) => parseInt(sn))
94
+
95
+ version_2 = version_2.slice(1)
96
+ const increments_2 = version_2.split('.').map((sn) => parseInt(sn))
97
+
98
+ let b_older = false // true if version_1 is < than version_2, false if version_1 >= older
99
+ for (let i = 0; i < increments_1.length; ++i) {
100
+ if (increments_1[i] < increments_2[i]) {
101
+ b_older = true
102
+ break
103
+ }
104
+ }
105
+ return b_older
106
+ }
107
+ async function requestOnlineData() {
108
+ const { requestGet } = require('./api')
109
+ const online_data = await requestGet(g_online_data_url)
110
+ return online_data
111
+ }
112
+ function nearestMultiple(input, multiple) {
113
+ //use the following formula for finding the upper value instead of the lower.
114
+ //( ( x - 1 ) | ( m - 1 ) ) + 1
115
+ const nearest_multiple = input - (input % multiple)
116
+ return nearest_multiple
117
+ }
118
+
119
+ function sudoTimer(progress_text = 'Loading ControlNet...') {
120
+ //sudo timer that will count to 100 and update the progress bar.
121
+ //use it for controlNet since block api progress call
122
+ let current_time = 0
123
+ let max_time = 100
124
+ var timerId = setInterval(countdown, 1000)
125
+
126
+ function countdown() {
127
+ if (current_time > max_time) {
128
+ clearTimeout(timerId)
129
+ // doSomething()
130
+ // html_manip.updateProgressBarsHtml(0)
131
+ } else {
132
+ html_manip.updateProgressBarsHtml(current_time, progress_text)
133
+ console.log(current_time + ' seconds remaining')
134
+ current_time++
135
+ }
136
+ }
137
+ return timerId
138
+ }
139
+ function countNewLines(string) {
140
+ const count = (string.match(/\n/g) || []).length
141
+ // console.log(count)
142
+ return count
143
+ }
144
+ module.exports = {
145
+ newOutputImageName,
146
+ makeImagePath,
147
+ convertImageNameToPng,
148
+ fixNativePath,
149
+ base64ToBase64Url,
150
+ base64UrlToBase64,
151
+ timer,
152
+ scaleToClosestKeepRatio,
153
+ scaleToRatio,
154
+ mapRange,
155
+ compareVersions,
156
+ requestOnlineData,
157
+ nearestMultiple,
158
+ sudoTimer,
159
+ countNewLines,
160
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/html_manip.js ADDED
@@ -0,0 +1,1177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ////// Start Prompt//////////
2
+
3
+ function getPrompt() {
4
+ const prompt = document.getElementById('taPrompt').value
5
+ return prompt
6
+ }
7
+
8
+ function autoFillInPrompt(prompt_value) {
9
+ document.getElementById('taPrompt').value = prompt_value
10
+ }
11
+
12
+ ////// End Prompt//////////
13
+
14
+ ////// Start Negative Prompt//////////
15
+
16
+ function getNegativePrompt() {
17
+ const negative_prompt = document.getElementById('taNegativePrompt').value
18
+ return negative_prompt
19
+ }
20
+
21
+ function autoFillInNegativePrompt(negative_prompt_value) {
22
+ document.getElementById('taNegativePrompt').value = negative_prompt_value
23
+ }
24
+
25
+ ////// End Negative Prompt//////////
26
+
27
+ ////// Start Width//////////
28
+
29
+ document.getElementById('slWidth').addEventListener('input', (evt) => {
30
+ const width = evt.target.value * 64
31
+
32
+ document.getElementById('lWidth').textContent = parseInt(width)
33
+ // widthSliderOnChangeEventHandler(evt)
34
+ updateResDifferenceLabel()
35
+ })
36
+
37
+ document.getElementById('slHeight').addEventListener('input', (evt) => {
38
+ const height = evt.target.value * 64
39
+
40
+ document.getElementById('lHeight').textContent = parseInt(height)
41
+ // heightSliderOnChangeEventHandler(evt)
42
+ updateResDifferenceLabel()
43
+ })
44
+
45
+ function widthSliderOnChangeEventHandler(evt) {
46
+ let new_width = evt.target.value * 64
47
+ const b_link = getLinkWidthHeightState()
48
+ let final_width = new_width
49
+ let final_height
50
+ if (b_link) {
51
+ const current_height = html_manip.getHeight()
52
+ ;[final_width, final_height] = general.scaleToRatio(
53
+ new_width,
54
+ g_old_slider_width,
55
+ _,
56
+ current_height,
57
+ parseInt(evt.target.max * 64),
58
+ parseInt(evt.target.min * 64)
59
+ )
60
+
61
+ evt.target.value = parseInt(final_width / 64)
62
+ html_manip.autoFillInHeight(final_height)
63
+ }
64
+
65
+ g_old_slider_width = final_width // update the old value, so we can use it later
66
+ document.getElementById('lWidth').textContent = parseInt(final_width)
67
+ }
68
+ document.getElementById('slWidth').addEventListener('change', (evt) => {
69
+ widthSliderOnChangeEventHandler(evt)
70
+ })
71
+ // document.getElementById('slWidth').addEventListener('change', (evt) => {
72
+ // let new_width = evt.target.value * 64
73
+ // const b_link = getLinkWidthHeightState()
74
+ // let final_width = new_width
75
+ // let final_height
76
+ // if (b_link) {
77
+ // const current_height = html_manip.getHeight()
78
+ // ;[final_width, final_height] = general.scaleToRatio(
79
+ // new_width,
80
+ // g_old_slider_width,
81
+ // _,
82
+ // current_height,
83
+ // parseInt(evt.target.max * 64),
84
+ // parseInt(evt.target.min * 64)
85
+ // )
86
+
87
+ // evt.target.value = parseInt(final_width / 64)
88
+ // html_manip.autoFillInHeight(final_height)
89
+ // }
90
+
91
+ // g_old_slider_width = final_width // update the old value, so we can use it later
92
+ // document.getElementById('lWidth').textContent = parseInt(final_width)
93
+ // })
94
+
95
+ function heightSliderOnChangeEventHandler(evt) {
96
+ let new_height = evt.target.value * 64
97
+
98
+ let final_width
99
+ let final_height = new_height
100
+ const b_link = getLinkWidthHeightState()
101
+ if (b_link) {
102
+ const current_width = html_manip.getWidth()
103
+ ;[final_height, final_width] = general.scaleToRatio(
104
+ new_height,
105
+ g_old_slider_height,
106
+ _,
107
+ current_width,
108
+ parseInt(evt.target.max * 64),
109
+ parseInt(evt.target.min * 64)
110
+ )
111
+
112
+ evt.target.value = parseInt(final_height / 64)
113
+ html_manip.autoFillInWidth(final_width)
114
+ }
115
+ g_old_slider_height = final_height // update the old value, so we can use it later
116
+ document.getElementById('lHeight').textContent = parseInt(final_height)
117
+ }
118
+ document.getElementById('slHeight').addEventListener('change', (evt) => {
119
+ heightSliderOnChangeEventHandler(evt)
120
+ })
121
+
122
+ function getWidth() {
123
+ slider_width = document.getElementById('slWidth').value
124
+ const width = slider_width * 64
125
+ return width
126
+ }
127
+
128
+ function getHrWidth() {
129
+ slider_width = document.getElementById('hrWidth').value
130
+ const width = slider_width * 64
131
+ return width
132
+ }
133
+
134
+ function getHrHeight() {
135
+ slider_width = document.getElementById('hrHeight').value
136
+ const width = slider_width * 64
137
+ return width
138
+ }
139
+ function autoFillInWidth(width_value) {
140
+ const width_slider = document.getElementById('slHeight')
141
+
142
+ // g_old_slider_width = width_slider.value * 64 //store the old value
143
+ g_old_slider_width = width_value
144
+
145
+ document.getElementById('slWidth').value = `${width_value / 64}`
146
+ //update the label
147
+ document.getElementById('lWidth').innerHTML = `${parseInt(width_value)}`
148
+ updateResDifferenceLabel()
149
+ }
150
+ ////// End Width//////////
151
+
152
+ ////// Start Height//////////
153
+
154
+ function getHeight() {
155
+ slider_value = document.getElementById('slHeight').value
156
+ const height = slider_value * 64
157
+ return height
158
+ }
159
+
160
+ function autoFillInHeight(height_value) {
161
+ const height_slider = document.getElementById('slHeight')
162
+ // g_old_slider_height = height_slider.value * 64
163
+ g_old_slider_height = height_value //store the current value as old value. counterintuitive!. only use old value when the user directly manipulate the slider
164
+
165
+ height_slider.value = `${height_value / 64}`
166
+ //update the label
167
+ document.getElementById('lHeight').innerHTML = `${parseInt(height_value)}`
168
+ updateResDifferenceLabel()
169
+ }
170
+
171
+ function autoFillInHRHeight(height_value) {
172
+ document.getElementById('hrHeight').value = `${height_value / 64}`
173
+ //update the label
174
+ document.getElementById('hHeight').innerHTML = `${height_value}`
175
+ }
176
+
177
+ function autoFillInHRWidth(height_value) {
178
+ document.getElementById('hrWidth').value = `${height_value / 64}`
179
+ //update the label
180
+ document.getElementById('hWidth').innerHTML = `${height_value}`
181
+ }
182
+
183
+ ////// End Height//////////
184
+
185
+ ////// Start Denoising Strength//////////
186
+ document
187
+ .querySelector('#slDenoisingStrength')
188
+ .addEventListener('input', (evt) => {
189
+ const label_value = evt.target.value / 100
190
+ // console.log("label_value: ", label_value)
191
+ document.getElementById(
192
+ 'lDenoisingStrength'
193
+ ).innerHTML = `${label_value}`
194
+ })
195
+
196
+ //get the value that is relevant to stable diffusion
197
+ function getDenoisingStrength() {
198
+ const slider_value = document.getElementById('slDenoisingStrength').value
199
+ const denoising_strength_value = slider_value / 100.0
200
+ return denoising_strength_value
201
+ }
202
+
203
+ // display the value the user need to see in all elements related to denoising strength attribute
204
+ function autoFillInDenoisingStrength(denoising_strength_value) {
205
+ //sd denoising strength value range from [0,1] slider range from [0, 100]
206
+ //update the slider
207
+ document.getElementById('slDenoisingStrength').value = `${
208
+ denoising_strength_value * 100
209
+ }`
210
+ //update the label
211
+ document.getElementById(
212
+ 'lDenoisingStrength'
213
+ ).innerHTML = `${denoising_strength_value}`
214
+ }
215
+
216
+ ////// End Denoising Strength//////////
217
+
218
+ ////// Start Hi Res Fix//////////
219
+
220
+ document.getElementById('chInpaintFullRes').addEventListener('click', (ev) => {
221
+ const inpaint_padding_slider = document.getElementById('slInpaintPadding')
222
+
223
+ if (ev.target.checked) {
224
+ inpaint_padding_slider.style.display = 'block'
225
+ } else {
226
+ inpaint_padding_slider.style.display = 'none'
227
+ }
228
+ })
229
+ document.getElementById('chHiResFixs').addEventListener('click', (ev) => {
230
+ const container = document.getElementById('hi-res-sliders-container')
231
+
232
+ if (ev.target.checked) {
233
+ container.style.display = 'flex'
234
+ } else {
235
+ container.style.display = 'none'
236
+ }
237
+ })
238
+ //get the value that is relevant to stable diffusion
239
+ function getHiResFixs() {
240
+ const isChecked = document.getElementById('chHiResFixs').checked
241
+ return isChecked
242
+ }
243
+
244
+ function setHiResFixs(isChecked) {
245
+ document.getElementById('chHiResFixs').checked = isChecked
246
+ }
247
+
248
+ function sliderAddEventListener(
249
+ slider_id,
250
+ label_id,
251
+ multiplier,
252
+ fractionDigits = 2
253
+ ) {
254
+ document.getElementById(slider_id).addEventListener('input', (evt) => {
255
+ const sd_value = evt.target.value * multiplier // convert slider value to SD ready value
256
+ document.getElementById(label_id).textContent =
257
+ Number(sd_value).toFixed(fractionDigits)
258
+ })
259
+ }
260
+ function sliderAddEventListener_new(
261
+ slider_id,
262
+ label_id,
263
+ slider_start,
264
+ slider_end,
265
+ sd_start,
266
+ sd_end
267
+ ) {
268
+ document.getElementById(slider_id).addEventListener('input', (evt) => {
269
+ const sd_value = general.mapRange(
270
+ evt.target.value,
271
+ slider_start,
272
+ slider_end,
273
+ sd_start,
274
+ sd_end
275
+ ) // convert slider value to SD ready value
276
+
277
+ document.getElementById(label_id).textContent =
278
+ Number(sd_value).toFixed(2)
279
+ })
280
+ }
281
+
282
+ //get the stable diffusion ready value from the slider with "slider_id"
283
+ //REFACTOR: delete, getSliderSdValue_Old is deprecated, instead use getSliderSdValue
284
+ function getSliderSdValue_Old(slider_id, multiplier) {
285
+ // console.warn(
286
+ // 'getSliderSdValue_Old is deprecated, instead use getSliderSdValue'
287
+ // )
288
+ const slider_value = document.getElementById(slider_id).value
289
+ const sd_value = slider_value * multiplier
290
+ return sd_value
291
+ }
292
+ //REFACTOR: delete, autoFillInSliderUi is deprecated, instead use setSliderSdValue
293
+ function autoFillInSliderUi(sd_value, slider_id, label_id, multiplier) {
294
+ // console.warn(
295
+ // 'autoFillInSliderUi is deprecated, instead use setSliderSdValue'
296
+ // )
297
+ //update the slider
298
+ document.getElementById(slider_id).value = `${sd_value * multiplier}`
299
+ //update the label
300
+ document.getElementById(label_id).innerHTML = `${sd_value}`
301
+ }
302
+
303
+ function getSliderSdValue(
304
+ slider_id,
305
+ slider_start,
306
+ slider_end,
307
+ sd_start,
308
+ sd_end
309
+ ) {
310
+ const slider_value = document.getElementById(slider_id).value
311
+ // const sd_value = general.mapRange(slider_value, 0, 100, 0, 1) // convert slider value to SD ready value
312
+ const sd_value = general.mapRange(
313
+ slider_value,
314
+ slider_start,
315
+ slider_end,
316
+ sd_start,
317
+ sd_end
318
+ ) // convert slider value to SD ready value
319
+
320
+ return sd_value
321
+ }
322
+ function setSliderSdValue(
323
+ slider_id,
324
+ label_id,
325
+ sd_value,
326
+ slider_start,
327
+ slider_end,
328
+ sd_start,
329
+ sd_end
330
+ ) {
331
+ const slider_value = general.mapRange(
332
+ sd_value,
333
+ sd_start,
334
+ sd_end,
335
+ slider_start,
336
+ slider_end
337
+ ) // convert slider value to SD ready value
338
+ document.getElementById(slider_id).value = slider_value.toString()
339
+ document.getElementById(label_id).innerHTML = sd_value.toString()
340
+ }
341
+ function getSliderSdValueByElement(
342
+ slider_element,
343
+ slider_start,
344
+ slider_end,
345
+ sd_start,
346
+ sd_end
347
+ ) {
348
+ const slider_value = slider_element.value
349
+ // const sd_value = general.mapRange(slider_value, 0, 100, 0, 1) // convert slider value to SD ready value
350
+ const sd_value = general.mapRange(
351
+ slider_value,
352
+ slider_start,
353
+ slider_end,
354
+ sd_start,
355
+ sd_end
356
+ ) // convert slider value to SD ready value
357
+
358
+ return sd_value
359
+ }
360
+ function setSliderSdValueByElements(
361
+ slider_element,
362
+ label_element,
363
+ sd_value,
364
+ slider_start,
365
+ slider_end,
366
+ sd_start,
367
+ sd_end
368
+ ) {
369
+ const slider_value = general.mapRange(
370
+ sd_value,
371
+ sd_start,
372
+ sd_end,
373
+ slider_start,
374
+ slider_end
375
+ ) // convert slider value to SD ready value
376
+ slider_element.value = slider_value.toString()
377
+ label_element.innerHTML = sd_value.toString()
378
+ }
379
+
380
+ //hrWidth is from [1 to 32] * 64 => [64 to 2048]
381
+ sliderAddEventListener('hrWidth', 'hWidth', 64)
382
+ sliderAddEventListener('hrHeight', 'hHeight', 64)
383
+
384
+ //convert hrDenoisingStrength from [1, 100] * 0.01 => [0.01 to 1]
385
+ sliderAddEventListener('hrDenoisingStrength', 'hDenoisingStrength', 0.01)
386
+
387
+ function autoFillInHiResFixs(firstphase_width, firstphase_height) {
388
+ //update the firstphase width slider and label
389
+ autoFillInSliderUi(firstphase_width, 'hrWidth', 'hWidth', 1.0 / 64)
390
+ //update the firstphase height slider and label
391
+ autoFillInSliderUi(firstphase_height, 'hrHeight', 'hHeight', 1.0 / 64)
392
+ }
393
+ ////// End Hi Res Fix//////////
394
+
395
+ ////// Start Inpaint Mask Weight//////////
396
+ function autoFillInInpaintMaskWeight(sd_value) {
397
+ //update the inpaint mask weight
398
+ autoFillInSliderUi(
399
+ sd_value,
400
+ 'slInpaintingMaskWeight',
401
+ 'lInpaintingMaskWeight',
402
+ 100
403
+ )
404
+ }
405
+ ////// End Inpaint Mask Weight//////////
406
+
407
+ ////// Start Samplers//////////
408
+ function unCheckAllSamplers() {
409
+ document
410
+ .getElementsByClassName('rbSampler')
411
+
412
+ .forEach((e) => e.removeAttribute('checked'))
413
+ }
414
+
415
+ function getSelectedRadioButtonElement(rbClass) {
416
+ try {
417
+ const rb_element = [...document.getElementsByClassName(rbClass)].filter(
418
+ (e) => e.checked == true
419
+ )[0]
420
+ return rb_element
421
+ } catch (e) {
422
+ console.warn(e)
423
+ }
424
+ }
425
+ function getSamplerElementByName(sampler_name) {
426
+ try {
427
+ //assume the sampler_name is valid
428
+ //return the first
429
+ //convert htmlCollection into an array, then user filter to get the radio button with the value equals to sampler_name
430
+ const sampler_element = [
431
+ ...document.getElementsByClassName('rbSampler'),
432
+ ].filter((e) => e.value == sampler_name)[0]
433
+ return sampler_element
434
+ } catch (e) {
435
+ console.warn(`Sampler '${sampler_name}' not found ${e}`)
436
+ }
437
+ }
438
+
439
+ function getCheckedSamplerName() {
440
+ //we assume that the samplers exist and loaded in html
441
+ //return the name of the first checked sampler
442
+ try {
443
+ return [...document.getElementsByClassName('rbSampler')].filter(
444
+ (elm) => elm.checked == true
445
+ )[0].value
446
+ } catch (e) {
447
+ console.warn(e)
448
+ }
449
+ }
450
+ function getMode() {
451
+ return [...document.getElementsByClassName('rbMode')].filter(
452
+ (e) => e.checked == true
453
+ )[0].value
454
+ }
455
+
456
+ function getBackendType() {
457
+ return [...document.getElementsByClassName('rbBackendType')].filter(
458
+ (e) => e.checked == true
459
+ )[0].value
460
+ }
461
+
462
+ function getHordeApiKey() {
463
+ let key = document.getElementById('tiHordeApiKey').value
464
+ const valid_key = key ? key : '0000000000'
465
+ return valid_key
466
+ }
467
+
468
+ function setHordeApiKey(key) {
469
+ document.getElementById('tiHordeApiKey').value = key
470
+ }
471
+ function checkSampler(sampler_name) {
472
+ sampler_element = getSamplerElementByName(sampler_name)
473
+ sampler_element.checked = true
474
+ }
475
+ function autoFillInSampler(sampler_name) {
476
+ // unCheckAllSamplers()
477
+ checkSampler(sampler_name)
478
+ }
479
+ ////// End Samplers//////////
480
+
481
+ ////// Start Models//////////
482
+
483
+ function getModelElementByHash(model_hash) {
484
+ try {
485
+ //assume the model_hash is valid
486
+ //return the first model menu item element with model_hash
487
+ const model_element = [
488
+ ...document.getElementsByClassName('mModelMenuItem'),
489
+ ].filter((e) => e.dataset.model_hash == model_hash)[0]
490
+ return model_element
491
+ } catch (e) {
492
+ console.warn(`Model '${model_hash}' not found ${e}`)
493
+ }
494
+ }
495
+ function getModelHashByTitle(model_title) {
496
+ //return find the model hash by it's title
497
+ try {
498
+ return [...document.getElementsByClassName('mModelMenuItem')].filter(
499
+ (e) => e.dataset.model_title == model_title
500
+ )[0].dataset.model_hash
501
+ } catch (e) {
502
+ console.warn(e)
503
+ }
504
+ }
505
+
506
+ function getSelectedModelHash() {
507
+ //return the hash of the first selected model menu item
508
+ try {
509
+ return [...document.getElementsByClassName('mModelMenuItem')].filter(
510
+ (e) => e.selected == true
511
+ )[0].dataset.model_hash
512
+ } catch (e) {
513
+ console.warn(e)
514
+ }
515
+ }
516
+
517
+ function selectModelUi(model_hash) {
518
+ model_element = getModelElementByHash(model_hash)
519
+ model_element.selected = true
520
+ }
521
+
522
+ function autoFillInModel(model_hash) {
523
+ try {
524
+ // unCheckAllSamplers()
525
+ model_element = getModelElementByHash(model_hash)
526
+ selectModelUi(model_hash)
527
+ // model_element.
528
+ const model_title = model_element.dataset.model_title
529
+ return model_title
530
+ } catch (e) {
531
+ console.warn(e)
532
+ }
533
+ }
534
+ ////// End Models//////////
535
+
536
+ ////// Start Init Image && Init Image Mask//////////
537
+
538
+ function getInitImageElement() {
539
+ const ini_image_element = document.getElementById('init_image')
540
+ return ini_image_element
541
+ }
542
+ function setInitImageSrc(image_src) {
543
+ const ini_image_element = getInitImageElement()
544
+ ini_image_element.src = image_src
545
+ }
546
+ function setControlImageSrc(image_src, element_index = 0) {
547
+ const control_net_image_element = document.querySelector(
548
+ `#controlnet_settings_${element_index} .control_net_image_`
549
+ )
550
+ // const control_net_image_element = document.getElementById(
551
+ // 'control_net_image' + '_' + element_index
552
+ // )
553
+ control_net_image_element.src = image_src
554
+ }
555
+ function setControlMaskSrc(image_src, element_index = 0) {
556
+ const control_net_image_element = document.querySelector(
557
+ `#controlnet_settings_${element_index} .control_net_mask_`
558
+ )
559
+ // const control_net_image_element = document.getElementById(
560
+ // 'control_net_mask' + '_' + element_index
561
+ // )
562
+ control_net_image_element.src = image_src
563
+ }
564
+
565
+ function setProgressImageSrc(image_src) {
566
+ // const progress_image_element = document.getElementById('progressImage')
567
+
568
+ const progress_image_element = document.getElementById(
569
+ 'divProgressImageViewerContainer'
570
+ )
571
+ // progress_image_element.src = image_src
572
+
573
+ progress_image_element.style.backgroundSize = 'contain'
574
+ progress_image_element.style.height = '500px'
575
+
576
+ progress_image_element.style.backgroundImage = `url('${image_src}')`
577
+ }
578
+
579
+ function getInitImageMaskElement() {
580
+ const ini_image_mask_element = document.getElementById('init_image_mask')
581
+ return ini_image_mask_element
582
+ }
583
+ function setInitImageMaskSrc(image_src) {
584
+ const ini_image_mask_element = getInitImageMaskElement()
585
+ ini_image_mask_element.src = image_src
586
+ }
587
+ ////// End Init Image && Init Image Mask//////////
588
+
589
+ ////// Start Generate Buttons //////////
590
+
591
+ function getGenerateButtonsElements() {
592
+ generate_buttons = [...document.getElementsByClassName('btnGenerateClass')]
593
+ return generate_buttons
594
+ }
595
+ function setGenerateButtonsColor(addClassName, removeClassName) {
596
+ const buttons = getGenerateButtonsElements()
597
+ buttons.forEach((button) => {
598
+ button.classList.add(addClassName)
599
+ button.classList.remove(removeClassName)
600
+ })
601
+ }
602
+
603
+ ////// End Generate Buttons //////////
604
+
605
+ ////// Start Servers Status //////////
606
+
607
+ function setAutomaticStatus(newStatusClass, oldStatusClass) {
608
+ document.getElementById('automaticStatus').classList.add(newStatusClass)
609
+ document.getElementById('automaticStatus').classList.remove(oldStatusClass)
610
+ }
611
+ function setProxyServerStatus(newStatusClass, oldStatusClass) {
612
+ document.getElementById('proxyServerStatus').classList.add(newStatusClass)
613
+ document
614
+ .getElementById('proxyServerStatus')
615
+ .classList.remove(oldStatusClass)
616
+ }
617
+ ////// End Servers Status //////////
618
+
619
+ ////// Start Extras //////////
620
+
621
+ sliderAddEventListener('slUpscaleSize', 'lUpscaleSize', 0.1, 1)
622
+
623
+ function getUpscaleSize() {
624
+ slider_width = document.getElementById('slUpscaleSize').value
625
+ const size = slider_width / 10
626
+ return size
627
+ }
628
+
629
+ sliderAddEventListener('slUpscaler2Visibility', 'lUpscaler2Visibility', 0.1, 1)
630
+
631
+ function getUpscaler2Visibility() {
632
+ slider_width = document.getElementById('slUpscaler2Visibility').value
633
+ const size = slider_width / 10
634
+ return size
635
+ }
636
+
637
+ sliderAddEventListener('slGFPGANVisibility', 'lGFPGANVisibility', 0.1, 1)
638
+
639
+ function getGFPGANVisibility() {
640
+ slider_width = document.getElementById('slGFPGANVisibility').value
641
+ const size = slider_width / 10
642
+ return size
643
+ }
644
+
645
+ sliderAddEventListener(
646
+ 'slCodeFormerVisibility',
647
+ 'lCodeFormerVisibility',
648
+ 0.1,
649
+ 1
650
+ )
651
+
652
+ function getCodeFormerVisibility() {
653
+ slider_width = document.getElementById('slCodeFormerVisibility').value
654
+ const size = slider_width / 10
655
+ return size
656
+ }
657
+
658
+ sliderAddEventListener('slCodeFormerWeight', 'lCodeFormerWeight', 0.1, 1)
659
+
660
+ function getCodeFormerWeight() {
661
+ slider_width = document.getElementById('slCodeFormerWeight').value
662
+ const size = slider_width / 10
663
+ return size
664
+ }
665
+
666
+ ////// End Extras //////////
667
+
668
+ ////// Start Reset Settings Button //////////
669
+
670
+ const defaultSettings = {
671
+ model: null,
672
+ prompt_shortcut: null,
673
+ positive_prompt: '',
674
+ negative_prompt: '',
675
+ selection_mode: null,
676
+ batch_number: 1,
677
+ steps: 20,
678
+ width: 512,
679
+ height: 512,
680
+ firstphase_width: 512,
681
+ firstphase_height: 512,
682
+ cfg: 7,
683
+ denoising_strength: 0.7,
684
+ hi_res_denoising_strength: 0.7,
685
+ mask_blur: 8,
686
+ inpaint_at_full_res: false,
687
+ hi_res_fix: false,
688
+ inpaint_padding: 0,
689
+ seed: -1,
690
+ samplers: null,
691
+ mask_content: null,
692
+ }
693
+
694
+ const snapshot_btns = Array.from(
695
+ document.getElementsByClassName('snapshotButton')
696
+ )
697
+ snapshot_btns.forEach((element) =>
698
+ element.addEventListener('click', async () => {
699
+ try {
700
+ await psapi.snapshot_layerExe()
701
+ } catch (e) {
702
+ console.warn(e)
703
+ }
704
+ })
705
+ )
706
+
707
+ const reset_btns = Array.from(document.getElementsByClassName('resetButton'))
708
+ reset_btns.forEach((element) =>
709
+ element.addEventListener('click', async () => {
710
+ try {
711
+ autoFillDefaultSettings(defaultSettings)
712
+ } catch (e) {
713
+ console.warn(e)
714
+ }
715
+ })
716
+ )
717
+
718
+ function getBatchNumber() {
719
+ // return document.getElementById('tiNumberOfImages').value
720
+ return document.getElementById('tiNumberOfBatchSize').value
721
+ }
722
+ function autoFillInBatchNumber(batch_number) {
723
+ // document.getElementById('tiNumberOfImages').value = String(batch_number)
724
+ document.getElementById('tiNumberOfBatchSize').value = String(batch_number)
725
+ }
726
+
727
+ function getSteps() {
728
+ return document.getElementById('tiNumberOfSteps').value
729
+ }
730
+ function autoFillInSteps(steps) {
731
+ document.getElementById('tiNumberOfSteps').value = String(steps)
732
+ }
733
+ function autoFillDefaultSettings(default_settings) {
734
+ autoFillSettings(default_settings)
735
+ }
736
+
737
+ function setCFG(cfg_value) {
738
+ document.getElementById('slCfgScale').value = cfg_value
739
+ }
740
+ function getCFG() {
741
+ return document.getElementById('slCfgScale').value
742
+ }
743
+
744
+ function autoFillSettings(settings) {
745
+ try {
746
+ //reset all UI settings except model selection and sampler selection
747
+ autoFillInPrompt(settings['positive_prompt'])
748
+ autoFillInNegativePrompt(settings['negative_prompt'])
749
+ autoFillInBatchNumber(settings['batch_number'])
750
+ autoFillInSteps(settings['steps'])
751
+ autoFillInWidth(settings['width'])
752
+ autoFillInHeight(settings['height'])
753
+ autoFillInHiResFixs(
754
+ settings['firstphase_width'],
755
+ settings['firstphase_height']
756
+ )
757
+ document.getElementById('slCfgScale').value = settings['cfg']
758
+ autoFillInDenoisingStrength(settings['denoising_strength'])
759
+ autoFillInSliderUi(
760
+ settings['hi_res_denoising_strength'],
761
+ 'hrDenoisingStrength',
762
+ 'hDenoisingStrength',
763
+ 100
764
+ )
765
+ document.getElementById('slMaskBlur').value = settings['mask_blur']
766
+ document.getElementById('chInpaintFullRes').checked =
767
+ settings['inpaint_at_full_res']
768
+ setHiResFixs(settings['hi_res_fix'])
769
+ document.getElementById('tiSeed').value = String(settings['seed'])
770
+ } catch (e) {
771
+ console.warn(e)
772
+ }
773
+ }
774
+ ////// End Reset Settings Button //////////
775
+
776
+ function getMaskBlur() {
777
+ const isDisabled = document
778
+ .getElementById('slMaskBlur')
779
+ .hasAttribute('disabled')
780
+ let mask_blur = 0
781
+ if (isDisabled) {
782
+ mask_blur = 0
783
+ } else {
784
+ mask_blur = document.getElementById('slMaskBlur').value
785
+ }
786
+ return mask_blur
787
+ }
788
+ function setMaskBlur(mask_blur) {
789
+ document.getElementById('slMaskBlur').value = mask_blur
790
+ }
791
+
792
+ function getPromptShortcut() {
793
+ //read json string
794
+ //converted into json object
795
+ const prompt_shortcut_string =
796
+ document.getElementById('taPromptShortcut').value
797
+ const prompt_shortcut = JSON.parse(prompt_shortcut_string)
798
+ return prompt_shortcut
799
+ }
800
+ function setPromptShortcut(prompt_shortcut) {
801
+ //prompt_shortcut is json object
802
+ //convert it into pretty json string and save it in the prompt shortcut textarea
803
+ var JSONInPrettyFormat = JSON.stringify(prompt_shortcut, undefined, 7)
804
+ document.getElementById('taPromptShortcut').value = JSONInPrettyFormat
805
+ }
806
+
807
+ ////start selection mode/////
808
+ function getSelectionMode() {
809
+ return [...document.getElementsByClassName('rbSelectionMode')].filter(
810
+ (e) => e.checked == true
811
+ )[0].value
812
+ }
813
+ function getMaskContent() {
814
+ return [...document.getElementsByClassName('rbMaskContent')].filter(
815
+ (e) => e.checked == true
816
+ )[0].value
817
+ }
818
+ function setMaskContent(value) {
819
+ try {
820
+ //assume the sampler_name is valid
821
+ //return the first
822
+ //convert htmlCollection into an array, then user filter to get the radio button with the value equals to sampler_name
823
+ const mask_content_element = [
824
+ ...document.getElementsByClassName('rbMaskContent'),
825
+ ].filter((e) => e.value == value)[0]
826
+ mask_content_element.checked = true
827
+ return mask_content_element
828
+ } catch (e) {
829
+ console.warn(e)
830
+ }
831
+ }
832
+
833
+ function addHistoryButtonsHtml(img_html) {
834
+ // Create new container element
835
+ const container = document.createElement('div')
836
+
837
+ container.className = 'viewer-image-container'
838
+
839
+ const elem = document.getElementById('svg_sp_btn')
840
+ // Create a copy of it
841
+ const clone = elem.cloneNode(true)
842
+ const button = clone
843
+ button.style.display = null
844
+ button.removeAttribute('id')
845
+
846
+ button.setAttribute('title', 'place the image on the canvas')
847
+
848
+ // Create button element
849
+ // const button = document.createElement('sp-button');
850
+ button.className = 'viewer-image-button'
851
+ // button.innerHTML = "Button";
852
+
853
+ button.addEventListener('click', async () => {
854
+ //set init image event listener, use when settion is active
855
+ let image_path = img_html.dataset.path
856
+ const image_path_escape = image_path.replace(/\o/g, '/o') //escape string "\o" in "\output"
857
+
858
+ // load the image from "data:image/png;base64," base64_str
859
+ const base64_image = img_html.src.replace('data:image/png;base64,', '')
860
+ await base64ToFile(base64_image)
861
+ })
862
+
863
+ // Append elements to container
864
+ container.appendChild(img_html)
865
+ container.appendChild(button)
866
+
867
+ return container
868
+ }
869
+ function getSeed() {
870
+ const seed = document.getElementById('tiSeed').value
871
+ return seed
872
+ }
873
+ function setSeed(new_seed) {
874
+ document.getElementById('tiSeed').value = new_seed
875
+ }
876
+
877
+ function getMaskExpansion() {
878
+ const mask_expansion = document.getElementById('slMaskExpansion').value
879
+ return mask_expansion
880
+ }
881
+
882
+ function setMaskExpansion(mask_expansion) {
883
+ document.getElementById('slMaskExpansion').value = mask_expansion
884
+ }
885
+
886
+ function updateProgressBarsHtml(new_value, progress_text = 'Progress...') {
887
+ document.querySelectorAll('.pProgressBars').forEach((bar_elm) => {
888
+ // id = el.getAttribute("id")
889
+ // console.log("progressbar id:", id)
890
+ try {
891
+ bar_elm.setAttribute('value', new_value)
892
+ document
893
+ .querySelectorAll('.lProgressLabel')
894
+ .forEach((lable_elm) => {
895
+ lable_elm.innerHTML = progress_text
896
+ // else el.innerHTML = 'No work in progress'
897
+ })
898
+ } catch (e) {
899
+ console.warn(e) //value is not valid
900
+ }
901
+ })
902
+
903
+ // document.querySelector('#pProgressBar').value
904
+ }
905
+ ///end selection mode////
906
+ function getLinkWidthHeightState() {
907
+ const state_str = document.getElementById('linkWidthHeight').dataset.b_link // sometime it's true and other time it's "true"
908
+ const b_state = state_str.toString() === 'true' ? true : false
909
+ return b_state
910
+ }
911
+ function setLinkWidthHeightState(state) {
912
+ document.getElementById('linkWidthHeight').dataset.b_link = state
913
+ }
914
+ function isSquareThumbnail() {
915
+ return document.getElementById('chSquareThumbnail').checked
916
+ }
917
+
918
+ async function populateMenu(
919
+ html_menu_id,
920
+ menu_item_class,
921
+ items,
922
+ createMenuItemHtml,
923
+ b_keep_old_selection = false,
924
+ label = ''
925
+ ) {
926
+ // function createMenuItemHtml(item, item_html_element) {
927
+ // // menu_item_element.innerHTML = item.title
928
+ // // menu_item_element.dataset.model_hash = model.hash
929
+ // // menu_item_element.dataset.model_title = model.title
930
+ // }
931
+
932
+ try {
933
+ const html_menu_element = document.getElementById(html_menu_id)
934
+ html_menu_element.innerHTML = '' // empty the menu
935
+ if (label) {
936
+ const label_item = document.createElement('sp-menu-item')
937
+ label_item.selected = true
938
+ label_item.disabled = true
939
+ label_item.innerText = label
940
+ html_menu_element.appendChild(label_item)
941
+ }
942
+ for (let item of items) {
943
+ const menu_item_element = document.createElement('sp-menu-item')
944
+ menu_item_element.className = menu_item_class
945
+ createMenuItemHtml(item, menu_item_element)
946
+ html_menu_element.appendChild(menu_item_element)
947
+ }
948
+ } catch (e) {
949
+ b_result = false
950
+ console.warn(e)
951
+ }
952
+ return b_result
953
+ }
954
+ async function populateMenuByElement(
955
+ html_menu_element,
956
+ menu_item_class,
957
+ items,
958
+ createMenuItemHtml,
959
+ b_keep_old_selection = false,
960
+ label = ''
961
+ ) {
962
+ // function createMenuItemHtml(item, item_html_element) {
963
+ // // menu_item_element.innerHTML = item.title
964
+ // // menu_item_element.dataset.model_hash = model.hash
965
+ // // menu_item_element.dataset.model_title = model.title
966
+ // }
967
+
968
+ try {
969
+ html_menu_element.innerHTML = '' // empty the menu
970
+ if (label) {
971
+ const label_item = document.createElement('sp-menu-item')
972
+ label_item.selected = true
973
+ label_item.disabled = true
974
+ label_item.innerText = label
975
+ html_menu_element.appendChild(label_item)
976
+ }
977
+ for (let item of items) {
978
+ const menu_item_element = document.createElement('sp-menu-item')
979
+ menu_item_element.className = menu_item_class
980
+ createMenuItemHtml(item, menu_item_element)
981
+ html_menu_element.appendChild(menu_item_element)
982
+ }
983
+ } catch (e) {
984
+ b_result = false
985
+ console.warn(e)
986
+ }
987
+ return b_result
988
+ }
989
+ function getSelectedMenuItem(menu_id) {
990
+ try {
991
+ const menu_element = document.getElementById(menu_id)
992
+ return menu_element.selectedOptions[0]
993
+ } catch (e) {
994
+ console.warn(e)
995
+ }
996
+ }
997
+ function getSelectedMenuItemByElement(menu_element) {
998
+ try {
999
+ // const menu_element = document.getElementById(menu_id)
1000
+ return menu_element.selectedOptions[0]
1001
+ } catch (e) {
1002
+ console.warn(e)
1003
+ }
1004
+ }
1005
+ function selectMenuItem(menu_id, item) {
1006
+ try {
1007
+ const menu_element = document.getElementById(menu_id)
1008
+ const option = Array.from(menu_element.options).filter(
1009
+ (element) => element.value === item
1010
+ )[0]
1011
+ option.selected = true
1012
+ } catch (e) {
1013
+ unselectMenuItem(menu_id)
1014
+ console.warn(e)
1015
+ }
1016
+ }
1017
+ function selectMenuItemByElement(menu_element, item) {
1018
+ try {
1019
+ const option = Array.from(menu_element.options).filter(
1020
+ (element) => element.value === item
1021
+ )[0]
1022
+ // debugger
1023
+ option.selected = true
1024
+ // option.dispatchEvent(new Event('click'))
1025
+ // option.click()
1026
+ } catch (e) {
1027
+ unselectMenuItemByElement(menu_element)
1028
+ console.warn(e)
1029
+ }
1030
+ }
1031
+ function getSelectedMenuItemTextContent(menu_id) {
1032
+ try {
1033
+ const selected_item = getSelectedMenuItem(menu_id)
1034
+ if (selected_item.disabled === true) return '' // ignore the label item
1035
+
1036
+ const text_content = selected_item.textContent
1037
+ return text_content
1038
+ } catch (e) {
1039
+ console.warn(e)
1040
+ }
1041
+ }
1042
+ function getSelectedMenuItemTextContentByElement(menu_element) {
1043
+ try {
1044
+ const selected_item = getSelectedMenuItemByElement(menu_element)
1045
+ if (selected_item.disabled === true) return ''
1046
+ const text_content = selected_item.textContent
1047
+ return text_content
1048
+ } catch (e) {
1049
+ console.warn(e)
1050
+ }
1051
+ }
1052
+ function unselectMenuItem(menu_id) {
1053
+ try {
1054
+ document.getElementById(menu_id).selectedIndex = null
1055
+ } catch (e) {
1056
+ console.warn(e)
1057
+ }
1058
+ }
1059
+ function unselectMenuItemByElement(menu_element) {
1060
+ try {
1061
+ menu_element.selectedIndex = null
1062
+ } catch (e) {
1063
+ console.warn(e)
1064
+ }
1065
+ }
1066
+
1067
+ function getUseNsfw() {
1068
+ //this method is shared between horde native and horde script
1069
+ const b_nsfw = document.getElementById('chUseNSFW').checked
1070
+ return b_nsfw
1071
+ }
1072
+ function getUseSilentMode_Old() {
1073
+ const b_use_silent_mode = document.getElementById('chUseSilentMode').checked
1074
+ return b_use_silent_mode
1075
+ }
1076
+ function getUseSilentMode() {
1077
+ let b_use_silent_mode = true //fast machine
1078
+ const pc_speed = getSelectedRadioButtonElement('rbPCSpeed').value
1079
+ if (pc_speed === 'slow') {
1080
+ b_use_silent_mode = false // use noisy mode
1081
+ } else if (pc_speed === 'fast') {
1082
+ b_use_silent_mode = true // use silent mode
1083
+ }
1084
+ // const b_use_silent_mode = document.getElementById('chUseSilentMode').checked
1085
+ return b_use_silent_mode
1086
+ }
1087
+
1088
+ module.exports = {
1089
+ getPrompt,
1090
+ autoFillInPrompt,
1091
+ getNegativePrompt,
1092
+ autoFillInNegativePrompt,
1093
+ getDenoisingStrength,
1094
+ autoFillInDenoisingStrength,
1095
+ getWidth,
1096
+ autoFillInWidth,
1097
+ getHeight,
1098
+ autoFillInHeight,
1099
+ getSliderSdValue,
1100
+ setSliderSdValue,
1101
+ autoFillInHiResFixs,
1102
+ getHiResFixs,
1103
+ setHiResFixs,
1104
+ autoFillInSliderUi,
1105
+ getCheckedSamplerName,
1106
+ autoFillInSampler,
1107
+ autoFillInModel,
1108
+ getMode,
1109
+ setInitImageSrc,
1110
+ setInitImageMaskSrc,
1111
+ setGenerateButtonsColor,
1112
+ setAutomaticStatus,
1113
+ setProxyServerStatus,
1114
+ defaultSettings,
1115
+ autoFillDefaultSettings,
1116
+ autoFillSettings,
1117
+ getMaskBlur,
1118
+ setMaskBlur,
1119
+
1120
+ autoFillInHRHeight,
1121
+ autoFillInHRWidth,
1122
+ getPromptShortcut,
1123
+ setPromptShortcut,
1124
+ getModelHashByTitle,
1125
+ getSelectionMode,
1126
+ autoFillInInpaintMaskWeight,
1127
+ autoFillInSteps,
1128
+ getSteps,
1129
+ getBatchNumber,
1130
+ autoFillInBatchNumber,
1131
+ getHrWidth,
1132
+ getHrHeight,
1133
+ setCFG,
1134
+ getCFG,
1135
+ getMaskContent,
1136
+ setMaskContent,
1137
+ addHistoryButtonsHtml,
1138
+
1139
+ getSeed,
1140
+ setSeed,
1141
+ getMaskExpansion,
1142
+ setMaskExpansion,
1143
+ getUpscaleSize,
1144
+ getUpscaler2Visibility,
1145
+ getCodeFormerVisibility,
1146
+ getGFPGANVisibility,
1147
+ getCodeFormerWeight,
1148
+ updateProgressBarsHtml,
1149
+ getBackendType,
1150
+ getHordeApiKey,
1151
+ setProgressImageSrc,
1152
+ getLinkWidthHeightState,
1153
+ setLinkWidthHeightState,
1154
+ isSquareThumbnail,
1155
+ setControlImageSrc,
1156
+ setControlMaskSrc,
1157
+
1158
+ setHordeApiKey,
1159
+ populateMenu,
1160
+ getSelectedMenuItem,
1161
+ getSelectedMenuItemTextContent,
1162
+ getUseNsfw,
1163
+ getUseSilentMode,
1164
+ unselectMenuItem,
1165
+ selectMenuItem,
1166
+ getSliderSdValue_Old,
1167
+ getSelectedRadioButtonElement,
1168
+ getInitImageMaskElement,
1169
+ sliderAddEventListener_new,
1170
+ getSliderSdValueByElement,
1171
+ setSliderSdValueByElements,
1172
+ populateMenuByElement,
1173
+ selectMenuItemByElement,
1174
+ unselectMenuItemByElement,
1175
+ getSelectedMenuItemByElement,
1176
+ getSelectedMenuItemTextContentByElement,
1177
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/io.js ADDED
@@ -0,0 +1,836 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const psapi = require('../psapi')
2
+ // const sdapi = require('../sdapi_py_re')
3
+ const layer_util = require('../utility/layer')
4
+ const general = require('./general')
5
+ const Jimp = require('../jimp/browser/lib/jimp.min')
6
+
7
+ const { executeAsModal } = require('photoshop').core
8
+ const batchPlay = require('photoshop').action.batchPlay
9
+ const formats = require('uxp').storage.formats
10
+ const storage = require('uxp').storage
11
+ const fs = storage.localFileSystem
12
+
13
+ async function isBlackAndWhiteImage(base64EncodedImage) {
14
+ try {
15
+ // Load your base64 encoded image
16
+ const image = await Jimp.read(Buffer.from(base64EncodedImage, 'base64'))
17
+ console.log(
18
+ 'isBlackAndWhiteImage(): image.bitmap.width, image.bitmap.height: ',
19
+ image.bitmap.width,
20
+ image.bitmap.height
21
+ )
22
+ // Check if your image only has one channel
23
+ return (
24
+ image.bitmap.width === image.bitmap.height &&
25
+ image.bitmap.width === 1
26
+ )
27
+ } catch (e) {
28
+ console.warn(e)
29
+ }
30
+ }
31
+
32
+ async function convertBlackAndWhiteImageToRGBChannels(base64EncodedImage) {
33
+ // Load your base64 encoded image
34
+ const image = await Jimp.read(Buffer.from(base64EncodedImage, 'base64'))
35
+
36
+ // Convert your black and white image to RGB channels
37
+ image.color([
38
+ { apply: 'red', params: [255] },
39
+ { apply: 'green', params: [255] },
40
+ { apply: 'blue', params: [255] },
41
+ ])
42
+
43
+ // Get your base64 encoded black and white image with RGB channels
44
+ const base64EncodedImageWithRGBChannels = await image.getBase64Async(
45
+ Jimp.MIME_JPEG
46
+ )
47
+
48
+ return base64EncodedImageWithRGBChannels
49
+ }
50
+ async function convertBlackAndWhiteImageToRGBChannels2(base64EncodedImage) {
51
+ try {
52
+ // Load your base64 encoded image
53
+ const image = await Jimp.read(Buffer.from(base64EncodedImage, 'base64'))
54
+
55
+ // Convert your black and white image to RGB channels
56
+ image.color([{ apply: 'mix', params: ['#ffffff', 100] }])
57
+
58
+ // Get your base64 encoded black and white image with RGB channels
59
+ const base64EncodedImageWithRGBChannels = await image.getBase64Async(
60
+ Jimp.MIME_JPEG
61
+ )
62
+
63
+ return base64EncodedImageWithRGBChannels
64
+ } catch (e) {
65
+ console.warn(e)
66
+ }
67
+ }
68
+ async function convertBlackAndWhiteImageToRGBChannels3(base64EncodedImage) {
69
+ try {
70
+ // Load your base64 encoded image
71
+ const image = await Jimp.read(Buffer.from(base64EncodedImage, 'base64'))
72
+
73
+ // Convert your black and white image to RGB channels
74
+ // image.color([{ apply: 'mix', params: ['#ffffff', 100] }])
75
+
76
+ // Get your base64 encoded black and white image with RGB channels
77
+ const base64EncodedImageWithRGBChannels = await image.getBase64Async(
78
+ Jimp.MIME_PNG
79
+ )
80
+
81
+ return base64EncodedImageWithRGBChannels
82
+ } catch (e) {
83
+ console.warn(e)
84
+ }
85
+ }
86
+
87
+ async function snapShotLayer() {
88
+ //snapshot layer with no mask
89
+ let command = [
90
+ // Select All Layers current layer
91
+ {
92
+ _obj: 'selectAllLayers',
93
+ _target: [
94
+ { _enum: 'ordinal', _ref: 'layer', _value: 'targetEnum' },
95
+ ],
96
+ },
97
+ // Duplicate current layer
98
+ // {"ID":[459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513],"_obj":"duplicate","_target":[{"_enum":"ordinal","_ref":"layer","_value":"targetEnum"}],"version":5},
99
+ {
100
+ // ID: ids,
101
+ _obj: 'duplicate',
102
+ _target: [
103
+ { _enum: 'ordinal', _ref: 'layer', _value: 'targetEnum' },
104
+ ],
105
+ // version: 5
106
+ },
107
+
108
+ // Merge Layers
109
+ { _obj: 'mergeLayersNew' },
110
+ // Make
111
+ {
112
+ _obj: 'make',
113
+ at: { _enum: 'channel', _ref: 'channel', _value: 'mask' },
114
+ new: { _class: 'channel' },
115
+ using: { _enum: 'userMaskEnabled', _value: 'revealSelection' },
116
+ },
117
+ // Set Selection
118
+ {
119
+ _obj: 'set',
120
+ _target: [{ _property: 'selection', _ref: 'channel' }],
121
+ to: { _enum: 'ordinal', _ref: 'channel', _value: 'targetEnum' },
122
+ },
123
+ //make a group
124
+ {
125
+ _obj: 'make',
126
+ _target: [
127
+ {
128
+ _ref: 'layerSection',
129
+ },
130
+ ],
131
+ from: {
132
+ _ref: 'layer',
133
+ _enum: 'ordinal',
134
+ _value: 'targetEnum',
135
+ },
136
+ layerSectionStart: 512,
137
+ layerSectionEnd: 513,
138
+ name: 'Group 2',
139
+ _options: {
140
+ dialogOptions: 'dontDisplay',
141
+ },
142
+ },
143
+ {
144
+ _obj: 'mergeLayersNew',
145
+ _options: {
146
+ dialogOptions: 'dontDisplay',
147
+ },
148
+ },
149
+ ]
150
+
151
+ const result = await batchPlay(command, {
152
+ synchronousExecution: true,
153
+ modalBehavior: 'execute',
154
+ })
155
+
156
+ return result
157
+ }
158
+
159
+ async function snapShotLayerExe() {
160
+ await executeAsModal(async () => {
161
+ //create a fill layer above the background layer, so that it's present in the snapshot
162
+ try {
163
+ const selectionInfo = await psapi.getSelectionInfoExe()
164
+
165
+ // const backgroundLayer = await app.activeDocument.backgroundLayer
166
+
167
+ await psapi.createSolidLayer(255, 255, 255)
168
+ const solid_layer = await app.activeDocument.activeLayers[0]
169
+ // await psapi.unSelectMarqueeExe()//unselect the
170
+
171
+ // await solid_layer.moveAbove(backgroundLayer)
172
+ // await snapShotLayer() //create a layer with only the opaque pixels
173
+ // await psapi.reSelectMarqueeExe(selectionInfo)
174
+ // await solid_layer.delete()
175
+ } catch (e) {
176
+ console.warn(e)
177
+ }
178
+ })
179
+ await executeAsModal(async () => {
180
+ //create a fill layer above the background layer, so that it's present in the snapshot
181
+ try {
182
+ const solid_layer = await app.activeDocument.activeLayers[0]
183
+ const backgroundLayer = await app.activeDocument.backgroundLayer
184
+ await solid_layer.moveAbove(backgroundLayer)
185
+ await psapi.unselectActiveLayersExe()
186
+ await snapShotLayer() //create a layer with only the opaque pixels
187
+ // await psapi.reSelectMarqueeExe(selectionInfo)
188
+ // await psapi.unSelectMarqueeExe()//unselect the
189
+ await solid_layer.delete()
190
+ } catch (e) {
191
+ console.warn(e)
192
+ }
193
+ })
194
+ }
195
+
196
+ class IO {
197
+ // constructor() {}
198
+ static async exportWebp(layer, export_width, export_height) {
199
+ await executeAsModal(async () => {
200
+ //we assume we have a valid layer rectangular image/layer, no transparency
201
+ const doc_entry = await getCurrentDocFolder() //get the main document folder before we switch doc
202
+ const layer_info = await layer_util.Layer.getLayerInfo(layer)
203
+ //*) create a new document
204
+ const new_doc = await IOHelper.createDocumentExe(
205
+ export_width,
206
+ export_height
207
+ )
208
+ const new_layer = await layer_util.Layer.duplicateToDoc(
209
+ layer,
210
+ new_doc
211
+ )
212
+ //*) resize the layer to the same dimension as the document
213
+
214
+ await layer_util.Layer.scaleTo(
215
+ new_layer,
216
+ new_doc.width,
217
+ new_doc.height
218
+ ) //
219
+ await layer_util.Layer.moveTo(new_layer, 0, 0) //move to the top left corner
220
+ //
221
+ await IOHelper.saveAsWebpExe(doc_entry) //save current document as .webp file, save it into doc_entry folder
222
+ await new_doc.closeWithoutSaving()
223
+ })
224
+ }
225
+ static async exportPng() {}
226
+ static async exportDoc() {}
227
+ static async exportLayer() {}
228
+
229
+ static async base64PngToPngFile(
230
+ base64_png,
231
+ folder,
232
+ image_name = 'temp_base64Png.png'
233
+ ) {
234
+ const arrayBuffer = _base64ToArrayBuffer(base64_png)
235
+
236
+ // const folder = await storage.localFileSystem.getTemporaryFolder()
237
+
238
+ const file = await folder.createFile(image_name, { overwrite: true })
239
+
240
+ await file.write(arrayBuffer, { format: storage.formats.binary })
241
+ return file
242
+ }
243
+ static async openImageFileAsDocument(file_entry) {
244
+ const new_doc = await app.open(file_entry)
245
+ return new_doc
246
+ }
247
+ static async base64PngToBase64Webp(base64_png) {
248
+ let base64_webp
249
+ try {
250
+ await executeAsModal(async () => {
251
+ try {
252
+ const main_doc_entry = await getCurrentDocFolder()
253
+ //save the base64_png to .png file
254
+ const temp_folder = await fs.getTemporaryFolder()
255
+ const png_file = await this.base64PngToPngFile(
256
+ base64_png,
257
+ temp_folder
258
+ )
259
+
260
+ //load the .png file as a layer in new document
261
+ const new_doc = await this.openImageFileAsDocument(png_file)
262
+ //save document as .webp
263
+ const [_, webp_file] = await IOHelper.saveAsWebpExe(
264
+ main_doc_entry
265
+ ) //save current document as .webp file, save it into doc_entry folder
266
+ await new_doc.closeWithoutSaving()
267
+ //load/read the .webp file as an arraybuffer
268
+ const ArrayBufferWebp = await webp_file.read({
269
+ format: formats.binary,
270
+ })
271
+
272
+ //convert the arraybuffer to base64Webp string
273
+
274
+ base64_webp = _arrayBufferToBase64(ArrayBufferWebp)
275
+ } catch (e) {
276
+ console.warn(e)
277
+ }
278
+ })
279
+ return base64_webp
280
+ } catch (e) {
281
+ console.warn(e)
282
+ }
283
+ }
284
+ static async base64WebpFromFile(file_entry) {
285
+ //file_entry most be .webp
286
+ let webp_base64
287
+ try {
288
+ await executeAsModal(async () => {
289
+ const arrayBuffer = await file_entry.read({
290
+ format: formats.binary,
291
+ })
292
+ console.log('webp arrayBuffer:', arrayBuffer)
293
+
294
+ const base64_image = _arrayBufferToBase64(arrayBuffer) //convert the buffer to base64
295
+ console.log('base64_image:', base64_image)
296
+ webp_base64 = base64_image
297
+ })
298
+ return [webp_base64, webp_arrayBuffer]
299
+ } catch (e) {
300
+ console.warn(e)
301
+ }
302
+ }
303
+
304
+ static async base64ToLayer(
305
+ base64_png,
306
+ image_name = 'base64_to_layer.png',
307
+ to_x = 0,
308
+ to_y = 0,
309
+ width = 512,
310
+ height = 512,
311
+ format = 'png'
312
+ ) {
313
+ let layer
314
+ if (format === 'png') {
315
+ layer = await IOBase64ToLayer.base64PngToLayer(
316
+ base64_png,
317
+ image_name
318
+ )
319
+
320
+ psapi.setVisibleExe(layer, true)
321
+ await layer_util.Layer.scaleTo(layer, width, height) //
322
+ await layer_util.Layer.moveTo(layer, to_x, to_y) //move to the top left corner
323
+ psapi.setVisibleExe(layer, true)
324
+ }
325
+ return layer
326
+ }
327
+
328
+ static async getSelectionFromCanvasAsBase64Silent(
329
+ selectionInfo,
330
+ b_resize = false,
331
+ resize_width = 0,
332
+ resize_height = 0
333
+ ) {
334
+ //it will save the document then crop it so that only the selection area are left.
335
+ //return arrayBuffer or base64Png?
336
+ try {
337
+ let file
338
+ const folder = await fs.getTemporaryFolder()
339
+ await executeAsModal(
340
+ async () => {
341
+ const canvas_image_name = 'canvas_image.png'
342
+ file = await folder.createFile(canvas_image_name, {
343
+ overwrite: true,
344
+ })
345
+
346
+ const currentDocument = app.activeDocument
347
+ await currentDocument.saveAs.png(file, null, true)
348
+ //save file end
349
+
350
+ //read the saved image.png
351
+ },
352
+
353
+ { commandName: 'readPng' }
354
+ )
355
+
356
+ const arrayBuffer = await file.read({
357
+ format: formats.binary,
358
+ })
359
+
360
+ // const selectionInfo = g_generation_session.selectionInfo
361
+ // const selectionInfo = await psapi.getSelectionInfoExe()
362
+ const cropped_base64_url = await IOHelper.cropPng(
363
+ arrayBuffer,
364
+ selectionInfo,
365
+ true,
366
+ resize_width,
367
+ resize_height
368
+ )
369
+ const cropped_base64 = general.base64UrlToBase64(cropped_base64_url)
370
+
371
+ // html_manip.setInitImageSrc(cropped_base64_url)
372
+ return cropped_base64
373
+ } catch (e) {
374
+ console.warn(e)
375
+ }
376
+ }
377
+ static async getSelectionFromCanvasAsBase64NonSilent(
378
+ layer,
379
+ image_name,
380
+ width,
381
+ height
382
+ ) {
383
+ try {
384
+ const image_buffer = await psapi.newExportPng(
385
+ layer,
386
+ image_name,
387
+ width,
388
+ height
389
+ )
390
+
391
+ const base64_image = _arrayBufferToBase64(image_buffer) //convert the buffer to base64
392
+ //send the base64 to the server to save the file in the desired directory
393
+ // await sdapi.requestSavePng(base64_image, image_name)
394
+ // await saveFileInSubFolder(base64_image, document_name, image_name)
395
+ // debugger
396
+ const { requestSavePng } = require('../sdapi_py_re')
397
+ await requestSavePng(base64_image, image_name)
398
+ return base64_image
399
+ } catch (e) {
400
+ console.warn(e)
401
+ }
402
+ }
403
+ static async getSelectionFromCanvasAsBase64Interface(
404
+ width,
405
+ height,
406
+ layer,
407
+ selectionInfo,
408
+ resize = true,
409
+ use_silent_mode = true,
410
+ image_name = 'temp.png'
411
+ ) {
412
+ let base64_image
413
+ if (use_silent_mode) {
414
+ base64_image = await this.getSelectionFromCanvasAsBase64Silent(
415
+ selectionInfo,
416
+ resize,
417
+ width,
418
+ height
419
+ )
420
+ } else {
421
+ base64_image = await this.getSelectionFromCanvasAsBase64NonSilent(
422
+ layer,
423
+ image_name,
424
+ width,
425
+ height
426
+ )
427
+ }
428
+ return base64_image
429
+ }
430
+ static async getSelectionFromCanvasAsBase64Interface_New(
431
+ width,
432
+ height,
433
+ selectionInfo,
434
+ resize = true,
435
+ image_name = 'temp.png'
436
+ ) {
437
+ //use this version, it has less parameters
438
+ const use_silent_mode = html_manip.getUseSilentMode()
439
+ let layer = null
440
+ if (!use_silent_mode) {
441
+ await psapi.snapshot_layerExe()
442
+ const snapshotLayer = await app.activeDocument.activeLayers[0]
443
+ layer = snapshotLayer
444
+ }
445
+ let base64_image
446
+ if (use_silent_mode) {
447
+ base64_image = await this.getSelectionFromCanvasAsBase64Silent(
448
+ selectionInfo,
449
+ resize,
450
+ width,
451
+ height
452
+ )
453
+ } else {
454
+ base64_image = await this.getSelectionFromCanvasAsBase64NonSilent(
455
+ layer,
456
+ image_name,
457
+ width,
458
+ height
459
+ )
460
+ }
461
+ await layer_util.deleteLayers([layer]) //delete the snapshot layer if it exists
462
+ return base64_image
463
+ }
464
+
465
+ static async urlToLayer(image_url, image_file_name = 'image_from_url.png') {
466
+ try {
467
+ await psapi.unselectActiveLayersExe()
468
+ const temp_entry = await fs.getTemporaryFolder()
469
+ await downloadItExe(image_url, temp_entry, image_file_name)
470
+ } catch (e) {
471
+ console.warn('urlToLayer()', image_url, image_file_name, e)
472
+ }
473
+ }
474
+ }
475
+
476
+ class IOHelper {
477
+ static async saveAsWebp(doc_entry) {
478
+ //doc_entry must be in dataFolder or tempFolder
479
+ //save document as webp
480
+ const document_id = app.activeDocument.id
481
+
482
+ // doc_entry = await getCurrentDocFolder()
483
+ const file_entry = await doc_entry.createFile('temp.webp', {
484
+ overwrite: true,
485
+ })
486
+
487
+ const token = await fs.createSessionToken(file_entry)
488
+ const result = await batchPlay(
489
+ [
490
+ {
491
+ _obj: 'save',
492
+ as: {
493
+ _obj: 'WebPFormat',
494
+ compression: {
495
+ _enum: 'WebPCompression',
496
+ _value: 'compressionLossless',
497
+ },
498
+ includeXMPData: false,
499
+ includeEXIFData: false,
500
+ includePsExtras: false,
501
+ },
502
+ in: {
503
+ _path: token,
504
+ _kind: 'local',
505
+ },
506
+ documentID: 59,
507
+ copy: true,
508
+ lowerCase: true,
509
+ saveStage: {
510
+ _enum: 'saveStageType',
511
+ _value: 'saveBegin',
512
+ },
513
+ _options: {
514
+ dialogOptions: 'dontDisplay',
515
+ },
516
+ },
517
+ ],
518
+ {
519
+ synchronousExecution: true,
520
+ modalBehavior: 'execute',
521
+ }
522
+ )
523
+
524
+ return [result, file_entry]
525
+ }
526
+
527
+ static async saveAsWebpExe(doc_entry) {
528
+ let result
529
+ let file_entry
530
+ await executeAsModal(async () => {
531
+ ;[result, file_entry] = await this.saveAsWebp(doc_entry)
532
+ })
533
+ return [result, file_entry]
534
+ }
535
+ static async createDocumentExe(width, height) {
536
+ let new_doc
537
+ try {
538
+ await executeAsModal(async () => {
539
+ new_doc = await app.documents.add({
540
+ width: width,
541
+ height: height,
542
+ resolution: await app.activeDocument.resolution,
543
+ mode: 'RGBColorMode',
544
+ fill: 'transparent',
545
+ })
546
+ })
547
+ } catch (e) {
548
+ console.warn(e)
549
+ }
550
+ return new_doc
551
+ }
552
+ static async cropPng(
553
+ arrayBuffer,
554
+ selectionInfo,
555
+ b_resize = false,
556
+ resize_width = 0,
557
+ resize_height = 0
558
+ ) {
559
+ //crop png from array buffer
560
+ //have the option to resize the after cropping
561
+
562
+ const crop_x = selectionInfo.left
563
+ const crop_y = selectionInfo.top
564
+ const crop_w = selectionInfo.width
565
+ const crop_h = selectionInfo.height
566
+ const base64_url_result = await Jimp.read(arrayBuffer)
567
+ .then(async (img) => {
568
+ let cropped_img = await img.crop(crop_x, crop_y, crop_w, crop_h)
569
+
570
+ let resized_img
571
+ if (b_resize) {
572
+ resized_img = await cropped_img.resize(
573
+ resize_width,
574
+ resize_height
575
+ )
576
+ } else {
577
+ resized_img = cropped_img
578
+ }
579
+
580
+ const base64_url = await resized_img.getBase64Async(
581
+ Jimp.MIME_PNG
582
+ )
583
+
584
+ // console.log('jimp: base64_url: ', base64_url)
585
+ // document.getElementById("image").setAttribute("src", data);
586
+
587
+ return base64_url
588
+ })
589
+ .catch((error) => {
590
+ console.error(error)
591
+ })
592
+ return base64_url_result
593
+ }
594
+ }
595
+
596
+ class IOBase64ToLayer {
597
+ static {}
598
+ static async base64PngToLayer(base64_png, image_name) {
599
+ //unselect all layers so that the imported layer get place at the top of the document
600
+ await psapi.unselectActiveLayersExe()
601
+
602
+ const imported_layer = await base64ToFile(base64_png, image_name) //silent import into the document
603
+
604
+ return imported_layer
605
+ }
606
+ }
607
+ class IOFolder {
608
+ static {}
609
+ static async createSettingsFolder() {
610
+ //create a folder named "Settings" in the DataFolder
611
+ let settings_entry
612
+ await executeAsModal(async () => {
613
+ settings_entry = await this.createFolderSafe('Settings')
614
+ })
615
+ return settings_entry
616
+ }
617
+ static async findOrCreateFolderExe(folder_name) {
618
+ //create a folder named "Settings" in the DataFolder
619
+ let folder_entry
620
+ await executeAsModal(async () => {
621
+ folder_entry = await this.createFolderSafe(folder_name)
622
+ })
623
+ return folder_entry
624
+ }
625
+
626
+ static async doesFolderExist(folder_name) {
627
+ //check if folder exist. return true if it does. false if it doesn't.
628
+ const data_folder = await fs.getDataFolder()
629
+ let b_exist = false
630
+ let folder
631
+ try {
632
+ folder = await data_folder.getEntry(folder_name)
633
+ b_exist = true
634
+ } catch (e) {
635
+ // console.warn(e)
636
+ b_exist = false
637
+ }
638
+ return b_exist
639
+ }
640
+
641
+ static async createFolderSafe(folder_name) {
642
+ //will always return a folder. it will create the folder if it doesn't exist.
643
+ try {
644
+ // const uuid = await getUniqueDocumentId()
645
+ const data_folder = await fs.getDataFolder()
646
+
647
+ let folder_entry
648
+ try {
649
+ folder_entry = await data_folder.getEntry(folder_name)
650
+ } catch (e) {
651
+ console.warn(e)
652
+ //create document folder
653
+ folder_entry = await data_folder.createFolder(folder_name)
654
+ }
655
+
656
+ return folder_entry
657
+ } catch (e) {
658
+ console.warn(e)
659
+ }
660
+ }
661
+
662
+ static async getDocumentFolderNativePath() {
663
+ try {
664
+ const uuid = await getUniqueDocumentId()
665
+
666
+ let doc_folder = await this.getDocFolder(uuid)
667
+ const path = general.fixNativePath(doc_folder.nativePath)
668
+ return path
669
+ } catch (e) {
670
+ console.warn(e)
671
+ }
672
+ return ''
673
+ }
674
+
675
+ static async getDocFolder(doc_uuid) {
676
+ //will create folder if does not exist. always return a folder entry
677
+ const doc_entry = await getDocFolder(doc_uuid)
678
+ return doc_entry
679
+ }
680
+ static async getSettingsFolder() {
681
+ //will create folder if does not exist. always return a folder entry
682
+ const settings_entry = await this.createSettingsFolder()
683
+ return settings_entry
684
+ }
685
+ static async getPresetFolder() {
686
+ //will create folder if does not exist. always return a folder entry
687
+ const preset_entry = await this.findOrCreateFolderExe('Preset')
688
+ return preset_entry
689
+ }
690
+ static async getCustomPresetFolder(
691
+ custom_preset_folder_name = 'custom_preset'
692
+ ) {
693
+ //will create folder if does not exist. always return a folder entry
694
+ const preset_entry = await this.findOrCreateFolderExe(
695
+ custom_preset_folder_name
696
+ )
697
+ return preset_entry
698
+ }
699
+ static async createFolderIfDoesNotExist(folder_name) {
700
+ try {
701
+ await executeAsModal(async () => {
702
+ try {
703
+ const folder = await fs.getDataFolder()
704
+ const sub_folder = await folder.createFolder(folder_name)
705
+ } catch (e) {
706
+ console.warn(e)
707
+ }
708
+ })
709
+ } catch (e) {
710
+ console.warn(e)
711
+ }
712
+ }
713
+ }
714
+
715
+ class IOLog {
716
+ static {}
717
+ static async saveLogToFile(json, file_name) {
718
+ try {
719
+ const plugin_folder = await fs.getDataFolder()
720
+ const file = await plugin_folder.createFile(file_name, {
721
+ type: storage.types.file,
722
+ overwrite: true,
723
+ })
724
+
725
+ const JSONInPrettyFormat = JSON.stringify(json, undefined, 4)
726
+ await file.write(JSONInPrettyFormat, {
727
+ format: storage.formats.utf8,
728
+ append: true,
729
+ })
730
+ } catch (e) {
731
+ console.warn(e)
732
+ }
733
+ }
734
+ }
735
+ class IOJson {
736
+ static {}
737
+ static async saveJsonToFile(json, folder_entry, file_name) {
738
+ try {
739
+ const file = await folder_entry.createFile(file_name, {
740
+ type: storage.types.file,
741
+ overwrite: true,
742
+ })
743
+
744
+ const JSONInPrettyFormat = JSON.stringify(json, undefined, 4)
745
+ await file.write(JSONInPrettyFormat, {
746
+ format: storage.formats.utf8,
747
+ append: false,
748
+ })
749
+ } catch (e) {
750
+ console.warn(e)
751
+ }
752
+ }
753
+
754
+ static async saveJsonToFileExe(json, folder_entry, file_name) {
755
+ await executeAsModal(async () => {
756
+ await this.saveJsonToFile(json, folder_entry, file_name)
757
+ })
758
+ }
759
+ static async loadJsonFromFile(folder_entry, file_name) {
760
+ const json_file_name = file_name
761
+
762
+ try {
763
+ const json_entry = await folder_entry.getEntry(json_file_name)
764
+ if (json_entry) {
765
+ const json = JSON.parse(
766
+ await json_entry.read({
767
+ format: storage.formats.utf8,
768
+ })
769
+ )
770
+ return json
771
+ }
772
+ } catch (e) {
773
+ console.warn(e)
774
+ }
775
+ }
776
+
777
+ static async saveSettingsToFile(settings_json, settings_file_name) {
778
+ await executeAsModal(async () => {
779
+ // debugger
780
+ const folder_entry = await IOFolder.getSettingsFolder('Settings')
781
+ await this.saveJsonToFile(
782
+ settings_json,
783
+ folder_entry,
784
+ settings_file_name
785
+ )
786
+ })
787
+ }
788
+ static async loadSettingsFromFile(settings_file_name) {
789
+ const folder_entry = await IOFolder.getSettingsFolder('Settings')
790
+ const settings_json = await this.loadJsonFromFile(
791
+ folder_entry,
792
+ settings_file_name
793
+ )
794
+ return settings_json
795
+ }
796
+ static async saveHordeSettingsToFile(settings_json) {
797
+ const settings_file_name = 'horde_settings.json'
798
+ await this.saveSettingsToFile(settings_json, settings_file_name)
799
+ }
800
+ static async loadHordeSettingsFromFile() {
801
+ const settings_file_name = 'horde_settings.json'
802
+ const settings_json = await this.loadSettingsFromFile(
803
+ settings_file_name
804
+ )
805
+ return settings_json
806
+ }
807
+
808
+ static async getJsonEntries(doc_entry) {
809
+ let entries = await doc_entry.getEntries()
810
+ const json_entries = entries.filter(
811
+ (e) => e.isFile && e.name.toLowerCase().includes('.json') // must be a file and has the of the type .json
812
+ )
813
+ console.log('json_entries: ', json_entries)
814
+ // .forEach((e) => console.log(e.name))
815
+ return json_entries
816
+ }
817
+ static async deleteFile(doc_entry, file_name) {
818
+ try {
819
+ const file_entry = await doc_entry.getEntry(file_name)
820
+ file_entry.delete()
821
+ } catch (e) {}
822
+ }
823
+ }
824
+
825
+ module.exports = {
826
+ IO,
827
+ snapShotLayerExe,
828
+ IOHelper,
829
+ IOJson,
830
+ IOFolder,
831
+ IOLog,
832
+ convertBlackAndWhiteImageToRGBChannels,
833
+ convertBlackAndWhiteImageToRGBChannels2,
834
+ convertBlackAndWhiteImageToRGBChannels3,
835
+ isBlackAndWhiteImage,
836
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/layer.js ADDED
@@ -0,0 +1,357 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { batchPlay } = require('photoshop').action
2
+ const { executeAsModal } = require('photoshop').core
3
+ const {
4
+ cleanLayers,
5
+ getLayerIndex,
6
+ selectLayers,
7
+ unSelectMarqueeCommand,
8
+ unSelectMarqueeExe,
9
+ getSelectionInfoExe,
10
+ reSelectMarqueeExe,
11
+ } = require('../psapi')
12
+
13
+ const psapi = require('../psapi')
14
+
15
+ async function createNewLayerExe(layerName, opacity = 100) {
16
+ await executeAsModal(async () => {
17
+ await createNewLayerCommand(layerName, opacity)
18
+ })
19
+ const new_layer = await app.activeDocument.activeLayers[0]
20
+ return new_layer
21
+ }
22
+
23
+ async function createNewLayerCommand(layerName, opacity = 100) {
24
+ return await app.activeDocument.createLayer({
25
+ name: layerName,
26
+ opacity: opacity,
27
+ mode: 'normal',
28
+ })
29
+ }
30
+
31
+ async function deleteLayers(layers) {
32
+ try {
33
+ await cleanLayers(layers)
34
+ } catch (e) {
35
+ console.warn(e)
36
+ }
37
+ }
38
+
39
+ async function getIndexCommand() {
40
+ const command = {
41
+ _obj: 'get',
42
+ _target: [
43
+ {
44
+ _property: 'itemIndex',
45
+ },
46
+ {
47
+ _ref: 'layer',
48
+ _enum: 'ordinal',
49
+ _value: 'targetEnum',
50
+ },
51
+ ],
52
+ }
53
+ const result = await batchPlay([command], {
54
+ synchronousExecution: true,
55
+ modalBehavior: 'execute',
56
+ })
57
+
58
+ return result
59
+ }
60
+
61
+ async function getIndexExe() {
62
+ let index
63
+ await executeAsModal(async () => {
64
+ index = await getIndexCommand()
65
+ })
66
+
67
+ return index
68
+ }
69
+ const photoshop = require('photoshop')
70
+
71
+ const collapseFolderCommand = async (expand = false, recursive = false) => {
72
+ let result
73
+ try {
74
+ result = await batchPlay(
75
+ [
76
+ {
77
+ _obj: 'set',
78
+ _target: {
79
+ _ref: [
80
+ { _property: 'layerSectionExpanded' },
81
+ {
82
+ _ref: 'layer',
83
+ _enum: 'ordinal',
84
+ _value: 'targetEnum',
85
+ },
86
+ ],
87
+ },
88
+ to: expand,
89
+ recursive,
90
+ _options: { dialogOptions: 'dontDisplay' },
91
+ },
92
+ ],
93
+ { synchronousExecution: true }
94
+ )
95
+ } catch (e) {
96
+ console.error(e.message)
97
+ }
98
+ return result
99
+ }
100
+ async function collapseFolderExe(layers, expand = false, recursive = false) {
101
+ for (let layer of layers) {
102
+ try {
103
+ await executeAsModal(async () => {
104
+ const is_visible = await layer.visible // don't change the visiblity of the layer when collapsing
105
+ await selectLayers([layer])
106
+ await collapseFolderCommand(expand, recursive)
107
+ layer.visible = is_visible
108
+ })
109
+ } catch (e) {
110
+ console.warn(e)
111
+ }
112
+ }
113
+ }
114
+
115
+ class Layer {
116
+ static doesLayerExist(layer) {
117
+ let b_exist = false
118
+ try {
119
+ if (typeof layer !== 'undefined' && layer && layer.name) {
120
+ //it will throw an error if the layer has been deleted
121
+ b_exist = true
122
+ // return true
123
+ }
124
+ // b_exist = true
125
+ } catch (e) {
126
+ b_exist = false
127
+ // console.warn(e)
128
+ }
129
+ return b_exist
130
+ }
131
+ static async getLayerInfo(layer) {
132
+ const bounds = layer.bounds
133
+ const height = bounds.bottom - bounds.top
134
+ const width = bounds.right - bounds.left
135
+ const layer_info = {
136
+ height: height,
137
+ width: width,
138
+ left: bounds.left,
139
+ right: bounds.right,
140
+ top: bounds.top,
141
+ bottom: bounds.bottom,
142
+ }
143
+ console.log('layer_info:', layer_info)
144
+ return layer_info
145
+ }
146
+ static async scaleTo(layer, new_width, new_height) {
147
+ await executeAsModal(async () => {
148
+ try {
149
+ const selection_info = await psapi.getSelectionInfoExe()
150
+ await psapi.unSelectMarqueeExe()
151
+
152
+ console.log('scaleLayer got called')
153
+ // const activeLayer = getActiveLayer()
154
+ // const activeLayer = await app.activeDocument.activeLayers[0]
155
+
156
+ const layer_info = await this.getLayerInfo(layer)
157
+ const scale_x_ratio = (new_width / layer_info.width) * 100
158
+ const scale_y_ratio = (new_height / layer_info.height) * 100
159
+ console.log('scale_x_y_ratio:', scale_x_ratio, scale_y_ratio)
160
+ await layer.scale(scale_x_ratio, scale_y_ratio)
161
+ await psapi.reSelectMarqueeExe(selection_info)
162
+ } catch (e) {
163
+ console.warn(e)
164
+ }
165
+ })
166
+ }
167
+
168
+ static async moveTo(layer, to_x, to_y) {
169
+ try {
170
+ await executeAsModal(async () => {
171
+ try {
172
+ //translate doesn't work with selection active. so store the selection and then unselect. move the layer, then reselect the selection info
173
+ const selection_info = await psapi.getSelectionInfoExe()
174
+ await psapi.unSelectMarqueeExe()
175
+
176
+ const layer_info = await this.getLayerInfo(layer)
177
+ const top_dist = layer_info.top - to_y
178
+ const left_dist = layer_info.left - to_x
179
+ console.log('-left_dist, -top_dist', -left_dist, -top_dist)
180
+ await layer.translate(-left_dist, -top_dist)
181
+
182
+ await psapi.reSelectMarqueeExe(selection_info)
183
+ } catch (e) {
184
+ console.warn(e)
185
+ }
186
+ })
187
+ } catch (e) {
188
+ console.warn(e)
189
+ }
190
+ }
191
+ static resizeTo() {}
192
+ static fitSelection() {}
193
+ static async duplicateToDoc(layer, to_doc) {
194
+ const dupLayer = await layer.duplicate(to_doc)
195
+ // await selectLayers([dupLayer])
196
+ return dupLayer
197
+ }
198
+
199
+ static async duplicateLayerExe(layer) {
200
+ let layer_copy
201
+ try {
202
+ await executeAsModal(async () => {
203
+ layer_copy = await layer.duplicate()
204
+ })
205
+ } catch (e) {
206
+ console.warn('duplication error:', e)
207
+ }
208
+ return layer_copy
209
+ }
210
+
211
+ static {}
212
+ }
213
+
214
+ const hasBackgroundLayerDesc = () => ({
215
+ _obj: 'get',
216
+ _target: [
217
+ { _property: 'hasBackgroundLayer' },
218
+ {
219
+ _ref: 'document',
220
+ _enum: 'ordinal',
221
+ _value: 'targetEnum',
222
+ },
223
+ ],
224
+ })
225
+ async function hasBackgroundLayer() {
226
+ // check if a document has a background layer
227
+ try {
228
+ const result = await batchPlay([hasBackgroundLayerDesc()], {
229
+ synchronousExecution: true,
230
+ modalBehavior: 'execute',
231
+ })
232
+ const b_hasBackgroundLayer = result[0]?.hasBackgroundLayer
233
+ return b_hasBackgroundLayer
234
+ } catch (e) {
235
+ console.warn(e)
236
+ }
237
+ }
238
+ const makeBackgroundLayerDesc = () => ({
239
+ _obj: 'make',
240
+ _target: [
241
+ {
242
+ _ref: 'backgroundLayer',
243
+ },
244
+ ],
245
+ using: {
246
+ _ref: 'layer',
247
+ _enum: 'ordinal',
248
+ _value: 'targetEnum',
249
+ },
250
+ _options: { failOnMissingProperty: false, failOnMissingElement: false },
251
+ // _options: {
252
+ // dialogOptions: 'dontDisplay',
253
+ // },
254
+ })
255
+
256
+ const createSolidLayerDesc = (r, g, b) => ({
257
+ _obj: 'make',
258
+ _target: [
259
+ {
260
+ _ref: 'contentLayer',
261
+ },
262
+ ],
263
+ using: {
264
+ _obj: 'contentLayer',
265
+ type: {
266
+ _obj: 'solidColorLayer',
267
+ color: {
268
+ _obj: 'RGBColor',
269
+ red: r,
270
+ grain: g,
271
+ blue: b,
272
+ },
273
+ },
274
+ },
275
+ _options: {
276
+ dialogOptions: 'dontDisplay',
277
+ },
278
+ })
279
+
280
+ const toggleBackgroundLayerDesc = () => ({
281
+ _obj: 'show',
282
+ null: [
283
+ {
284
+ _ref: 'layer',
285
+ _property: 'background',
286
+ },
287
+ ],
288
+ toggleOptionsPalette: true,
289
+ _options: {
290
+ dialogOptions: 'dontDisplay',
291
+ },
292
+ })
293
+
294
+ async function toggleBackgroundLayerExe() {
295
+ try {
296
+ await executeAsModal(async () => {
297
+ const result = await batchPlay([toggleBackgroundLayerDesc()], {
298
+ synchronousExecution: true,
299
+ modalBehavior: 'execute',
300
+ })
301
+ console.log('toggleBackgroundLayerExe result: ', result)
302
+ })
303
+ } catch (e) {
304
+ console.warn(e)
305
+ }
306
+ }
307
+
308
+ async function createBackgroundLayer(r = 255, g = 255, b = 255) {
309
+ try {
310
+ const has_background = await hasBackgroundLayer()
311
+ if (has_background) {
312
+ //no need to create a background layer
313
+ return null
314
+ }
315
+
316
+ //reselect the selection area if it exist
317
+
318
+ await executeAsModal(async () => {
319
+ //store the selection area and then unselected
320
+ const selectionInfo = await psapi.getSelectionInfoExe()
321
+ await psapi.unSelectMarqueeExe()
322
+ const active_layers = app.activeDocument.activeLayers
323
+
324
+ // await createNewLayerCommand('background') //create layer
325
+ //make the layer into background
326
+ const result = await batchPlay(
327
+ [createSolidLayerDesc(r, g, b), makeBackgroundLayerDesc()],
328
+ {
329
+ synchronousExecution: true,
330
+ modalBehavior: 'execute',
331
+ }
332
+ )
333
+
334
+ await psapi.reSelectMarqueeExe(selectionInfo)
335
+ await psapi.selectLayersExe(active_layers)
336
+ })
337
+ } catch (e) {
338
+ console.warn(e)
339
+ }
340
+ }
341
+ async function fixImageBackgroundLayer() {
342
+ //convert the background layer to a normal layer
343
+ //create a new layer
344
+ //convert the new layer to background
345
+ }
346
+ module.exports = {
347
+ createNewLayerExe,
348
+ deleteLayers,
349
+ getIndexExe,
350
+ collapseFolderExe,
351
+ Layer,
352
+ hasBackgroundLayer,
353
+ createBackgroundLayer,
354
+ createSolidLayerDesc,
355
+ makeBackgroundLayerDesc,
356
+ toggleBackgroundLayerExe,
357
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/notification.js ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const dialog_box = require('../dialog_box')
2
+ const psapi = require('../psapi')
3
+ const { createBackgroundLayer } = require('./layer')
4
+ class Notification {
5
+ static {}
6
+ static async webuiIsOffline() {
7
+ const r1 = await dialog_box.prompt(
8
+ 'Automatic1111 is Offline',
9
+ "make sure Automatic1111 is running in the background, or select the 'native horde' option from the horde tab",
10
+ ['Cancel', 'OK']
11
+ )
12
+
13
+ try {
14
+ if (r1 === 'Cancel') {
15
+ /* cancelled or No */
16
+ console.log('cancel')
17
+ } else if (r1 === 'OK') {
18
+ console.log('ok')
19
+ }
20
+ } catch (e) {
21
+ console.warn(e)
22
+ }
23
+ }
24
+ static async webuiAPIMissing() {
25
+ const r1 = await dialog_box.prompt(
26
+ "The Plugin can't communicate with Automatic1111",
27
+ 'Automatic1111 is running, but you forgot to add --api flag to the webui command flags',
28
+ ['Cancel', 'OK']
29
+ )
30
+
31
+ try {
32
+ if (r1 === 'Cancel') {
33
+ /* cancelled or No */
34
+ console.log('cancel')
35
+ } else if (r1 === 'OK') {
36
+ console.log('ok')
37
+ }
38
+ } catch (e) {
39
+ console.warn(e)
40
+ }
41
+ }
42
+ static async backgroundLayerIsMissing() {
43
+ const r1 = await dialog_box.prompt(
44
+ 'You need a white background layer present in your document',
45
+ '',
46
+ ['Cancel', 'Create']
47
+ )
48
+
49
+ try {
50
+ if (r1 === 'Cancel') {
51
+ /* cancelled or No */
52
+ console.log('cancel')
53
+ return false
54
+ } else if (r1 === 'Create') {
55
+ //store the selection area and then unselected
56
+ const selectionInfo = await psapi.getSelectionInfoExe()
57
+ await psapi.unSelectMarqueeExe()
58
+ const active_layers = app.activeDocument.activeLayers
59
+
60
+ //create a background layer with no selection active
61
+ await createBackgroundLayer()
62
+ console.log('create background layer')
63
+ //reselect the selection area if it exist
64
+
65
+ await psapi.reSelectMarqueeExe(selectionInfo)
66
+ await psapi.selectLayersExe(active_layers)
67
+ return true
68
+ }
69
+ } catch (e) {
70
+ console.warn(e)
71
+ }
72
+ return false
73
+ }
74
+ static async inactiveSelectionArea(is_active_session) {
75
+ let buttons = ['Cancel', 'Rectangular Marquee']
76
+ if (is_active_session) {
77
+ buttons.push('Continue Session')
78
+ }
79
+ const r1 = await dialog_box.prompt(
80
+ 'Please Select a Rectangular Area',
81
+ 'You Forgot to select a Rectangular Area',
82
+ buttons
83
+ )
84
+ if (r1 === 'Cancel') {
85
+ /* cancelled or No */
86
+ console.log('cancel')
87
+ return false
88
+ } else if (r1 === 'Rectangular Marquee') {
89
+ console.log('Rectangular Marquee')
90
+ psapi.selectMarqueeRectangularToolExe()
91
+ return false // should this be false?! what does true and false means in this context?! Yes: it should be false since boolean value represent wither we have an active selection area or not
92
+ } else if (r1 === 'Continue Session') {
93
+ await activateSessionSelectionArea()
94
+ return true
95
+ }
96
+ return false
97
+ }
98
+ }
99
+
100
+ module.exports = {
101
+ Notification,
102
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/online_data.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "new_version": "v1.2.4",
3
+ "update_message": "Your version is outdated.Please visit our Github page and download the one click installer / .ccx file.\nRun the .ccx file and it will automatically update the current version of your plug in. No data will be lost."
4
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/presets/controlnet_preset.js ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const MaintainPositionSettings = {
2
+ 0: {
3
+ module: 'openpose',
4
+ model: 'control_sd15_openpose [fef5e48e]',
5
+ weight: 1,
6
+ resize_mode: null,
7
+ lowvram: null,
8
+ processor_res: null,
9
+ threshold_a: null,
10
+ threshold_b: null,
11
+ guidance_start: 0,
12
+ guidance_end: 0.3,
13
+ guessmode: null,
14
+ },
15
+ 1: {
16
+ module: 'depth',
17
+ model: 'control_sd15_depth [fef5e48e]',
18
+ weight: 0.8,
19
+ resize_mode: null,
20
+ lowvram: null,
21
+ processor_res: null,
22
+ threshold_a: null,
23
+ threshold_b: null,
24
+ guidance_start: 0,
25
+ guidance_end: 0.6,
26
+ guessmode: null,
27
+ },
28
+ }
29
+
30
+ const Pose_Depth_Canny_HandFix = {
31
+ 0: {
32
+ module: 'openpose',
33
+ model: 'control_sd15_openpose [fef5e48e]',
34
+ weight: 1,
35
+ resize_mode: null,
36
+ lowvram: null,
37
+ processor_res: null,
38
+ threshold_a: null,
39
+ threshold_b: null,
40
+ guidance_start: 0,
41
+ guidance_end: 1,
42
+ guessmode: null,
43
+ },
44
+ 1: {
45
+ module: 'depth',
46
+ model: 'control_sd15_depth [fef5e48e]',
47
+ weight: 1.3,
48
+ resize_mode: null,
49
+ lowvram: null,
50
+ processor_res: null,
51
+ threshold_a: null,
52
+ threshold_b: null,
53
+ guidance_start: 0.3,
54
+ guidance_end: 1,
55
+ guessmode: null,
56
+ },
57
+ 2: {
58
+ module: 'canny',
59
+ model: 'control_sd15_canny [fef5e48e]',
60
+ weight: 1.3,
61
+ resize_mode: null,
62
+ lowvram: null,
63
+ processor_res: null,
64
+ threshold_a: null,
65
+ threshold_b: null,
66
+ guidance_start: 0.3,
67
+ guidance_end: 1,
68
+ guessmode: null,
69
+ },
70
+ }
71
+
72
+ MaintainComposition_Character = {
73
+ 0: {
74
+ module: 'openpose',
75
+ model: 'control_sd15_openpose [fef5e48e]',
76
+ weight: 1,
77
+ resize_mode: null,
78
+ lowvram: null,
79
+ processor_res: null,
80
+ threshold_a: null,
81
+ threshold_b: null,
82
+ guidance_start: 0,
83
+ guidance_end: 0.5,
84
+ guessmode: null,
85
+ },
86
+ 1: {
87
+ module: 'canny',
88
+ model: 'control_sd15_canny [fef5e48e]',
89
+ weight: 1,
90
+ resize_mode: null,
91
+ lowvram: null,
92
+ processor_res: null,
93
+ threshold_a: null,
94
+ threshold_b: null,
95
+ guidance_start: 0,
96
+ guidance_end: 0.8,
97
+ guessmode: null,
98
+ },
99
+ 2: {
100
+ weight: 1,
101
+ resize_mode: null,
102
+ lowvram: null,
103
+ processor_res: null,
104
+ threshold_a: null,
105
+ threshold_b: null,
106
+ guidance_start: 0,
107
+ guidance_end: 1,
108
+ guessmode: null,
109
+ },
110
+ }
111
+
112
+ const LogoCreation = {
113
+ 0: {
114
+ module: 'canny',
115
+ model: 'control_sd15_canny [fef5e48e]',
116
+ weight: 1.3,
117
+ resize_mode: null,
118
+ lowvram: null,
119
+ processor_res: null,
120
+ threshold_a: null,
121
+ threshold_b: null,
122
+ guidance_start: 0,
123
+ guidance_end: 0.9,
124
+ guessmode: null,
125
+ },
126
+ 1: {
127
+ module: 'scribble',
128
+ model: 'control_sd15_scribble [fef5e48e]',
129
+ weight: 1,
130
+ resize_mode: null,
131
+ lowvram: null,
132
+ processor_res: null,
133
+ threshold_a: null,
134
+ threshold_b: null,
135
+ guidance_start: 0,
136
+ guidance_end: 1,
137
+ guessmode: null,
138
+ },
139
+ 2: {
140
+ weight: 1,
141
+ resize_mode: null,
142
+ lowvram: null,
143
+ processor_res: null,
144
+ threshold_a: null,
145
+ threshold_b: null,
146
+ guidance_start: 0,
147
+ guidance_end: 1,
148
+ guessmode: null,
149
+ },
150
+ }
151
+
152
+ const Backgrounds = {
153
+ 0: {
154
+ module: 'mlsd',
155
+ model: 'control_sd15_mlsd [fef5e48e]',
156
+ weight: 1.3,
157
+ resize_mode: null,
158
+ lowvram: null,
159
+ processor_res: null,
160
+ threshold_a: null,
161
+ threshold_b: null,
162
+ guidance_start: 0,
163
+ guidance_end: 0.7,
164
+ guessmode: null,
165
+ },
166
+ 1: {
167
+ weight: 1,
168
+ resize_mode: null,
169
+ lowvram: null,
170
+ processor_res: null,
171
+ threshold_a: null,
172
+ threshold_b: null,
173
+ guidance_start: 0,
174
+ guidance_end: 1,
175
+ guessmode: null,
176
+ },
177
+ 2: {
178
+ weight: 1,
179
+ resize_mode: null,
180
+ lowvram: null,
181
+ processor_res: null,
182
+ threshold_a: null,
183
+ threshold_b: null,
184
+ guidance_start: 0,
185
+ guidance_end: 1,
186
+ guessmode: null,
187
+ },
188
+ }
189
+ const ControlNetNativePresets = {
190
+ 'Maintain Position': MaintainPositionSettings,
191
+ 'Hand Fix': Pose_Depth_Canny_HandFix,
192
+ 'Maintain Composition (Character)': MaintainComposition_Character,
193
+ 'Logo Creation': LogoCreation,
194
+ Backgrounds: Backgrounds,
195
+ }
196
+ module.exports = {
197
+ ControlNetNativePresets,
198
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/presets/preset.js ADDED
@@ -0,0 +1,408 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const io = require('../io')
2
+ const html_manip = require('../html_manip')
3
+ const Enum = require('../../enum')
4
+ const event = require('../event')
5
+
6
+ // const control_net = require('../../utility/tab/control_net')
7
+ let settings = {
8
+ model: null,
9
+ prompt_shortcut: null,
10
+ positive_prompt: null,
11
+ negative_prompt: null,
12
+ selection_mode: null,
13
+ batch_number: null,
14
+ steps: null,
15
+ width: null,
16
+ height: null,
17
+ firstphase_width: null,
18
+ firstphase_height: null,
19
+ cfg: null,
20
+ denoising_strength: null,
21
+ hi_res_denoising_strength: null,
22
+ mask_blur: null,
23
+ inpaint_at_full_res: null,
24
+ hi_res_fix: null,
25
+ inpaint_padding: null,
26
+ seed: null,
27
+ samplers: null,
28
+ mask_content: null,
29
+ }
30
+
31
+ let LatentNoiseSettings = {
32
+ model: null,
33
+ prompt_shortcut: null,
34
+ positive_prompt: null,
35
+ negative_prompt: null,
36
+ generation_mode: null,
37
+ batch_number: null,
38
+ steps: null,
39
+ width: null,
40
+ height: null,
41
+ firstphase_width: null,
42
+ firstphase_height: null,
43
+ cfg: null,
44
+ denoising_strength: 0.92,
45
+ hi_res_denoising_strength: null,
46
+ mask_blur: null,
47
+ inpaint_at_full_res: null,
48
+ hi_res_fix: null,
49
+ inpaint_padding: null,
50
+ seed: null,
51
+ samplers: null,
52
+ mask_content: '2',
53
+ }
54
+
55
+ let FillSettings = {
56
+ model: null,
57
+ prompt_shortcut: null,
58
+ positive_prompt: null,
59
+ negative_prompt: null,
60
+ generation_mode: null,
61
+ batch_number: null,
62
+ steps: null,
63
+ width: null,
64
+ height: null,
65
+ firstphase_width: null,
66
+ firstphase_height: null,
67
+ cfg: null,
68
+ denoising_strength: 0.7,
69
+ hi_res_denoising_strength: null,
70
+ mask_blur: null,
71
+ inpaint_at_full_res: null,
72
+ hi_res_fix: null,
73
+ inpaint_padding: null,
74
+ seed: null,
75
+ samplers: null,
76
+ mask_content: '0',
77
+ }
78
+ let OriginalSettings = {
79
+ model: null,
80
+ prompt_shortcut: null,
81
+ positive_prompt: null,
82
+ negative_prompt: null,
83
+ generation_mode: null,
84
+ batch_number: null,
85
+ steps: null,
86
+ width: null,
87
+ height: null,
88
+ firstphase_width: null,
89
+ firstphase_height: null,
90
+ cfg: null,
91
+ denoising_strength: 0.7,
92
+ hi_res_denoising_strength: null,
93
+ mask_blur: null,
94
+ inpaint_at_full_res: null,
95
+ hi_res_fix: null,
96
+ inpaint_padding: null,
97
+ seed: null,
98
+ samplers: null,
99
+ mask_content: '1',
100
+ }
101
+ let HealBrushSettings = {
102
+ model: null,
103
+ prompt_shortcut: null,
104
+ positive_prompt: null,
105
+ negative_prompt: null,
106
+ generation_mode: null,
107
+ batch_number: null,
108
+ steps: '25',
109
+ width: null,
110
+ height: null,
111
+ firstphase_width: null,
112
+ firstphase_height: null,
113
+ cfg: '9',
114
+ denoising_strength: 0.92,
115
+ hi_res_denoising_strength: null,
116
+ mask_blur: 1,
117
+ inpaint_at_full_res: null,
118
+ hi_res_fix: null,
119
+ inpaint_padding: null,
120
+ seed: null,
121
+ samplers: null,
122
+ mask_content: '2',
123
+ mask_expansion: 2,
124
+ }
125
+
126
+ function nullAllSettings() {}
127
+
128
+ class Preset {
129
+ constructor() {}
130
+
131
+ loadPresetFromJson(preset_path) {}
132
+ savePresetToJson(preset_path, settings) {}
133
+ }
134
+
135
+ function getPresetSettingsHtml() {
136
+ const value_str = document.getElementById('taPresetSettings').value
137
+ const value_json = JSON.parse(value_str)
138
+ return value_json
139
+ }
140
+ function setPresetSettingsHtml(preset_settings) {
141
+ const JSONInPrettyFormat = JSON.stringify(preset_settings, undefined, 7)
142
+ preset_settings_element = document.getElementById('taPresetSettings')
143
+ preset_settings_element.value = JSONInPrettyFormat
144
+
145
+ const new_lines_count = general.countNewLines(JSONInPrettyFormat)
146
+ new_lines_count
147
+ preset_settings_element.style.height =
148
+ Math.min(new_lines_count * 12 + 100, 800).toString() + 'px'
149
+ }
150
+
151
+ function getPresetName() {
152
+ const preset_name = document.getElementById('tiPresetName').value
153
+ return preset_name
154
+ }
155
+ function setPresetName(preset_name) {
156
+ document.getElementById('tiPresetName').value = preset_name
157
+ }
158
+
159
+ function getPresetSettings(preset_type) {
160
+ let preset_settings
161
+ if (preset_type === Enum.PresetTypeEnum['SDPreset']) {
162
+ preset_settings = g_ui_settings_object.getSettings()
163
+ } else if (preset_type === Enum.PresetTypeEnum['ControlNetPreset']) {
164
+ const { ControlNetUnit } = require('../../utility/tab/control_net') // only import ControlNetUnit to avoid circular dependency
165
+ // preset_settings = control_net.ControlNetUnit.getUnits()
166
+
167
+ preset_settings = ControlNetUnit.getUnits()
168
+ }
169
+ return preset_settings
170
+ }
171
+
172
+ function getPresetType() {
173
+ const presetType = document.getElementById('rgPresetType').selected
174
+
175
+ return presetType
176
+ }
177
+
178
+ document.getElementById('btnNewPreset').addEventListener('click', () => {
179
+ // const g_ui_settings_object = getUISettingsObject()
180
+ // debugger
181
+ const preset_type = getPresetType()
182
+ const preset_settings = getPresetSettings(preset_type)
183
+
184
+ // const settings = g_ui_settings_object.getSettings()
185
+
186
+ setPresetSettingsHtml(preset_settings)
187
+
188
+ const preset_name = getPresetName()
189
+ setPresetNameLabel(preset_name)
190
+ })
191
+
192
+ function getPresetNameLabel() {
193
+ //use presetNameLabel as the final name for a preset
194
+ const preset_name = document.getElementById('lPresetName').textContent
195
+ return preset_name
196
+ }
197
+ function setPresetNameLabel(preset_name) {
198
+ document.getElementById('lPresetName').textContent = preset_name.trim()
199
+ }
200
+
201
+ async function populatePresetMenu() {
202
+ // presets = ['preset_1', 'preset_2', 'preset_3']
203
+ const preset_type = getPresetType()
204
+ const presets = await getAllCustomPresetsSettings(preset_type)
205
+ const presets_names = Object.keys(presets)
206
+ html_manip.populateMenu(
207
+ 'mSettingTabPresetMenu',
208
+ 'mPresetMenuItemClass',
209
+ presets_names,
210
+ (item, item_html_element) => {
211
+ item_html_element.innerHTML = item
212
+ }
213
+ )
214
+ }
215
+ async function deletePreset() {
216
+ try {
217
+ const preset_name = html_manip.getSelectedMenuItemTextContent(
218
+ 'mSettingTabPresetMenu'
219
+ )
220
+ const preset_file_name = preset_name + '.json'
221
+
222
+ const preset_type = getPresetType()
223
+ const preset_folder_name = mapPresetTypeToPresetFolder(preset_type)
224
+ const custom_preset_entry = await io.IOFolder.getCustomPresetFolder(
225
+ preset_folder_name
226
+ )
227
+
228
+ await io.IOJson.deleteFile(custom_preset_entry, preset_file_name)
229
+ html_manip.unselectMenuItem('mSettingTabPresetMenu') // unselect the custom preset menu
230
+ setPresetSettingsHtml({}) //reset preset settings text area
231
+ setPresetName('')
232
+ setPresetNameLabel('')
233
+ await populatePresetMenu() // update the custom preset Menu
234
+ triggerUpdatePresetMenu(preset_type)
235
+ } catch (e) {
236
+ console.warn(e)
237
+ }
238
+ }
239
+
240
+ async function getCustomPresetEntries(preset_folder_name) {
241
+ const custom_preset_entry = await io.IOFolder.getCustomPresetFolder(
242
+ preset_folder_name
243
+ )
244
+
245
+ const custom_preset_entries = await io.IOJson.getJsonEntries(
246
+ custom_preset_entry
247
+ )
248
+
249
+ return custom_preset_entries
250
+ }
251
+
252
+ async function loadPresetSettingsFromFile(preset_file_name, preset_type) {
253
+ // const preset_type = getPresetType()
254
+
255
+ const preset_folder_name = mapPresetTypeToPresetFolder(preset_type)
256
+ const custom_preset_entry = await io.IOFolder.getCustomPresetFolder(
257
+ preset_folder_name
258
+ )
259
+ let preset_settings = {}
260
+ try {
261
+ preset_settings = await io.IOJson.loadJsonFromFile(
262
+ custom_preset_entry,
263
+ preset_file_name
264
+ )
265
+ } catch (e) {
266
+ console.warn(e)
267
+ }
268
+ return preset_settings
269
+ }
270
+ async function getAllCustomPresetsSettings(preset_type) {
271
+ const preset_folder_name = mapPresetTypeToPresetFolder(preset_type)
272
+ const custom_preset_entries = await getCustomPresetEntries(
273
+ preset_folder_name
274
+ )
275
+ let custom_presets = {}
276
+ for (const entry of custom_preset_entries) {
277
+ const preset_name = entry.name.split('.json')[0]
278
+ let preset_settings = await loadPresetSettingsFromFile(
279
+ entry.name,
280
+ preset_type
281
+ )
282
+
283
+ custom_presets[preset_name] = preset_settings
284
+ }
285
+ return custom_presets
286
+ }
287
+
288
+ function mapPresetTypeToPresetFolder(preset_type) {
289
+ let preset_folder
290
+
291
+ if (preset_type === Enum.PresetTypeEnum['SDPreset']) {
292
+ preset_folder = 'custom_preset'
293
+ } else if (preset_type === Enum.PresetTypeEnum['ControlNetPreset']) {
294
+ preset_folder = 'controlnet_preset'
295
+ }
296
+ return preset_folder
297
+ }
298
+
299
+ function triggerUpdatePresetMenu(preset_type) {
300
+ let menu_id
301
+ if (preset_type === Enum.PresetTypeEnum['SDPreset']) {
302
+ menu_id = '#mPresetMenu'
303
+ } else if (preset_type === Enum.PresetTypeEnum['ControlNetPreset']) {
304
+ menu_id = '#mControlNetPresetMenu'
305
+ }
306
+ event.triggerEvent(menu_id, event.updatePresetMenuEvent)
307
+ }
308
+ Array.from(document.getElementsByClassName('rbPresetType')).forEach((rb) => {
309
+ rb.addEventListener('click', async () => {
310
+ const preset_type = rb.value
311
+ await populatePresetMenu()
312
+ })
313
+ })
314
+
315
+ document.getElementById('btnSavePreset').addEventListener('click', async () => {
316
+ //save preset settings from textarea to json file
317
+ //reload the preset menu
318
+ const preset_type = getPresetType()
319
+ const custom_preset_folder_name = mapPresetTypeToPresetFolder(preset_type)
320
+ const custom_preset_entry = await io.IOFolder.getCustomPresetFolder(
321
+ custom_preset_folder_name
322
+ )
323
+ const preset_settings = getPresetSettingsHtml()
324
+ const preset_name = getPresetNameLabel()
325
+
326
+ //check if the file exist and prompt the user to override it or cancel
327
+ await io.IOJson.saveJsonToFileExe(
328
+ preset_settings,
329
+ custom_preset_entry,
330
+ preset_name + '.json'
331
+ )
332
+ await populatePresetMenu()
333
+ triggerUpdatePresetMenu(preset_type)
334
+ html_manip.selectMenuItem('mSettingTabPresetMenu', preset_name)
335
+ })
336
+ document
337
+ .getElementById('btnDeletePreset')
338
+ .addEventListener('click', async () => {
339
+ await deletePreset()
340
+ })
341
+
342
+ document.getElementById('tiPresetName').addEventListener('input', () => {
343
+ //save preset settings from textarea to json file
344
+ //reload the preset menu
345
+ const preset_name = getPresetName()
346
+ setPresetNameLabel(preset_name)
347
+ //check if the file exist and prompt the user to override it or cancel
348
+ })
349
+ document
350
+ .getElementById('mSettingTabPresetMenu')
351
+ .addEventListener('input', () => {
352
+ //Note: is this correct?! why use Input and change events together
353
+ //save preset settings from textarea to json file
354
+ //reload the preset menu
355
+ const preset_name = getPresetName()
356
+ setPresetNameLabel(preset_name)
357
+ //check if the file exist and prompt the user to override it or cancel
358
+ })
359
+
360
+ document
361
+ .getElementById('mSettingTabPresetMenu')
362
+ .addEventListener('change', async (evt) => {
363
+ try {
364
+ const preset_index = evt.target.selectedIndex
365
+ const preset_name = evt.target.options[preset_index].textContent
366
+ const preset_type = getPresetType()
367
+ setPresetName(preset_name)
368
+ setPresetNameLabel(preset_name)
369
+
370
+ const preset_settings = await loadPresetSettingsFromFile(
371
+ preset_name + '.json',
372
+ preset_type
373
+ )
374
+ setPresetSettingsHtml(preset_settings)
375
+ } catch (e) {}
376
+ })
377
+
378
+ document
379
+ .getElementById('mPresetMenu')
380
+ .addEventListener('updatePresetMenuEvent', async (event) => {
381
+ // console.log("I'm listening on a custom event")
382
+ const { populatePresetMenu } = require('../ui')
383
+ await populatePresetMenu()
384
+ })
385
+
386
+ async function initializePresetTab() {
387
+ try {
388
+ await populatePresetMenu()
389
+
390
+ const selected_rb =
391
+ html_manip.getSelectedRadioButtonElement('rbPresetType')
392
+ selected_rb.click() // to trigger the click event which will update the setting preset menu according to the preset type
393
+ } catch (e) {
394
+ console.error(e)
395
+ }
396
+ }
397
+ initializePresetTab()
398
+ module.exports = {
399
+ LatentNoiseSettings,
400
+ FillSettings,
401
+ OriginalSettings,
402
+ HealBrushSettings,
403
+ populatePresetMenu,
404
+ getCustomPresetEntries,
405
+ loadPresetSettingsFromFile,
406
+ getAllCustomPresetsSettings,
407
+ initializePresetTab,
408
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/sampler.js ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ samplers = [
2
+ {
3
+ name: 'Euler a',
4
+ aliases: ['k_euler_a', 'k_euler_ancestral'],
5
+ options: {},
6
+ },
7
+ {
8
+ name: 'Euler',
9
+ aliases: ['k_euler'],
10
+ options: {},
11
+ },
12
+ {
13
+ name: 'LMS',
14
+ aliases: ['k_lms'],
15
+ options: {},
16
+ },
17
+ {
18
+ name: 'Heun',
19
+ aliases: ['k_heun'],
20
+ options: {},
21
+ },
22
+ {
23
+ name: 'DPM2',
24
+ aliases: ['k_dpm_2'],
25
+ options: {
26
+ discard_next_to_last_sigma: 'True',
27
+ },
28
+ },
29
+ {
30
+ name: 'DPM2 a',
31
+ aliases: ['k_dpm_2_a'],
32
+ options: {
33
+ discard_next_to_last_sigma: 'True',
34
+ },
35
+ },
36
+ {
37
+ name: 'DPM++ 2S a',
38
+ aliases: ['k_dpmpp_2s_a'],
39
+ options: {},
40
+ },
41
+ {
42
+ name: 'DPM++ 2M',
43
+ aliases: ['k_dpmpp_2m'],
44
+ options: {},
45
+ },
46
+ {
47
+ name: 'DPM++ SDE',
48
+ aliases: ['k_dpmpp_sde'],
49
+ options: {},
50
+ },
51
+ {
52
+ name: 'DPM fast',
53
+ aliases: ['k_dpm_fast'],
54
+ options: {},
55
+ },
56
+ {
57
+ name: 'DPM adaptive',
58
+ aliases: ['k_dpm_ad'],
59
+ options: {},
60
+ },
61
+ {
62
+ name: 'LMS Karras',
63
+ aliases: ['k_lms_ka'],
64
+ options: {
65
+ scheduler: 'karras',
66
+ },
67
+ },
68
+ {
69
+ name: 'DPM2 Karras',
70
+ aliases: ['k_dpm_2_ka'],
71
+ options: {
72
+ scheduler: 'karras',
73
+ discard_next_to_last_sigma: 'True',
74
+ },
75
+ },
76
+ {
77
+ name: 'DPM2 a Karras',
78
+ aliases: ['k_dpm_2_a_ka'],
79
+ options: {
80
+ scheduler: 'karras',
81
+ discard_next_to_last_sigma: 'True',
82
+ },
83
+ },
84
+ {
85
+ name: 'DPM++ 2S a Karras',
86
+ aliases: ['k_dpmpp_2s_a_ka'],
87
+ options: {
88
+ scheduler: 'karras',
89
+ },
90
+ },
91
+ {
92
+ name: 'DPM++ 2M Karras',
93
+ aliases: ['k_dpmpp_2m_ka'],
94
+ options: {
95
+ scheduler: 'karras',
96
+ },
97
+ },
98
+ {
99
+ name: 'DPM++ SDE Karras',
100
+ aliases: ['k_dpmpp_sde_ka'],
101
+ options: {
102
+ scheduler: 'karras',
103
+ },
104
+ },
105
+ {
106
+ name: 'DDIM',
107
+ aliases: [],
108
+ options: {},
109
+ },
110
+ {
111
+ name: 'PLMS',
112
+ aliases: [],
113
+ options: {},
114
+ },
115
+ ]
116
+
117
+ module.exports = { samplers }
Auto-Photoshop-StableDiffusion-Plugin/utility/sd_scripts/horde.js ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ async function requestModelsHorde() {
2
+ //get the models list from url
3
+ // https://stablehorde.net/api/v2/status/models
4
+
5
+ console.log('requestModelsHorde: ')
6
+
7
+ const full_url = 'https://stablehorde.net/api/v2/status/models'
8
+ let request = await fetch(full_url)
9
+ let json = await request.json()
10
+ console.log('hordes models json:')
11
+ console.dir(json)
12
+
13
+ return json
14
+ }
15
+
16
+ function addHordeModelMenuItem(model_title, model_name) {
17
+ // console.log(model_title,model_name)
18
+ const menu_item_element = document.createElement('sp-menu-item')
19
+ menu_item_element.className = 'mModelMenuItemHorde'
20
+ menu_item_element.innerHTML = model_title
21
+
22
+ menu_item_element.dataset.name = model_name
23
+ return menu_item_element
24
+ }
25
+
26
+ async function refreshModelsHorde() {
27
+ try {
28
+ let g_models_horde = await requestModelsHorde()
29
+ // const models_menu_element = document.getElementById('mModelsMenu')
30
+ // models_menu_element.value = ""
31
+ //(optional): sort the models
32
+
33
+ g_models_horde.sort(function (a, b) {
34
+ return b.count - a.count
35
+ })
36
+ // g_models_horde = g_models_horde.sort( compareModelCounts );
37
+ document.getElementById('mModelsMenuHorde').innerHTML = ''
38
+ let model_item_random = addHordeModelMenuItem('Random', 'Random')
39
+ // model_item_random.selected = true
40
+ document
41
+ .getElementById('mModelsMenuHorde')
42
+ .appendChild(model_item_random)
43
+ for (let model of g_models_horde) {
44
+ // console.log(model.name, model.count) //Log
45
+ const model_html_tile = `${model.name}: ${model.count}`
46
+ const model_item_element = addHordeModelMenuItem(
47
+ model_html_tile,
48
+ model.name
49
+ )
50
+ if (model.name === 'stable_diffusion') {
51
+ // TODO: refactor this code outside the for loop
52
+ // maybe call it in an init function
53
+ //selection the stable diffusion model by default
54
+ model_item_element.selected = true
55
+ }
56
+ document
57
+ .getElementById('mModelsMenuHorde')
58
+ .appendChild(model_item_element)
59
+ }
60
+ } catch (e) {
61
+ console.warn(e)
62
+ }
63
+ }
64
+ function getModelHorde() {
65
+ return [...document.getElementsByClassName('mModelMenuItemHorde')].filter(
66
+ (e) => e.selected == true
67
+ )[0].dataset.name
68
+ }
69
+
70
+ function getScriptArgs() {
71
+ const model = getModelHorde()
72
+ const b_nsfw = document.getElementById('chUseNSFW').checked
73
+ const b_shared_laion = document.getElementById('chUseSharedLaion').checked
74
+
75
+ let seed_variation = document.getElementById('slSeedVariation').value
76
+ seed_variation = parseInt(seed_variation)
77
+ const script_args_json = {
78
+ model: model,
79
+ nsfw: b_nsfw,
80
+ shared_laion: b_shared_laion,
81
+ seed_variation: seed_variation,
82
+ post_processing_1: 'None',
83
+ post_processing_2: 'None',
84
+ post_processing_3: 'None',
85
+ }
86
+ const script_args = Object.values(script_args_json)
87
+ return script_args
88
+ }
89
+
90
+ document
91
+ .getElementById('btnRefreshModelsHorde')
92
+ .addEventListener('click', async () => {
93
+ await refreshModelsHorde()
94
+ })
95
+
96
+ const script_name = 'Run on Stable Horde'
97
+
98
+ refreshModelsHorde() //refresh the model when importing the script
99
+
100
+ module.exports = {
101
+ requestModelsHorde,
102
+ refreshModelsHorde,
103
+ getModelHorde,
104
+
105
+ getScriptArgs,
106
+ script_name,
107
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/sdapi/config.js ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class SdConfig {
2
+ constructor() {
3
+ this.config //store sd options
4
+ }
5
+
6
+ async getConfig() {
7
+ try {
8
+ this.config = await sdapi.requestGetConfig()
9
+ return this.config
10
+ } catch (e) {
11
+ console.warn(e)
12
+ }
13
+ }
14
+ getUpscalerModels() {
15
+ try {
16
+ // const upscaler_comp = this.config.components.filter(comp =>comp.props.elem_id === "txt2img_hr_upscaler")[0]
17
+ let upscaler_comp
18
+ // console.log('this.config: ', this.config)
19
+ for (let comp of this.config.components) {
20
+ if (comp?.props?.elem_id) {
21
+ const elem_id = comp?.props?.elem_id
22
+ if (elem_id === 'txt2img_hr_upscaler') {
23
+ console.log('elem_id: ', elem_id)
24
+ upscaler_comp = comp
25
+ break
26
+ }
27
+ }
28
+ }
29
+ console.log('upscaler_comp: ', upscaler_comp)
30
+ const upscalers = upscaler_comp.props.choices
31
+
32
+ return upscalers
33
+ } catch (e) {
34
+ console.warn(e)
35
+ }
36
+ }
37
+
38
+ getControlNetMaxModelsNum() {
39
+ try {
40
+ let max_models_num = 0
41
+ for (let comp of this.config.components) {
42
+ if (comp?.props?.elem_id) {
43
+ const elem_id = comp?.props?.elem_id
44
+ if (elem_id === 'setting_control_net_max_models_num') {
45
+ console.log(
46
+ 'setting_control_net_max_models_num: ',
47
+ comp?.props?.value
48
+ )
49
+ max_models_num = comp?.props?.value
50
+ break
51
+ }
52
+ }
53
+ }
54
+ console.log('max_models_num: ', max_models_num)
55
+ return max_models_num
56
+ } catch (e) {
57
+ console.warn(e)
58
+ return 1 // default max number is one
59
+ }
60
+ }
61
+
62
+ getControlNetPreprocessors() {
63
+ try {
64
+ let max_models_num
65
+ let choices
66
+ for (let comp of this.config.components) {
67
+ const label = comp?.props?.label
68
+ if (label === 'Preprocessor') {
69
+ choices = comp?.props?.choices
70
+ break
71
+ }
72
+ }
73
+ console.log('Preprocessor list: ', choices)
74
+ return choices
75
+ } catch (e) {
76
+ console.warn(e)
77
+ }
78
+ }
79
+ }
80
+
81
+ module.exports = {
82
+ SdConfig,
83
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/sdapi/horde_native.js ADDED
@@ -0,0 +1,731 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const general = require('../general')
2
+ const psapi = require('../../psapi')
3
+ const html_manip = require('../html_manip')
4
+ const layer_util = require('../layer')
5
+ const dummy = require('../dummy')
6
+ const io = require('../io')
7
+ class HordeSettings {
8
+ static {}
9
+ static async saveSettings() {
10
+ try {
11
+ const settings = await getSettings()
12
+
13
+ let native_horde_settings = await mapPluginSettingsToHorde(settings)
14
+ const horde_api_key = html_manip.getHordeApiKey()
15
+ native_horde_settings['api_key'] = html_manip.getHordeApiKey()
16
+ await io.IOJson.saveHordeSettingsToFile(native_horde_settings)
17
+ } catch (e) {
18
+ console.warn(e)
19
+ }
20
+ }
21
+ static async loadSettings() {
22
+ try {
23
+ let native_horde_settings =
24
+ await io.IOJson.loadHordeSettingsFromFile()
25
+ html_manip.setHordeApiKey(native_horde_settings['api_key'])
26
+ } catch (e) {
27
+ console.warn(e)
28
+ }
29
+ }
30
+ }
31
+ class hordeGenerator {
32
+ //horde generation process:
33
+ //*) get the settings
34
+ //*) get send request
35
+ //*) wait for response
36
+ //*) load the image to the canvas
37
+ //*) move and scale image to the selection
38
+ //*) save the image to history/data folder
39
+ //*) load the image data into the plugin / viewer tab
40
+ //*)
41
+
42
+ //other options:
43
+ //*)interrupt the generation process
44
+ //*)cancel the generation process on error
45
+
46
+ constructor() {
47
+ this.horde_settings
48
+ this.plugin_settings
49
+ this.currentGenerationResult = null
50
+ this.requestStatus = null
51
+ this.isProcessHordeResultCalled = false
52
+ this.maxWaitTime = 0
53
+ this.waiting = 0
54
+ this.isCanceled = false
55
+ this.horde_id = null
56
+ this.last_horde_id = null
57
+ }
58
+
59
+ async getSettings() {
60
+ try {
61
+ const workers = await getWorkers()
62
+
63
+ const workers_ids = getWorkerID(workers)
64
+ const settings = await getSettings()
65
+ this.plugin_settings = settings
66
+ let payload = await mapPluginSettingsToHorde(settings)
67
+ // payload['workers'] = workers_ids
68
+ payload['workers'] = []
69
+
70
+ this.horde_settings = payload
71
+ return this.horde_settings
72
+ } catch (e) {
73
+ console.warn('getSettings: ', e)
74
+ }
75
+ }
76
+
77
+ /**
78
+ * @returns {json}{payload, dir_name, images_info, metadata}
79
+ */
80
+ async generateRequest(settings) {
81
+ try {
82
+ this.horde_id = null //reset request_id
83
+ this.requestStatus = await requestHorde(settings)
84
+ if (this.requestStatus?.message) {
85
+ await app.showAlert(this.requestStatus?.message)
86
+ }
87
+ this.horde_id = this.requestStatus.id
88
+ console.log(
89
+ 'generateRequest this.requestStatus: ',
90
+ this.requestStatus
91
+ )
92
+
93
+ const images_info = await this.startCheckingProgress()
94
+ const result = await this.toGenerationFormat(images_info)
95
+ console.warn('generateRequest() images_info: ', images_info)
96
+ console.warn('generateRequest() result: ', result)
97
+
98
+ html_manip.updateProgressBarsHtml(0) // reset progress bar
99
+ return result
100
+ } catch (e) {
101
+ this.horde_id = null
102
+ console.warn(e)
103
+ }
104
+ }
105
+ async generate() {
106
+ //*) get the settings
107
+ this.horde_settings = await this.getSettings()
108
+ //*) send generateRequest() and trigger the progress bar update
109
+ this.isCanceled = false
110
+ const result = await this.generateRequest(this.horde_settings)
111
+
112
+ return result
113
+ //*) store the generation result in the currentGenerationResult
114
+
115
+ //*) return the generation currentGenerationResult
116
+ }
117
+
118
+ isValidGeneration() {
119
+ if (this.currentGenerationResult) {
120
+ return true // if true if valid, false otherwise
121
+ } else {
122
+ return false
123
+ }
124
+ }
125
+ preGenerate() {}
126
+ // async layerToBase64WebpToFile
127
+ //convert layer to .webp file
128
+ //read the .webp file as buffer data base64 .webp
129
+ async layerToBase64Webp(layer, document_name, image_name) {
130
+ const width = html_manip.getWidth()
131
+ const height = html_manip.getHeight()
132
+ const image_buffer = await psapi.newExportPng(
133
+ layer,
134
+ image_name,
135
+ width,
136
+ height
137
+ )
138
+
139
+ const base64_image = _arrayBufferToBase64(image_buffer) //convert the buffer to base64
140
+ //send the base64 to the server to save the file in the desired directory
141
+ // await sdapi.requestSavePng(base64_image, image_name)
142
+ await saveFileInSubFolder(base64_image, document_name, image_name)
143
+ return base64_image
144
+ }
145
+
146
+ async layerToBase64ToFile(layer, document_name, image_name) {
147
+ const width = html_manip.getWidth()
148
+ const height = html_manip.getHeight()
149
+ const image_buffer = await psapi.newExportPng(
150
+ layer,
151
+ image_name,
152
+ width,
153
+ height
154
+ )
155
+
156
+ const base64_image = _arrayBufferToBase64(image_buffer) //convert the buffer to base64
157
+ //send the base64 to the server to save the file in the desired directory
158
+ // await sdapi.requestSavePng(base64_image, image_name)
159
+ await saveFileInSubFolder(base64_image, document_name, image_name)
160
+ return base64_image
161
+ }
162
+
163
+ async toGenerationFormat(images_info) {
164
+ //convert the output of native horde generation to the values that generate() can use
165
+ try {
166
+ //images_info[0] = {path:path,base64:base64png}
167
+ // let last_images_paths = await silentImagesToLayersExe(images_info)
168
+ let last_images_paths = {}
169
+ for (const image_info of images_info) {
170
+ const path = image_info['path']
171
+ // const base64_image = image_info['base64']
172
+ const layer = image_info['layer']
173
+ const [document_name, image_name] = path.split('/')
174
+
175
+ // await saveFileInSubFolder(base64_image, document_name, image_name)
176
+ image_info['base64'] = await this.layerToBase64ToFile(
177
+ layer,
178
+ document_name,
179
+ image_name
180
+ )
181
+
182
+ // delete the layer made by the webp image.
183
+ await layer_util.deleteLayers([layer])
184
+ // await layer.delete()
185
+
186
+ // const json_file_name = `${image_name.split('.')[0]}.json`
187
+ this.plugin_settings['auto_metadata'] =
188
+ image_info?.auto_metadata
189
+
190
+ // g_generation_session.base64OutputImages[path] =
191
+ // image_info['base64']
192
+ // await saveJsonFileInSubFolder(
193
+ // this.plugin_settings,
194
+ // document_name,
195
+ // json_file_name
196
+ // ) //save the settings
197
+ // last_images_paths[path] = image_info['layer']
198
+ // images_info.push({
199
+ // base64: i,
200
+ // path: image_path,
201
+ // auto_metadata: auto_metadata_json,
202
+ // })
203
+ // // console.log("metadata_json: ", metadata_json)
204
+ }
205
+
206
+ // if (g_generation_session.isFirstGeneration) {
207
+ // //store them in the generation session for viewer manager to use
208
+ // g_generation_session.image_paths_to_layers = last_images_paths
209
+ // } else {
210
+ // g_generation_session.image_paths_to_layers = {
211
+ // ...g_generation_session.image_paths_to_layers,
212
+ // ...last_images_paths,
213
+ // }
214
+ // // g_number_generation_per_session++
215
+
216
+ // }
217
+ const dir_name = 'temp_dir_name'
218
+ return {
219
+ // payload: payload,
220
+ dir_name: dir_name,
221
+ images_info: images_info,
222
+ metadata: this.plugin_settings,
223
+ }
224
+ } catch (e) {
225
+ console.warn(e)
226
+ }
227
+ }
228
+
229
+ async toSession(images_info) {
230
+ try {
231
+ //images_info[0] = {path:path,base64:base64png}
232
+ // let last_images_paths = await silentImagesToLayersExe(images_info)
233
+ let last_images_paths = {}
234
+ for (const image_info of images_info) {
235
+ const path = image_info['path']
236
+ // const base64_image = image_info['base64']
237
+ const layer = image_info['layer']
238
+ const [document_name, image_name] = path.split('/')
239
+
240
+ // await saveFileInSubFolder(base64_image, document_name, image_name)
241
+ image_info['base64'] = await this.layerToBase64ToFile(
242
+ layer,
243
+ document_name,
244
+ image_name
245
+ )
246
+ const json_file_name = `${image_name.split('.')[0]}.json`
247
+ this.plugin_settings['auto_metadata'] =
248
+ image_info?.auto_metadata
249
+
250
+ g_generation_session.base64OutputImages[path] =
251
+ image_info['base64']
252
+ await saveJsonFileInSubFolder(
253
+ this.plugin_settings,
254
+ document_name,
255
+ json_file_name
256
+ ) //save the settings
257
+ last_images_paths[path] = image_info['layer']
258
+ }
259
+
260
+ if (g_generation_session.isFirstGeneration) {
261
+ //store them in the generation session for viewer manager to use
262
+ g_generation_session.image_paths_to_layers = last_images_paths
263
+ } else {
264
+ g_generation_session.image_paths_to_layers = {
265
+ ...g_generation_session.image_paths_to_layers,
266
+ ...last_images_paths,
267
+ }
268
+ // g_number_generation_per_session++
269
+ }
270
+ } catch (e) {
271
+ console.warn(e)
272
+ }
273
+ }
274
+
275
+ async interruptRequest(horde_id) {
276
+ try {
277
+ console.log('interruptRquest():')
278
+
279
+ const full_url = `https://stablehorde.net/api/v2/generate/status/${horde_id}`
280
+
281
+ console.log(full_url)
282
+
283
+ let response = await fetch(full_url, {
284
+ method: 'DELETE',
285
+ headers: {
286
+ Accept: 'application/json',
287
+ 'Content-Type': 'application/json',
288
+ // 'Client-Agent': '4c79ab19-8e6c-4054-83b3-773b7ce71ece',
289
+ 'Client-Agent': 'unknown:0:unknown',
290
+ },
291
+ })
292
+
293
+ let result = await response.json()
294
+ console.log('interruptReqquest result:', result)
295
+
296
+ return result
297
+ } catch (e) {
298
+ console.warn(e)
299
+ return
300
+ }
301
+ }
302
+ async interrupt() {
303
+ try {
304
+ html_manip.updateProgressBarsHtml(0)
305
+ // g_generation_session.request_status = Enum.requestStatus['']
306
+ this.last_horde_id = this.horde_id
307
+ this.horde_id = null //horde_id could be used startCheckingprogress() so we need to nullify it as soon as possible. TODO: refactor this dependency.
308
+ this.isCanceled = true
309
+ // this.interval_id = clearTimeout(this.interval_id)
310
+ await this.interruptRequest(this.last_horde_id)
311
+ } catch (e) {
312
+ console.warn(e)
313
+ }
314
+ }
315
+ async postGeneration() {
316
+ toggleTwoButtonsByClass(false, 'btnGenerateClass', 'btnInterruptClass')
317
+ }
318
+ async processHordeResult() {
319
+ //*) get the result from the horde server
320
+ //*) save them locally to output directory
321
+ //*) import them into the canvas
322
+ //*) resize and move the layers to fit the selection
323
+ //*) return the results to be stored and processed by the g_generation_session
324
+ try {
325
+ if (this.isProcessHordeResultCalled) {
326
+ return
327
+ }
328
+ this.isProcessHordeResultCalled = true
329
+ console.log('horde request is done')
330
+ // g_b_request_result = true
331
+ const temp_id = this.horde_id //this.horde_id will reset
332
+ // cancelRequestClientSide()
333
+ g_horde_generation_result = await requestHordeStatus(temp_id)
334
+
335
+ const generations = g_horde_generation_result.generations
336
+ const writeable_entry = await getCurrentDocFolder()
337
+ const images_info = [] //{path:image_path,base64:}
338
+ for (const image_horde_container of generations) {
339
+ try {
340
+ const url = image_horde_container.img
341
+ const image_file_name = general.newOutputImageName('webp')
342
+
343
+ const image_layer = await downloadItExe(
344
+ url,
345
+ writeable_entry,
346
+ image_file_name
347
+ ) //download the image from url, it works even with .webp format
348
+ const image_png_file_name =
349
+ general.convertImageNameToPng(image_file_name)
350
+
351
+ const uuid = await getUniqueDocumentId()
352
+ const image_path = `${uuid}/${image_png_file_name}` //this is the png path
353
+ images_info.push({
354
+ path: image_path,
355
+ base64: dummy.getDummyBase64(), //TODO:change this to the base64_png
356
+ layer: image_layer,
357
+ })
358
+ await psapi.layerToSelection(
359
+ g_generation_session.selectionInfo
360
+ ) //TODO: create a safe layerToSelection function
361
+ } catch (e) {
362
+ console.warn(e)
363
+ }
364
+ }
365
+ this.isProcessHordeResultCalled = false //reset for next generation
366
+ return images_info
367
+ } catch (e) {
368
+ console.warn(e)
369
+ }
370
+ }
371
+ updateHordeProgressBar(check_horde_status) {
372
+ //update the progress bar proceduer
373
+ console.log('this.maxWaitTime: ', this.maxWaitTime)
374
+ console.log(
375
+ "check_horde_status['wait_time']: ",
376
+ check_horde_status['wait_time']
377
+ )
378
+ console.log(
379
+ "check_horde_status['waiting']: ",
380
+ check_horde_status['waiting']
381
+ )
382
+
383
+ this.maxWaitTime = Math.max(
384
+ check_horde_status['wait_time'],
385
+ this.maxWaitTime
386
+ ) // return the max time value, so we could use to calculate the complection percentage
387
+ const delta_time = this.maxWaitTime - check_horde_status['wait_time']
388
+
389
+ if (isNaN(this.maxWaitTime) || parseInt(this.maxWaitTime) === 0) {
390
+ this.maxWaitTime = 0 // reset to zero
391
+ } else {
392
+ console.log('delta_time:', delta_time)
393
+ console.log('this.maxWaitTime:', this.maxWaitTime)
394
+
395
+ const completion_percentage = (delta_time / this.maxWaitTime) * 100
396
+ console.log('completion_percentage:', completion_percentage)
397
+
398
+ html_manip.updateProgressBarsHtml(completion_percentage)
399
+ }
400
+ }
401
+ async startCheckingProgress() {
402
+ console.log('startCheckingProgress is called')
403
+ return await new Promise((resolve, reject) => {
404
+ if (this.horde_id && !this.isCanceled) {
405
+ this.interval_id = setTimeout(async () => {
406
+ try {
407
+ console.warn(
408
+ 'startCheckingProgress(): horde_id and isCanceled',
409
+ this.horde_id,
410
+ this.isCanceled
411
+ )
412
+ //check the request status
413
+ const check_json = await requestHordeCheck(
414
+ this.horde_id
415
+ )
416
+
417
+ this.updateHordeProgressBar(check_json)
418
+
419
+ if (check_json['done']) {
420
+ // this.interval_id = clearTimeout(this.interval_id)
421
+
422
+ const images_info = await this.processHordeResult()
423
+ if (this.horde_id) {
424
+ this.last_horde_id = this.horde_id
425
+ this.horde_id = null
426
+ }
427
+ return resolve(images_info)
428
+ } else {
429
+ //the request is not done and the user hasn't canceled it
430
+ console.warn(
431
+ 'startCheckingProgress(): reqursive startCheckingProgress call',
432
+ this.horde_id,
433
+ this.isCanceled
434
+ )
435
+ const horde_result =
436
+ await this.startCheckingProgress() // start another check
437
+ return resolve(horde_result) // return the result of the new check
438
+ }
439
+ } catch (e) {
440
+ console.warn(e)
441
+ const result = await this.startCheckingProgress()
442
+ return resolve(result)
443
+ }
444
+ }, 3000)
445
+ } else {
446
+ console.warn(
447
+ 'startCheckingProgress: else block',
448
+ this.horde_id,
449
+ this.isCanceled
450
+ )
451
+ return resolve()
452
+ }
453
+ })
454
+ }
455
+ }
456
+ const webui_to_horde_samplers = {
457
+ 'Euler a': 'k_euler_a',
458
+ Euler: 'k_euler',
459
+ LMS: 'k_lms',
460
+ Heun: 'k_heun',
461
+ DPM2: 'k_dpm_2',
462
+ 'DPM2 a': 'k_dpm_2_a',
463
+ 'DPM++ 2S a': 'k_dpmpp_2s_a',
464
+ 'DPM++ 2M': 'k_dpmpp_2m',
465
+ 'DPM++ SDE': 'k_dpmpp_sde',
466
+ 'DPM fast': 'k_dpm_fast',
467
+ 'DPM adaptive': 'k_dpm_adaptive',
468
+ 'LMS Karras': 'k_lms',
469
+ 'DPM2 Karras': 'k_dpm_2',
470
+ 'DPM2 a Karras': 'k_dpm_2_a',
471
+ 'DPM++ 2S a Karras': 'k_dpmpp_2s_a',
472
+ 'DPM++ 2M Karras': 'k_dpmpp_2m',
473
+ 'DPM++ SDE Karras': 'k_dpmpp_sde',
474
+ DDIM: 'ddim',
475
+ PLMS: 'plms',
476
+ }
477
+
478
+ //get workers
479
+ //select a worker
480
+ //send a request => requestHorde(horde_settings)
481
+ //check for progress => requestHordeCheck(request_id)
482
+ //when progress is full, request the result => requestHordeStatus(request_id)
483
+
484
+ async function mapPluginSettingsToHorde(plugin_settings) {
485
+ const { getModelHorde } = require('../sd_scripts/horde')
486
+ const ps = plugin_settings // for shortness
487
+ const sampler = webui_to_horde_samplers[ps['sampler_index']]
488
+ const model = getModelHorde()
489
+ let horde_prompt
490
+ if (ps['negative_prompt'].length > 0) {
491
+ horde_prompt = `${ps['prompt']} ### ${ps['negative_prompt']}`
492
+ } else {
493
+ horde_prompt = ps['prompt'] //no negative prompt
494
+ }
495
+ const extra_payload = {}
496
+ if (ps['mode'] === 'img2img') {
497
+ // payload['source_image'] = ps['init_images']
498
+ // let current_doc_entry =await getCurrentDocFolder()
499
+ // let webp_file = await current_doc_entry.getEntry('temp.webp')
500
+ // let base64_webp = await io.IO.base64WebpFromFile(webp_file)
501
+ // payload['source_image'] = io.IO.base64WebpFromFile()
502
+ // console.log('base64_webp:', base64_webp)
503
+
504
+ // const dummy_str = getDummyWebpBase64()
505
+ // if (base64_webp === dummy_str) {
506
+ // console.warn('the same base64')
507
+ // } else {
508
+ // console.warn('different base64')
509
+ // }
510
+ // payload['source_image'] = dummy_str
511
+
512
+ // payload['source_image'] = base64.b64encode(buffer.getvalue()).decode() //does it need to be webp?
513
+
514
+ const init_image_base64_webp = await io.IO.base64PngToBase64Webp(
515
+ ps['init_images'][0]
516
+ )
517
+ extra_payload['source_image'] = init_image_base64_webp
518
+ extra_payload['source_processing'] = 'img2img'
519
+ } else if (ps['mode'] === 'inpaint' || ps['mode'] === 'outpaint') {
520
+ const init_image_base64_webp = await io.IO.base64PngToBase64Webp(
521
+ ps['init_images'][0]
522
+ )
523
+ const mask_base64_webp = await io.IO.base64PngToBase64Webp(ps['mask'])
524
+ extra_payload['source_processing'] = 'inpainting'
525
+ extra_payload['source_image'] = init_image_base64_webp
526
+ extra_payload['source_mask'] = mask_base64_webp
527
+ // payload["source_mask"] = base64.b64encode(buffer.getvalue()).decode()//does it need to be webp?
528
+ }
529
+
530
+ let seed = ps['seed']
531
+ if (parseInt(ps['seed']) === -1) {
532
+ const random_seed = Math.floor(Math.random() * 100000000000 + 1) // Date.now() doesn't have enough resolution to avoid duplicate
533
+ seed = random_seed.toString()
534
+ }
535
+ const width = general.nearestMultiple(ps['width'], 64)
536
+ const height = general.nearestMultiple(ps['height'], 64)
537
+ const nsfw = html_manip.getUseNsfw()
538
+ let horde_payload = {
539
+ prompt: horde_prompt,
540
+ params: {
541
+ sampler_name: sampler,
542
+ toggles: [1, 4],
543
+ cfg_scale: ps['cfg_scale'],
544
+ denoising_strength: ps['denoising_strength'],
545
+ seed: seed,
546
+ height: height,
547
+ width: width,
548
+ seed_variation: 1,
549
+ post_processing: ['GFPGAN'],
550
+ karras: false,
551
+ tiling: false,
552
+ steps: parseInt(ps['steps']),
553
+ n: 1,
554
+ },
555
+ nsfw: nsfw,
556
+ trusted_workers: true,
557
+ censor_nsfw: false,
558
+ // workers: ['4c79ab19-8e6c-4054-83b3-773b7ce71ece'],
559
+ // workers: workers_ids,
560
+ // models: ['stable_diffusion'],
561
+ models: [model],
562
+ // source_image: 'string',
563
+ // source_processing: 'img2img',
564
+ // source_mask: 'string',
565
+ ...extra_payload,
566
+ r2: true,
567
+ shared: false,
568
+ }
569
+ return horde_payload
570
+ }
571
+
572
+ function getWorkerID(workers_json) {
573
+ let workers_ids = []
574
+ for (worker of workers_json) {
575
+ workers_ids.push(worker?.id)
576
+ }
577
+ console.log('workers_ids:', workers_ids)
578
+
579
+ return workers_ids
580
+ }
581
+ async function getWorkers() {
582
+ const full_url = 'https://stablehorde.net/api/v2/workers'
583
+ // const full_url = 'https://stablehorde.net/api/v2/generate/sync'
584
+ console.log(full_url)
585
+
586
+ let request = await fetch(full_url, {
587
+ method: 'GET',
588
+ headers: {
589
+ Accept: 'application/json',
590
+ },
591
+ })
592
+
593
+ let workers = await request.json()
594
+ // const workers_ids = getWorkerID(workers)
595
+ console.log('requestHorde workers:', workers)
596
+ return workers
597
+ }
598
+ async function requestHorde(payload) {
599
+ // const workers = await getWorkers()
600
+
601
+ // const workers_ids = getWorkerID(workers)
602
+ // const settings = await getSettings()
603
+ // payload = mapPluginSettingsToHorde(settings)
604
+ // payload['workers'] = workers_ids
605
+ // payload = {
606
+ // prompt: 'string',
607
+ // params: {
608
+ // sampler_name: 'k_lms',
609
+ // toggles: [1, 4],
610
+ // cfg_scale: 5,
611
+ // denoising_strength: 0.75,
612
+ // // seed: 'string',
613
+ // height: 512,
614
+ // width: 512,
615
+ // seed_variation: 1,
616
+ // post_processing: ['GFPGAN'],
617
+ // karras: false,
618
+ // tiling: false,
619
+ // steps: 5,
620
+ // n: 1,
621
+ // },
622
+ // nsfw: false,
623
+ // trusted_workers: true,
624
+ // censor_nsfw: false,
625
+ // // workers: ['4c79ab19-8e6c-4054-83b3-773b7ce71ece'],
626
+ // workers: workers_ids,
627
+ // models: ['stable_diffusion'],
628
+ // // source_image: 'string',
629
+ // // source_processing: 'img2img',
630
+ // // source_mask: 'string',
631
+ // r2: true,
632
+ // shared: false,
633
+ // }
634
+ try {
635
+ console.log('requestHorde():')
636
+
637
+ const full_url = 'https://stablehorde.net/api/v2/generate/async'
638
+ // const full_url = 'https://stablehorde.net/api/v2/generate/sync'
639
+ console.log(full_url)
640
+
641
+ const horde_api_key = html_manip.getHordeApiKey()
642
+ let request = await fetch(full_url, {
643
+ method: 'POST',
644
+ headers: {
645
+ Accept: 'application/json',
646
+ 'Content-Type': 'application/json',
647
+ apikey: horde_api_key,
648
+
649
+ 'Client-Agent': 'unknown:0:unknown',
650
+ },
651
+ body: JSON.stringify(payload),
652
+ })
653
+
654
+ let json = await request.json()
655
+ console.log('requestHorde json:', json)
656
+
657
+ return json
658
+ } catch (e) {
659
+ console.warn(e)
660
+ return {}
661
+ }
662
+ }
663
+ async function requestHordeCheck(id) {
664
+ try {
665
+ console.log('requestHordeCheck():')
666
+ const base_url = 'https://stablehorde.net/api/v2/generate/check'
667
+
668
+ const full_url = `${base_url}/${id}`
669
+ // const full_url = 'https://stablehorde.net/api/v2/generate/sync'
670
+ console.log(full_url)
671
+ const payload = {}
672
+ let request = await fetch(full_url, {
673
+ method: 'GET',
674
+ headers: {
675
+ Accept: 'application/json',
676
+ 'Content-Type': 'application/json',
677
+ // 'Client-Agent': '4c79ab19-8e6c-4054-83b3-773b7ce71ece',
678
+ 'Client-Agent': 'unknown:0:unknown',
679
+ },
680
+ })
681
+
682
+ let json = await request.json()
683
+ console.log('requestHordeCheck json:', json)
684
+
685
+ return json
686
+ } catch (e) {
687
+ console.warn(e)
688
+ return {}
689
+ }
690
+ }
691
+
692
+ async function requestHordeStatus(id) {
693
+ try {
694
+ console.log('requestHordeStatus():')
695
+ const base_url = 'https://stablehorde.net/api/v2/generate/status'
696
+
697
+ const full_url = `${base_url}/${id}`
698
+ // const full_url = 'https://stablehorde.net/api/v2/generate/sync'
699
+ console.log(full_url)
700
+ const payload = {}
701
+ let request = await fetch(full_url, {
702
+ method: 'GET',
703
+ headers: {
704
+ Accept: 'application/json',
705
+ 'Content-Type': 'application/json',
706
+ // 'Client-Agent': '4c79ab19-8e6c-4054-83b3-773b7ce71ece',
707
+ 'Client-Agent': 'unknown:0:unknown',
708
+ },
709
+ })
710
+
711
+ let json = await request.json()
712
+ console.log('requestHordeStatus json:', json)
713
+
714
+ return json
715
+ } catch (e) {
716
+ console.warn(e)
717
+ }
718
+ }
719
+
720
+ let g_horde_generation_result
721
+ let g_b_request_result = false
722
+ function cancelRequestClientSide() {
723
+ this.interval_id = clearTimeout(this.interval_id)
724
+ // g_id = null
725
+ g_b_request_result = false
726
+ }
727
+
728
+ module.exports = {
729
+ hordeGenerator,
730
+ HordeSettings,
731
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/sdapi/metadata_to_json.js ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // import serverHelper
2
+ // # metadata_str = 'cute cat\nSteps: 20, Sampler: Euler a, CFG scale: 7.0, Seed: 2253354038, Size: 512x512, Model hash: 3e16efc8, Seed resize from: -1x-1, Denoising strength: 0, Conditional mask weight: 1.0'
3
+ // def convertMetadataToJson(metadata_str):
4
+ // print(metadata_str)
5
+ // last_new_line_index = metadata_str.rindex('\n')
6
+ // prompt = metadata_str[:last_new_line_index]
7
+ // other_settings = metadata_str[last_new_line_index+1:]
8
+
9
+ // print("prompt:", prompt)
10
+ // print("other_settings:", other_settings)
11
+ // sub_settings = other_settings.split(",")
12
+ // print("sub_settings: ",sub_settings)
13
+
14
+ // settings_dict = {}
15
+ // settings_dict['prompt'] = prompt
16
+
17
+ // for setting in sub_settings:
18
+ // [key,value]= setting.split(":")
19
+ // key = key.lstrip(' ')
20
+ // value = value.lstrip(' ')
21
+ // settings_dict[key] = value
22
+ // import json
23
+ // settings_json = json.dumps(settings_dict)
24
+ // print("settings_dict: ",settings_dict)
25
+ // print("settings_json ",settings_json)
26
+ // return settings_json
27
+
28
+ // // function getMetadataFromPng(image_path){
29
+
30
+ // // // image_path = "./output/5c42fd2a-6708-45e2-b282-2e9f3894368e/output- 1672476035.4888158.png"
31
+ // // // image_path = "C:/Users/abdul/Desktop/auto-photoshop/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/output/5c42fd2a-6708-45e2-b282-2e9f3894368e/output- 1672476035.4888158.png"
32
+ // // im = Image.open(image_path)
33
+ // // # im.load() # Needed only for .png EXIF data (see citation above)
34
+ // // # print(im.info['parameters'])
35
+ // // metadata_string = im.info['parameters']
36
+ // // metadata_json_string = convertMetadataToJson(metadata_string)
37
+ // // metadata_dict = json.loads(metadata_json_string)
38
+ // // print("metadata_dict: ", metadata_dict)
39
+ // // # print(im.info['meta_to_read'])
40
+ // // return metadata_dict
41
+
42
+ // }
43
+ // def createMetadataJsonFileIfNotExist(image_path):
44
+
45
+ // # image_name = os.path.splitext(image_path)
46
+ // image_name = Path(image_path).stem
47
+ // # parent_dir_path = Path(image_path)
48
+ // # parent_dir_path = image_path.split(image_name)[0]
49
+ // # os.path.join()
50
+ // head = os.path.split(image_path)[0]
51
+ // json_file_tail = f'{image_name}.json'
52
+ // json_full_path = os.path.join(head,json_file_tail)
53
+ // print("image_name: ",image_name)
54
+ // print("json_full_path: ",json_full_path)
55
+ // isExist = os.path.exists(json_full_path)
56
+ // if(isExist):
57
+ // #read metadata from json
58
+ // metadata_dict = serverHelper.readJson(json_full_path)
59
+
60
+ // else:
61
+ // #read metadata from image
62
+ // #save the metadata to a json file
63
+ // metadata_dict = getMetadataFromPng(image_path)
64
+ // serverHelper.writeJson(json_full_path,metadata_dict)
65
+ // return metadata_dict
66
+
67
+ // if __name__ == "__main__":
68
+ // image_path = "C:/Users/abdul/Desktop/auto-photoshop/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/output/5c42fd2a-6708-45e2-b282-2e9f3894368e/output- 1672476035.4888158.png"
69
+ // # getMetadataFromPng(image_path)
70
+ // createMetadataJsonFileIfNotExist(image_path)
71
+
72
+ // module.exports = {
73
+ // convertMetadataToJson,
74
+ // getMetadataFromPng,
75
+ // createMetadataJsonFileIfNotExist,
76
+ // }
Auto-Photoshop-StableDiffusion-Plugin/utility/sdapi/options.js ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class SdOptions {
2
+ constructor() {
3
+ // this.status = false // true if we have a valid copy of sd options, false otherwise
4
+ this.options //store sd options
5
+ }
6
+
7
+ async getOptions() {
8
+ try {
9
+ // if (this.status) {
10
+ // return this.options
11
+ // } else {
12
+ // this.options = await sdapi.requestGetOptions()
13
+ // if (this.options) {
14
+ // this.status = true
15
+ // }
16
+ // }
17
+ this.options = await sdapi.requestGetOptions()
18
+ return this.options
19
+ } catch (e) {
20
+ console.warn(e)
21
+ }
22
+ }
23
+ getCurrentModel() {
24
+ const current_model = this.options?.sd_model_checkpoint
25
+ return current_model
26
+ }
27
+ getInpaintingMaskWeight() {
28
+ const inpainting_mask_weight = this.options?.inpainting_mask_weight
29
+ return inpainting_mask_weight
30
+ }
31
+ }
32
+ // const sd_options = new SdOptions()
33
+ // sd_options.option?.sd_model_checkpoint
34
+
35
+ module.exports = {
36
+ SdOptions,
37
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/sdapi/prompt_shortcut.js ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function find_words_inside_braces(string) {
2
+ const re = /\{(.*?)\}/g
3
+ let keywords = string.match(re)
4
+ // console.log('keywords: ', keywords)
5
+ if (!keywords) {
6
+ //avoid null keywords
7
+ keywords = []
8
+ }
9
+ return keywords
10
+ }
11
+
12
+ function replaceShortcut(text, prompt_shortcut_json) {
13
+ const original_keywords = find_words_inside_braces(text)
14
+ const strip_keywords = original_keywords.map((s) => {
15
+ let content = s.slice(1, -1) //remove '{' and '}'
16
+ content = content.trim() //remove any space in the beginning and end of content
17
+ return content
18
+ })
19
+
20
+ // original_substrings = list(map(lambda s: '{'+s+'}',raw_keywords))
21
+
22
+ // print("strip_keywords: ", strip_keywords)
23
+ // print("original_substrings: ",original_substrings)
24
+ // # print ("text:",text)
25
+
26
+ let i = 0
27
+ for (const word of strip_keywords) {
28
+ // # word = word.strip()
29
+ // print("word: ",word)
30
+ if (word.length > 0 && prompt_shortcut_json.hasOwnProperty(word)) {
31
+ const prompt = prompt_shortcut_json[word]
32
+ console.log('prompt: ', prompt)
33
+ text = text.replace(original_keywords[i], prompt)
34
+ }
35
+ }
36
+ console.log('final text: ', text)
37
+ return text
38
+ }
39
+ module.exports = {
40
+ find_words_inside_braces,
41
+ replaceShortcut,
42
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/sdapi/python_replacement.js ADDED
@@ -0,0 +1,594 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //how to get environment variable in javascript
2
+ const settings_tab = require('../tab/settings')
3
+ const { getPromptShortcut } = require('../html_manip')
4
+ const general = require('../general')
5
+ // function newOutputImageName(format = 'png') {
6
+ // const random_id = Math.floor(Math.random() * 100000000000 + 1) // Date.now() doesn't have enough resolution to avoid duplicate
7
+ // const image_name = `output- ${Date.now()}-${random_id}.${format}`
8
+ // console.log('generated image name:', image_name)
9
+ // return image_name
10
+ // }
11
+
12
+ function convertMetadataToJson(metadata_str) {
13
+ try {
14
+ console.log('metadata_str:', metadata_str)
15
+ const last_new_line_index = metadata_str.lastIndexOf('\n')
16
+
17
+ const prompt = metadata_str.slice(0, last_new_line_index)
18
+ const other_settings = metadata_str.slice(last_new_line_index + 1, -1)
19
+
20
+ console.log('prompt:', prompt)
21
+ console.log('other_settings:', other_settings)
22
+ const sub_settings = other_settings.split(',')
23
+ console.log('sub_settings: ', sub_settings)
24
+
25
+ const settings_json = {}
26
+ settings_json['prompt'] = prompt
27
+
28
+ for (const setting of sub_settings) {
29
+ let [key, value] = setting.split(':').map((s) => s.trimLeft())
30
+ // key = key.lstrip(' ')
31
+ // value = value.lstrip(' ')
32
+ settings_json[key] = value
33
+ // import json
34
+ // settings_json = json.dumps(settings_dict)
35
+ // print("settings_dict: ",settings_dict)
36
+ // print("settings_json ",settings_json)
37
+ }
38
+
39
+ return settings_json
40
+ } catch (e) {
41
+ console.warn(e)
42
+ }
43
+ }
44
+
45
+ async function getAuto1111Metadata(base64_image) {
46
+ try {
47
+ console.log('getAuto1111Metadata: ')
48
+
49
+ const full_url = `${g_sd_url}/sdapi/v1/png-info`
50
+
51
+ const payload = {
52
+ image: 'data:image/png;base64,' + base64_image,
53
+ }
54
+ let request = await fetch(full_url, {
55
+ method: 'POST',
56
+ headers: {
57
+ Accept: 'application/json',
58
+ 'Content-Type': 'application/json',
59
+ },
60
+ body: JSON.stringify(payload),
61
+ })
62
+
63
+ let json = await request.json()
64
+ console.log("json['info']:", json['info'])
65
+
66
+ console.log('getAuto1111Metadata json:', json)
67
+
68
+ return json['info']
69
+ } catch (e) {
70
+ console.warn(e)
71
+ }
72
+ }
73
+ async function convertToStandardResponse(settings, images, uuid) {
74
+ try {
75
+ //standardized the response between modes and backends
76
+ const uniqueDocumentId = uuid // maybe use the generation_session uuid
77
+
78
+ const image_paths = []
79
+
80
+ const metadata = []
81
+ const images_info = []
82
+
83
+ for (i of images) {
84
+ let auto_metadata_json = {}
85
+ try {
86
+ const auto_metadata_str = await getAuto1111Metadata(i)
87
+ auto_metadata_json = convertMetadataToJson(auto_metadata_str)
88
+ console.warn(
89
+ 'auto_metadata_json.Seed:',
90
+ auto_metadata_json?.Seed
91
+ )
92
+ } catch (e) {
93
+ console.warn(e)
94
+ auto_metadata_json = {} // set the metadata to empty if there an error while getting the metadata
95
+ }
96
+
97
+ const image_name = general.newOutputImageName()
98
+ const image_path = `${uniqueDocumentId}/${image_name}`
99
+
100
+ images_info.push({
101
+ base64: i,
102
+ path: image_path,
103
+ auto_metadata: auto_metadata_json,
104
+ })
105
+ // console.log("metadata_json: ", metadata_json)
106
+ }
107
+ const dir_name = 'temp_dir_name'
108
+ return {
109
+ payload: settings,
110
+ dir_name: dir_name,
111
+ images_info: images_info,
112
+ metadata: metadata,
113
+ }
114
+ } catch (e) {
115
+ console.warn(e)
116
+ }
117
+ }
118
+ function replacePromptsWithShortcuts(
119
+ prompt,
120
+ negative_prompt,
121
+ prompt_shortcut_dic
122
+ ) {
123
+ // const prompt_shortcut_dict = prompt_shortcut.load()
124
+ // prompt_shortcut_dict.update(payload["prompt_shortcut_ui_dict"])
125
+ new_prompt = prompt_shortcut.replaceShortcut(prompt, prompt_shortcut_dic)
126
+ // # edit negative prompt, replaceShortcut(negative_prompt)
127
+ new_negative_prompt = prompt_shortcut.replaceShortcut(
128
+ negative_prompt,
129
+ prompt_shortcut_dic
130
+ )
131
+ return [new_prompt, new_negative_prompt]
132
+ }
133
+ async function txt2ImgRequest(payload) {
134
+ console.log('payload:', payload)
135
+
136
+ if (payload['use_prompt_shortcut']) {
137
+ const [new_prompt, new_negative_prompt] = replacePromptsWithShortcuts(
138
+ payload['prompt'],
139
+ payload['negative_prompt'],
140
+ payload['prompt_shortcut_ui_dict']
141
+ )
142
+ payload['prompt'] = new_prompt
143
+ payload['negative_prompt'] = new_negative_prompt
144
+ }
145
+
146
+ const endpoint = 'sdapi/v1/txt2img'
147
+ try {
148
+ console.log('txt2ImgRequest(): about to send a fetch request')
149
+
150
+ const full_url = `${g_sd_url}/${endpoint}`
151
+ console.log(full_url)
152
+
153
+ let request = await fetch(full_url, {
154
+ method: 'POST',
155
+ headers: {
156
+ Accept: 'application/json',
157
+ 'Content-Type': 'application/json',
158
+ },
159
+ body: JSON.stringify(payload),
160
+ // "body": payload
161
+ })
162
+
163
+ let r = await request.json()
164
+ console.log('txt2ImgRequest json:', r)
165
+
166
+ const uniqueDocumentId = payload['uniqueDocumentId']
167
+ // dir_fullpath,dirName = serverHelper.getUniqueDocumentDirPathName(uniqueDocumentId)
168
+ // serverHelper.createFolder(dir_fullpath)
169
+ const image_paths = []
170
+
171
+ const metadata = []
172
+ const images_info = []
173
+
174
+ for (i of r['images']) {
175
+ let auto_metadata_json = {}
176
+ try {
177
+ const auto_metadata_str = await getAuto1111Metadata(i)
178
+ auto_metadata_json = convertMetadataToJson(auto_metadata_str)
179
+ console.warn(
180
+ 'auto_metadata_json.Seed:',
181
+ auto_metadata_json?.Seed
182
+ )
183
+ } catch (e) {
184
+ console.warn(e)
185
+ auto_metadata_json = {} // set the metadata to empty if there an error while getting the metadata
186
+ }
187
+
188
+ // response2 = await client.post(url=f'{sd_url}/sdapi/v1/png-info', json=png_payload)
189
+ // pnginfo = PngImagePlugin.PngInfo()
190
+ // pnginfo.add_text("parameters", response2.json().get("info"))
191
+
192
+ const image_name = general.newOutputImageName()
193
+ const image_path = `${uniqueDocumentId}/${image_name}`
194
+
195
+ // image_path = f'output/{dirName}/{image_name}'
196
+ // image_paths.append(image_path)
197
+ // image.save(f'./{image_path}', pnginfo=pnginfo)
198
+
199
+ // metadata_info = response2.json().get("info")
200
+ // metadata_json = metadata_to_json.convertMetadataToJson(metadata_info)
201
+ // metadata.append(metadata_json)
202
+
203
+ images_info.push({
204
+ base64: i,
205
+ path: image_path,
206
+ auto_metadata: auto_metadata_json,
207
+ })
208
+ // console.log("metadata_json: ", metadata_json)
209
+ }
210
+ const dir_name = 'temp_dir_name'
211
+ return {
212
+ payload: payload,
213
+ dir_name: dir_name,
214
+ images_info: images_info,
215
+ metadata: metadata,
216
+ }
217
+ } catch (e) {
218
+ console.warn(e)
219
+ return {}
220
+ }
221
+
222
+ // const request_path = '/sdapi/v1/txt2img'
223
+ }
224
+ function getExtensionUrl() {
225
+ const extension_type = settings_tab.getExtensionType()
226
+ let extension_url
227
+ if (extension_type === 'auto1111_extension') {
228
+ extension_url = `${g_sd_url}/sdapi/auto-photoshop-sd`
229
+ } else if (extension_type === 'proxy_server') {
230
+ extension_url = 'http://127.0.0.1:8000'
231
+ } else {
232
+ //none
233
+ extension_url = ''
234
+ }
235
+ return extension_url
236
+ }
237
+
238
+ async function openUrlRequest(url) {
239
+ try {
240
+ const payload = {
241
+ url: url,
242
+ }
243
+ // const full_url = 'http://127.0.0.1:8000/mask/expansion/'
244
+ const extension_url = getExtensionUrl()
245
+ const full_url = `${extension_url}/open/url/`
246
+ let request = await fetch(full_url, {
247
+ method: 'POST',
248
+ headers: {
249
+ Accept: 'application/json',
250
+ 'Content-Type': 'application/json',
251
+ },
252
+ body: JSON.stringify(payload),
253
+ // "body": payload
254
+ })
255
+
256
+ let r = await request.json()
257
+
258
+ console.log('openUrlRequest json:', r)
259
+ return r['url']
260
+ } catch (e) {
261
+ console.warn(e)
262
+ }
263
+ }
264
+ async function maskExpansionRequest(original_mask, mask_expansion_value) {
265
+ // const endpoint = 'sdapi/v1/img2img'
266
+ // const full_url = `${g_sd_url}/${endpoint}`
267
+
268
+ try {
269
+ const payload = {
270
+ mask: original_mask,
271
+ mask_expansion: mask_expansion_value,
272
+ }
273
+ // const full_url = 'http://127.0.0.1:8000/mask/expansion/'
274
+ const extension_url = getExtensionUrl()
275
+ const full_url = `${extension_url}/mask/expansion/`
276
+ let request = await fetch(full_url, {
277
+ method: 'POST',
278
+ headers: {
279
+ Accept: 'application/json',
280
+ 'Content-Type': 'application/json',
281
+ },
282
+ body: JSON.stringify(payload),
283
+ // "body": payload
284
+ })
285
+
286
+ let r = await request.json()
287
+
288
+ console.log('maskExpansionRequest json:', r)
289
+ return r['mask']
290
+ } catch (e) {
291
+ console.warn(e)
292
+ }
293
+ }
294
+ async function img2ImgRequest(sd_url, payload) {
295
+ console.log('payload:', payload)
296
+
297
+ if (payload['use_prompt_shortcut']) {
298
+ const [new_prompt, new_negative_prompt] = replacePromptsWithShortcuts(
299
+ payload['prompt'],
300
+ payload['negative_prompt'],
301
+ payload['prompt_shortcut_ui_dict']
302
+ )
303
+ payload['prompt'] = new_prompt
304
+ payload['negative_prompt'] = new_negative_prompt
305
+ }
306
+ // init_img_dir = "./init_images"
307
+ // init_img_name = payload['init_image_name']
308
+ // init_img = Image.open(f"{init_img_dir}/{init_img_name}")
309
+ // init_img_str = img_2_b64(init_img)
310
+ // payload['init_images'] = [init_img_str]
311
+
312
+ // init_img_mask_name = payload.get('init_image_mask_name',"")
313
+
314
+ // #only if image exist then try to open it
315
+
316
+ // if(len(init_img_mask_name) > 0):
317
+ // init_img_mask = Image.open(f"{init_img_dir}/{init_img_mask_name}")
318
+
319
+ // if (payload['use_sharp_mask'] === false && payload['mask']) {
320
+ // //only if mask is available and sharp_mask is off
321
+ // // use blurry and expanded mask
322
+ // const iterations = payload['mask_expansion']
323
+ // const mask = await maskExpansionRequest(payload['mask'], iterations)
324
+ // if (mask) {
325
+ // payload['mask'] = mask
326
+ // }
327
+ // }
328
+
329
+ // print(type(init_img_str))
330
+ // #request the images to be generated
331
+
332
+ const endpoint = 'sdapi/v1/img2img'
333
+
334
+ const full_url = `${sd_url}/${endpoint}`
335
+ let request = await fetch(full_url, {
336
+ method: 'POST',
337
+ headers: {
338
+ Accept: 'application/json',
339
+ 'Content-Type': 'application/json',
340
+ },
341
+ body: JSON.stringify(payload),
342
+ // "body": payload
343
+ })
344
+
345
+ let r = await request.json()
346
+
347
+ console.log('img2ImgRequest json:', r)
348
+
349
+ const uniqueDocumentId = payload['uniqueDocumentId']
350
+ // dir_fullpath,dirName = serverHelper.getUniqueDocumentDirPathName(uniqueDocumentId)
351
+ // serverHelper.createFolder(dir_fullpath)
352
+ const image_paths = []
353
+ const metadata = []
354
+ const images_info = []
355
+
356
+ for (i of r['images']) {
357
+ // image = Image.open(io.BytesIO(base64.b64decode(i.split(",",1)[0])))
358
+ let auto_metadata_json = {}
359
+ try {
360
+ const auto_metadata_str = await getAuto1111Metadata(i)
361
+ auto_metadata_json = convertMetadataToJson(auto_metadata_str)
362
+ } catch (e) {
363
+ console.warn(e)
364
+ auto_metadata_json = {} // set the metadata to empty if there an error while getting the metadata
365
+ }
366
+ // response2 = await client.post(url=f'{sd_url}/sdapi/v1/png-info', json=png_payload, timeout=None)
367
+ // pnginfo = PngImagePlugin.PngInfo()
368
+ // pnginfo.add_text("parameters", response2.json().get("info"))
369
+ // image_name = f'output- {time.time()}.png'
370
+ // image_path = f'output/{dirName}/{image_name}'
371
+ // image_paths.append(image_path)
372
+ // image.save(f'./{image_path}', pnginfo=pnginfo)
373
+
374
+ // metadata_info = response2.json().get("info")
375
+ // metadata_json = metadata_to_json.convertMetadataToJson(metadata_info)
376
+ // metadata.append(metadata_json)
377
+ const image_name = general.newOutputImageName()
378
+ const image_path = `${uniqueDocumentId}/${image_name}`
379
+
380
+ images_info.push({
381
+ base64: i,
382
+ path: image_path,
383
+ auto_metadata: auto_metadata_json,
384
+ })
385
+ // print("metadata_json: ", metadata_json)
386
+ }
387
+ const dir_name = 'temp_dir_name'
388
+ // return [dirName, images_info, metadata]
389
+ return {
390
+ payload: payload,
391
+ dir_name: dir_name,
392
+ images_info: images_info,
393
+ metadata: metadata,
394
+ }
395
+ }
396
+
397
+ async function getOutputImagesEntries(doc_entry) {
398
+ let entries = await doc_entry.getEntries()
399
+ const output_images_entries = entries.filter(
400
+ (e) => e.isFile && e.name.toLowerCase().includes('.png') // must be a file and has the of the type .png
401
+ )
402
+ console.log('output_images_entries: ', output_images_entries)
403
+ // .forEach((e) => console.log(e.name))
404
+ return output_images_entries
405
+ }
406
+
407
+ async function getMetaDataForOutputEntry(doc_entry, output_entry) {
408
+ const json_file_name = `${output_entry.name.split('.')[0]}.json`
409
+
410
+ try {
411
+ const json_entry = await doc_entry.getEntry(json_file_name)
412
+ if (json_entry) {
413
+ // await json_entry.read()
414
+
415
+ const json = JSON.parse(
416
+ await json_entry.read({
417
+ format: storage.formats.utf8,
418
+ })
419
+ )
420
+ return json
421
+ }
422
+ } catch (e) {
423
+ console.warn(e)
424
+ }
425
+ return {}
426
+ }
427
+ async function loadHistory(payload) {
428
+ // {'image_paths','metadata_setting'}
429
+ const history = {}
430
+
431
+ // const uniqueDocumentId = payload['uniqueDocumentId']
432
+ // const uniqueDocumentId = await getUniqueDocumentId()
433
+
434
+ const uuid = await getUniqueDocumentId()
435
+ const doc_entry = await getDocFolder(uuid)
436
+ const output_images_entries = await getOutputImagesEntries(doc_entry)
437
+ history['image_paths'] = []
438
+ history['metadata_jsons'] = []
439
+ history['base64_images'] = []
440
+ for (const output_entry of output_images_entries) {
441
+ history['image_paths'].push(output_entry.name)
442
+ const metadata_json = await getMetaDataForOutputEntry(
443
+ doc_entry,
444
+ output_entry
445
+ )
446
+ history['metadata_jsons'].push(metadata_json)
447
+
448
+ const arrayBuffer = await output_entry.read({ format: formats.binary })
449
+ const base64_image = _arrayBufferToBase64(arrayBuffer) //convert the buffer to base64
450
+
451
+ // const base64 =
452
+ history['base64_images'].push(base64_image)
453
+ }
454
+
455
+ // image_paths = glob.glob(f'./output/{uniqueDocumentId}/*.png')
456
+ // settings_paths = glob.glob(f'./output/{uniqueDocumentId}/*.json')#note: why is we are not using settings_paths?
457
+ // print("loadHistory: image_paths:", image_paths)
458
+
459
+ // history['image_paths'] = image_paths
460
+ // history['metadata_jsons'] = []
461
+ // history['base64_images'] = []
462
+ // for image_path in image_paths:
463
+ // print("image_path: ", image_path)
464
+ // metadata_dict = metadata_to_json.createMetadataJsonFileIfNotExist(image_path)
465
+ // history['metadata_jsons'].append(metadata_dict)
466
+
467
+ // img = Image.open(image_path)
468
+ // base64_image = img_2_b64(img)
469
+ // history['base64_images'].append(base64_image)
470
+
471
+ // except:
472
+
473
+ // print(f'{request}')
474
+
475
+ // #reverse the order so that newer generated images path will be shown first
476
+
477
+ // history['image_paths'].reverse()
478
+ // history['metadata_jsons'].reverse()
479
+ // history['base64_images'].reverse()
480
+ return {
481
+ image_paths: history['image_paths'],
482
+ metadata_jsons: history['metadata_jsons'],
483
+ base64_images: history['base64_images'],
484
+ }
485
+ }
486
+
487
+ async function savePromptShortcut(json, file_name) {
488
+ console.warn(
489
+ "savePromptShortcut() is deprecated, use it's IO class instead "
490
+ )
491
+ try {
492
+ const json_file_name = file_name
493
+
494
+ const folder = await storage.localFileSystem.getDataFolder()
495
+
496
+ const file = await folder.createFile(json_file_name, {
497
+ type: storage.types.file,
498
+ overwrite: true,
499
+ })
500
+
501
+ const JSONInPrettyFormat = JSON.stringify(json, undefined, 4)
502
+ await file.write(JSONInPrettyFormat, {
503
+ format: storage.formats.utf8,
504
+ append: false,
505
+ })
506
+ } catch (e) {
507
+ console.warn(e)
508
+ }
509
+ }
510
+
511
+ async function loadPromptShortcut(file_name) {
512
+ const json_file_name = file_name
513
+
514
+ const folder = await storage.localFileSystem.getDataFolder()
515
+
516
+ try {
517
+ const json_entry = await folder.getEntry(json_file_name)
518
+ if (json_entry) {
519
+ // await json_entry.read()
520
+
521
+ const json = JSON.parse(
522
+ await json_entry.read({
523
+ format: storage.formats.utf8,
524
+ })
525
+ )
526
+ return json
527
+ }
528
+ } catch (e) {
529
+ console.warn(e)
530
+ }
531
+ }
532
+
533
+ async function extraSingleImageRequest(sd_url, payload) {
534
+ console.log('extraSingleImageRequest payload:', payload)
535
+
536
+ const endpoint = 'sdapi/v1/extra-single-image'
537
+
538
+ const full_url = `${sd_url}/${endpoint}`
539
+ let request = await fetch(full_url, {
540
+ method: 'POST',
541
+ headers: {
542
+ Accept: 'application/json',
543
+ 'Content-Type': 'application/json',
544
+ },
545
+ body: JSON.stringify(payload),
546
+ // "body": payload
547
+ })
548
+
549
+ let r = await request.json()
550
+
551
+ const images_info = []
552
+
553
+ const image = r['image']
554
+
555
+ let auto_metadata_json = {}
556
+
557
+ const uniqueDocumentId = payload['uniqueDocumentId']
558
+ const image_name = general.newOutputImageName()
559
+ const image_path = `${uniqueDocumentId}/${image_name}`
560
+
561
+ images_info.push({
562
+ base64: image,
563
+ path: image_path,
564
+ auto_metadata: auto_metadata_json,
565
+ })
566
+
567
+ console.log('extraSingleImageRequest response json:', r)
568
+
569
+ const dir_name = 'temp_dir_name'
570
+ const metadata = []
571
+
572
+ return {
573
+ payload: payload,
574
+ dir_name: dir_name,
575
+ images_info: images_info,
576
+ metadata: metadata,
577
+ }
578
+ }
579
+
580
+ module.exports = {
581
+ txt2ImgRequest,
582
+ img2ImgRequest,
583
+ loadHistory,
584
+ maskExpansionRequest,
585
+ getExtensionUrl,
586
+ savePromptShortcut,
587
+ loadPromptShortcut,
588
+
589
+ convertMetadataToJson,
590
+ openUrlRequest,
591
+ replacePromptsWithShortcuts,
592
+ extraSingleImageRequest,
593
+ convertToStandardResponse,
594
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/session.js ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { cleanLayers } = require('../psapi')
2
+ const psapi = require('../psapi')
3
+ const io = require('./io')
4
+ const Enum = require('../enum')
5
+ const { ViewerManager } = require('../viewer')
6
+ const { base64ToBase64Url } = require('./general')
7
+ const html_manip = require('./html_manip')
8
+ const layer_util = require('./layer')
9
+ const SessionState = {
10
+ Active: 'active',
11
+ Inactive: 'inactive',
12
+ }
13
+ const GarbageCollectionState = {
14
+ Accept: 'accept', // accept all generated images
15
+ Discard: 'discard', //discard all generated images
16
+ DiscardSelected: 'discard_selected',
17
+ AcceptSelected: 'accept_selected', //accept_selected only chosen images
18
+ }
19
+
20
+ class GenerationSession {
21
+ constructor() {
22
+ //this should be unique session id and it also should act as the total number of sessions been created in the project
23
+ this.id = 0
24
+ this.state = SessionState['Inactive']
25
+ this.mode = 'txt2img'
26
+ this.selectionInfo = null
27
+ this.isFirstGeneration = true // only before the first generation is requested should this be true
28
+ this.outputGroup
29
+ this.prevOutputGroup
30
+ this.isLoadingActive = false
31
+ this.base64OutputImages = {} //image_id/path => base64_image
32
+ this.base64initImages = {} //init_image_path => base64
33
+ this.base64maskImage = []
34
+ this.base64maskExpansionImage
35
+ this.activeBase64InitImage
36
+ this.activeBase64MaskImage
37
+ this.image_paths_to_layers = {}
38
+ this.progress_layer
39
+ this.last_settings //the last settings been used for generation
40
+ this.controlNetImage = [] // base64 images (one for each control net)
41
+ this.controlNetMask = [] // base64 images (one for each control net)
42
+ this.request_status = Enum.RequestStateEnum['Finished'] //finish or ideal state
43
+ this.is_control_net = false
44
+ this.control_net_selection_info
45
+ this.control_net_preview_selection_info
46
+ }
47
+ isActive() {
48
+ return this.state === SessionState['Active']
49
+ }
50
+ isInactive() {
51
+ return this.state === SessionState['Inactive']
52
+ }
53
+ activate() {
54
+ this.state = SessionState['Active']
55
+ }
56
+ deactivate() {
57
+ this.state = SessionState['Inactive']
58
+ }
59
+ name() {
60
+ return `session - ${this.id}`
61
+ }
62
+ async startSession() {
63
+ this.id += 1 //increment the session id for each session we start
64
+ this.activate()
65
+ this.isFirstGeneration = true // only before the first generation is requested should this be true
66
+
67
+ console.log('current session id: ', this.id)
68
+ try {
69
+ const session_name = this.name()
70
+ const activeLayers = await app.activeDocument.activeLayers
71
+ await psapi.unselectActiveLayersExe() // unselect all layer so the create group is place at the top of the document
72
+ this.prevOutputGroup = this.outputGroup
73
+ const outputGroup = await psapi.createEmptyGroup(session_name)
74
+ await executeAsModal(async () => {
75
+ outputGroup.allLocked = true //lock the session folder so that it can't move
76
+ })
77
+ this.outputGroup = outputGroup
78
+ await psapi.selectLayersExe(activeLayers)
79
+ } catch (e) {
80
+ console.warn(e)
81
+ }
82
+ }
83
+
84
+ async endSession(garbage_collection_state) {
85
+ try {
86
+ if (!this.isActive()) {
87
+ //return if the session is not active
88
+ return null
89
+ }
90
+ this.state = SessionState['Inactive'] // end the session by deactivate it
91
+
92
+ this.deactivate()
93
+
94
+ if (garbage_collection_state === GarbageCollectionState['Accept']) {
95
+ await acceptAll()
96
+ } else if (
97
+ garbage_collection_state === GarbageCollectionState['Discard']
98
+ ) {
99
+ //this should be discardAll()
100
+
101
+ await discardAll()
102
+ } else if (
103
+ garbage_collection_state ===
104
+ GarbageCollectionState['DiscardSelected']
105
+ ) {
106
+ //this should be discardAllExcept(selectedLayers)
107
+ await discardSelected() //this will discard what is not been highlighted
108
+ } else if (
109
+ garbage_collection_state ===
110
+ GarbageCollectionState['AcceptSelected']
111
+ ) {
112
+ //this should be discardAllExcept(selectedLayers)
113
+ await discard() //this will discard what is not been highlighted
114
+ }
115
+
116
+ //delete the old selection area
117
+ // g_generation_session.selectionInfo = {}
118
+
119
+ this.isFirstGeneration = true // only before the first generation is requested should this be true
120
+ // const is_visible = await this.outputGroup.visible
121
+ g_viewer_manager.last_selected_viewer_obj = null // TODO: move this in viewerManager endSession()
122
+ g_viewer_manager.onSessionEnd()
123
+ await layer_util.collapseFolderExe([this.outputGroup], false) // close the folder group
124
+ await executeAsModal(async () => {
125
+ this.outputGroup.allLocked = false //unlock the session folder on session end
126
+ })
127
+ // this.outputGroup.visible = is_visible
128
+
129
+ if (
130
+ this.mode === generationMode['Inpaint'] &&
131
+ g_sd_mode === generationMode['Inpaint']
132
+ ) {
133
+ //create "Mask -- Paint White to Mask -- temporary" layer if current session was inpiant and the selected session is inpaint
134
+ // the current inpaint session ended on inpaint
135
+ g_b_mask_layer_exist = false
136
+ await layer_util.deleteLayers([g_inpaint_mask_layer])
137
+ await createTempInpaintMaskLayer()
138
+ }
139
+ //delete controlNet image, Note: don't delete control net, let the user disable controlNet if doesn't want to use it
140
+ // this.controlNetImage = null
141
+ // html_manip.setControlImageSrc('https://source.unsplash.com/random')
142
+ } catch (e) {
143
+ console.warn(e)
144
+ }
145
+ }
146
+ // initializeInitImage(group, snapshot, solid_background, path) {
147
+ // this.initGroup = group
148
+ // this.init_solid_background = solid_background
149
+ // this.InitSnapshot = snapshot
150
+ // }
151
+ deleteInitImageLayers() {}
152
+ async closePreviousOutputGroup() {
153
+ try {
154
+ //close the previous output folder
155
+
156
+ if (this.prevOutputGroup) {
157
+ // const is_visible = await this.prevOutputGroup.visible
158
+ await layer_util.collapseFolderExe(
159
+ [this.prevOutputGroup],
160
+ false
161
+ ) // close the folder group
162
+ // and reselect the current output folder for clarity
163
+ await psapi.selectLayersExe([this.outputGroup])
164
+ // this.prevOutputGroup.visible = is_visible
165
+ }
166
+ } catch (e) {
167
+ console.warn(e)
168
+ }
169
+ }
170
+ isSameMode(selected_mode) {
171
+ if (this.mode === selected_mode) {
172
+ return true
173
+ }
174
+ return false
175
+ }
176
+ loadLastSession() {
177
+ //load the last session from the server
178
+ }
179
+ saveCurrentSession() {
180
+ //all session info will be saved in a json file in the project folder
181
+ }
182
+ async moveToTopOfOutputGroup(layer) {
183
+ const output_group_id = await this.outputGroup.id
184
+ let group_index = await psapi.getLayerIndex(output_group_id)
185
+ const indexOffset = 1 //1 for background, 0 if no background exist
186
+ await executeAsModal(async () => {
187
+ await psapi.selectLayersExe([layer]) //the move command is selection selection sensitive
188
+ await psapi.moveToGroupCommand(group_index - indexOffset, layer.id)
189
+ })
190
+ }
191
+
192
+ async deleteProgressLayer() {
193
+ try {
194
+ await layer_util.deleteLayers([this.progress_layer]) // delete the old progress layer
195
+ } catch (e) {
196
+ console.warn(e)
197
+ }
198
+ }
199
+ deleteProgressImageHtml() {
200
+ try {
201
+ // await layer_util.deleteLayers([this.progress_layer]) // delete the old progress layer
202
+ // document.getElementById('progressImage').style.width = '0px'
203
+ // document.getElementById('progressImage').style.height = '0px'
204
+
205
+ document.getElementById(
206
+ 'divProgressImageViewerContainer'
207
+ ).style.height = '0px'
208
+ } catch (e) {
209
+ console.warn(e)
210
+ }
211
+ }
212
+ async deleteProgressImage() {
213
+ this.deleteProgressImageHtml()
214
+ await this.deleteProgressLayer()
215
+ }
216
+ async setControlNetImageHelper() {
217
+ const width = html_manip.getWidth()
218
+ const height = html_manip.getHeight()
219
+
220
+ //get the selection from the canvas as base64 png, make sure to resize to the width and height slider
221
+ const selectionInfo = await psapi.getSelectionInfoExe()
222
+ this.control_net_selection_info = selectionInfo
223
+ this.control_net_preview_selection_info = selectionInfo
224
+ // const base64_image = await io.IO.getSelectionFromCanvasAsBase64Silent(
225
+ // selectionInfo,
226
+ // true,
227
+ // width,
228
+ // height
229
+ // )
230
+
231
+ const use_silent_mode = html_manip.getUseSilentMode()
232
+ let layer = null
233
+ if (!use_silent_mode) {
234
+ await psapi.snapshot_layerExe()
235
+ const snapshotLayer = await app.activeDocument.activeLayers[0]
236
+ layer = snapshotLayer
237
+ }
238
+ const base64_image =
239
+ await io.IO.getSelectionFromCanvasAsBase64Interface(
240
+ width,
241
+ height,
242
+ layer,
243
+ selectionInfo,
244
+ true,
245
+ use_silent_mode
246
+ )
247
+
248
+ await layer_util.deleteLayers([layer]) //delete the snapshot layer if it exists
249
+ return base64_image
250
+ }
251
+ async setControlNetImage(control_net_index = 0, base64_image) {
252
+ //check if the selection area is active
253
+ //convert layer to base64
254
+ //the width and height of the exported image
255
+ // const base64_image = this.setControlNetImageHelper()
256
+ this.controlNetImage[control_net_index] = base64_image
257
+ html_manip.setControlImageSrc(
258
+ base64ToBase64Url(base64_image),
259
+ control_net_index
260
+ )
261
+ // console.log('base64_img:', base64_image)
262
+ // await io.IO.base64ToLayer(base64_image)
263
+ }
264
+ }
265
+
266
+ module.exports = {
267
+ GenerationSession,
268
+ GarbageCollectionState,
269
+ SessionState,
270
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/tab/control_net.js ADDED
@@ -0,0 +1,1144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const api = require('../api')
2
+ const html_manip = require('../html_manip')
3
+ const selection = require('../../selection')
4
+ const note = require('../notification')
5
+ const controlnet_preset = require('../presets/controlnet_preset')
6
+ const preset = require('../presets/preset')
7
+ const Enum = require('../../enum')
8
+ const event = require('../event')
9
+
10
+ // const g_controlnet_max_supported_models = 3
11
+ let g_controlnet_presets
12
+ let g_module_detail
13
+
14
+ class ControlNetUnit {
15
+ static {}
16
+
17
+ static resetUnit(index) {
18
+ const controlnet_unit_default = {
19
+ module: null,
20
+ model: null,
21
+ weight: 1.0,
22
+ resize_mode: null,
23
+ lowvram: null,
24
+ processor_res: null,
25
+ threshold_a: null,
26
+ threshold_b: null,
27
+
28
+ guidance_start: 0,
29
+ guidance_end: 1,
30
+ guessmode: null,
31
+ }
32
+ this.setUnit(index, controlnet_unit_default)
33
+ }
34
+
35
+ static resetUnits() {
36
+ for (let i = 0; i < g_controlnet_max_models; ++i) {
37
+ this.resetUnit(i)
38
+ }
39
+ }
40
+ static getUnit(index) {
41
+ const controlnet_unit = {
42
+ module: this.getModule(index),
43
+ model: this.getModel(index),
44
+ weight: this.getWeight(index),
45
+ resize_mode: null,
46
+ lowvram: null,
47
+ processor_res: null,
48
+ threshold_a: null,
49
+ threshold_b: null,
50
+
51
+ guidance_start: this.getGuidanceStrengthStart(index),
52
+ guidance_end: this.getGuidanceStrengthEnd(index),
53
+ guessmode: null,
54
+ }
55
+ return controlnet_unit
56
+ }
57
+ static setUnit(index, unit_settings) {
58
+ const controlnet_unit_setters = {
59
+ module: this.setModule,
60
+ model: this.setModel,
61
+ weight: this.setWeight,
62
+ resize_mode: null,
63
+ lowvram: null,
64
+ processor_res: null,
65
+ threshold_a: null,
66
+ threshold_b: null,
67
+
68
+ guidance_start: this.setGuidanceStrengthStart,
69
+ guidance_end: this.setGuidanceStrengthEnd,
70
+ guessmode: null,
71
+ }
72
+
73
+ for (const [name, value] of Object.entries(unit_settings)) {
74
+ try {
75
+ if (
76
+ controlnet_unit_setters.hasOwnProperty(name)
77
+ //&& value.toString() // check if it has a value, null return error; undefine return error; 0 pass
78
+ ) {
79
+ // if (value) {
80
+ const setter = controlnet_unit_setters[name]
81
+
82
+ setter(index, value)
83
+ // }
84
+ }
85
+ } catch (e) {
86
+ console.warn(e)
87
+ }
88
+ }
89
+ }
90
+ static getUnits() {
91
+ const controlnet_units = {}
92
+
93
+ for (let i = 0; i < g_controlnet_max_models; ++i) {
94
+ controlnet_units[i] = this.getUnit(i)
95
+ }
96
+ return controlnet_units
97
+ }
98
+ static setUnits(controlnet_units) {
99
+ for (const [index, unit] of Object.entries(controlnet_units)) {
100
+ try {
101
+ this.setUnit(index, unit)
102
+ } catch (e) {
103
+ console.warn(e)
104
+ }
105
+ }
106
+ }
107
+ static doesUnitExist(index) {
108
+ //TODO: check if controlnet unit exist
109
+ if (index >= 0) {
110
+ }
111
+ }
112
+
113
+ static getModule(index) {
114
+ const module = getSelectedModule(index)
115
+ return module
116
+ }
117
+ static setModule(index, module_item) {
118
+ try {
119
+ const module_menu_element = controlnetElement(
120
+ index,
121
+ '.mModulesMenuControlNet_'
122
+ )
123
+
124
+ html_manip.selectMenuItemByElement(module_menu_element, module_item)
125
+ // module_menu_element.dispatchEvent(new Event('click'))
126
+ // module_menu_element.click()
127
+ changeModule(module_item, index)
128
+ } catch (e) {
129
+ html_manip.unselectMenuItemByElement(module_menu_element)
130
+ console.warn(e)
131
+ }
132
+ }
133
+ static getModel(index) {
134
+ const model = getSelectedModel(index)
135
+ return model
136
+ }
137
+ static setModel(index, model_item) {
138
+ try {
139
+ const model_menu_element = controlnetElement(
140
+ index,
141
+ '.mModelsMenuControlNet_'
142
+ )
143
+ html_manip.selectMenuItemByElement(model_menu_element, model_item)
144
+ } catch (e) {
145
+ html_manip.unselectMenuItemByElement(model_menu_element)
146
+ console.warn(e)
147
+ }
148
+ }
149
+ static getWeight(index = 0) {
150
+ const weight = getWeight(index)
151
+ return weight
152
+ }
153
+ static setWeight(index, weight) {
154
+ setWeight(index, weight)
155
+ }
156
+ static getGuidanceStrengthStart(index) {
157
+ const guidance_strength = getControlNetGuidanceStrengthStart(index)
158
+ return guidance_strength
159
+ }
160
+
161
+ static setGuidanceStrengthStart(index, sd_value) {
162
+ setControlNetGuidanceStrengthStart(index, sd_value)
163
+ }
164
+ static getGuidanceStrengthEnd(index) {
165
+ const guidance_strength = getControlNetGuidanceStrengthEnd(index)
166
+ return guidance_strength
167
+ }
168
+ static setGuidanceStrengthEnd(index, sd_value) {
169
+ setControlNetGuidanceStrengthEnd(index, sd_value)
170
+ }
171
+ static getProcessorRes(index) {
172
+ const slider = controlnetElement(index, '.slControlNetProcessorRes_')
173
+
174
+ return slider.value
175
+ }
176
+ static setProcessorRes(index, sd_value) {
177
+ const slider = controlnetElement(index, '.slControlNetProcessorRes_')
178
+ slider.value = sd_value
179
+ }
180
+ static getThreshold(index, a_or_b) {
181
+ let slider
182
+ if (a_or_b === 'a') {
183
+ slider = controlnetElement(index, '.slControlNetThreshold_A_')
184
+ } else if (a_or_b === 'b') {
185
+ slider = controlnetElement(index, '.slControlNetThreshold_B_')
186
+ }
187
+
188
+ const sd_value = general.mapRange(
189
+ slider.value,
190
+ slider.min,
191
+ slider.max,
192
+ slider.dataset['sd_min'],
193
+ slider.dataset['sd_max']
194
+ )
195
+ return sd_value
196
+ }
197
+ static setThreshold(index, a_or_b, sd_value) {
198
+ let slider
199
+ let label
200
+ if (a_or_b === 'a') {
201
+ slider = controlnetElement(index, '.slControlNetThreshold_A_')
202
+ label = controlnetElement(index, '.lControlNetThreshold_A_')
203
+ } else if (a_or_b === 'b') {
204
+ slider = controlnetElement(index, '.slControlNetThreshold_B_')
205
+ label = controlnetElement(index, '.lControlNetThreshold_B_')
206
+ }
207
+
208
+ const slider_value = general.mapRange(
209
+ sd_value,
210
+ slider.dataset['sd_min'],
211
+ slider.dataset['sd_max'],
212
+ slider.min,
213
+ slider.max
214
+ )
215
+ slider.value = String(slider_value)
216
+ label.innerText = String(sd_value)
217
+ }
218
+
219
+ static getControlNetUnitJson(index = 0) {}
220
+ }
221
+
222
+ async function checkIfControlNetInstalled() {}
223
+
224
+ async function requestControlNetDetectMap(
225
+ controlnet_init_image,
226
+ _module,
227
+ processor_res,
228
+ threshold_a,
229
+ threshold_b
230
+ ) {
231
+ try {
232
+ const payload = {
233
+ controlnet_module: _module,
234
+ controlnet_input_images: [controlnet_init_image],
235
+ controlnet_processor_res: processor_res,
236
+ controlnet_threshold_a: threshold_a,
237
+ controlnet_threshold_b: threshold_b,
238
+ }
239
+ const full_url = `${g_sd_url}/controlnet/detect`
240
+
241
+ // debugger
242
+
243
+ const response_data = await api.requestPost(full_url, payload)
244
+
245
+ // update the mask preview with the new detectMap
246
+ if (response_data['images'].length === 0) {
247
+ app.showAlert(response_data['info'])
248
+ }
249
+ return response_data['images'][0]
250
+ } catch (e) {
251
+ console.warn('requestControlNetDetectMap(): ', _module, e)
252
+ }
253
+ }
254
+
255
+ async function requestControlNetModelList() {
256
+ const control_net_json = await api.requestGet(
257
+ `${g_sd_url}/controlnet/model_list`
258
+ )
259
+
260
+ const model_list = control_net_json?.model_list
261
+
262
+ // const model_list = [
263
+ // 'none',
264
+ // 'control_sd15_depth [fef5e48e]',
265
+ // 'control_sd15_openpose [fef5e48e]',
266
+ // 'control_sd15_scribble [fef5e48e]',
267
+ // ]
268
+ return model_list
269
+ }
270
+
271
+ async function requestControlNetModuleList() {
272
+ // const control_net_json = await api.requestGet(
273
+ // `${g_sd_url}/controlnet/model_list`
274
+ // )
275
+ // const module_list = [
276
+ // // 'none',
277
+ // 'canny',
278
+ // 'depth',
279
+ // 'depth_leres',
280
+ // 'hed',
281
+ // 'mlsd',
282
+ // 'normal_map',
283
+ // 'openpose',
284
+ // // "openpose_hand",
285
+ // 'pidinet',
286
+ // 'scribble',
287
+ // 'fake_scribble',
288
+ // 'segmentation',
289
+ // ]
290
+
291
+ // const module_list = g_sd_config_obj.getControlNetPreprocessors()
292
+
293
+ const result = await api.requestGet(
294
+ `${g_sd_url}/controlnet/module_list?alias_names=1`
295
+ )
296
+
297
+ return result?.module_list
298
+ }
299
+ async function populateModelMenu() {
300
+ try {
301
+ const models = await requestControlNetModelList()
302
+ for (let index = 0; index < g_controlnet_max_models; index++) {
303
+ const menu_element = controlnetElement(
304
+ index,
305
+ '.mModelsMenuControlNet_'
306
+ )
307
+ html_manip.populateMenuByElement(
308
+ menu_element,
309
+ 'mModelsMenuItemControlNet_',
310
+ models,
311
+ (item, item_html_element) => {
312
+ item_html_element.innerHTML = item
313
+ item_html_element.dataset['index'] = index
314
+ },
315
+ false,
316
+ 'Select Model'
317
+ )
318
+ }
319
+ } catch (e) {
320
+ console.warn(e)
321
+ }
322
+ }
323
+
324
+ function changeModule(_module, index) {
325
+ // const index = index
326
+
327
+ const preprocessor_res_element = controlnetElement(
328
+ index,
329
+ '.slControlNetProcessorRes_'
330
+ )
331
+
332
+ const threshold_a_element = controlnetElement(
333
+ index,
334
+ '.slControlNetThreshold_A_'
335
+ )
336
+ const threshold_b_element = controlnetElement(
337
+ index,
338
+ '.slControlNetThreshold_B_'
339
+ )
340
+
341
+ const detail = g_module_detail[_module]
342
+
343
+ function remapArray(arr) {
344
+ let obj = {
345
+ preprocessor_res: arr[0] || null,
346
+ threshold_a: arr[1] || null,
347
+ threshold_b: arr[2] || null,
348
+ }
349
+ return obj
350
+ }
351
+ const params = remapArray(detail['sliders'])
352
+ const model_free = detail.model_free
353
+
354
+ // threshold_a_element.min = prams.
355
+ // threshold_a_element.max =
356
+ // debugger
357
+ if (model_free)
358
+ controlnetElement(
359
+ index,
360
+ '.mModelsMenuControlNet_'
361
+ ).parentElement.style.display = 'none'
362
+
363
+ else
364
+ controlnetElement(
365
+ index,
366
+ '.mModelsMenuControlNet_'
367
+ ).parentElement.style.display = 'block'
368
+
369
+
370
+ if (params?.preprocessor_res) {
371
+ const preprocessor_res_label_element = controlnetElement(
372
+ index,
373
+ '.labelControlNetProcessorRes_'
374
+ )
375
+ preprocessor_res_element.style.display = 'block'
376
+ preprocessor_res_label_element.innerText = params.preprocessor_res.name
377
+ } else {
378
+ preprocessor_res_element.style.display = 'none'
379
+ }
380
+ if (params?.threshold_a) {
381
+ const threshold_a_label_element = controlnetElement(
382
+ index,
383
+ '.labelControlNetThreshold_A_'
384
+ )
385
+
386
+ threshold_a_element.dataset['sd_min'] = params.threshold_a.min
387
+ threshold_a_element.dataset['sd_max'] = params.threshold_a.max
388
+ ControlNetUnit.setThreshold(index, 'a', params.threshold_a.value)
389
+ threshold_a_element.style.display = 'block'
390
+ threshold_a_label_element.innerText = params.threshold_a.name + ":"
391
+ } else {
392
+ ControlNetUnit.setThreshold(index, 'a', 32)
393
+ threshold_a_element.style.display = 'none'
394
+ }
395
+
396
+ if (params?.threshold_b) {
397
+ const threshold_b_label_element = controlnetElement(
398
+ index,
399
+ '.labelControlNetThreshold_B_'
400
+ )
401
+
402
+ threshold_b_element.dataset['sd_min'] = params.threshold_b.min
403
+ threshold_b_element.dataset['sd_max'] = params.threshold_b.max
404
+ ControlNetUnit.setThreshold(index, 'b', params.threshold_b.value)
405
+ threshold_b_element.style.display = 'block'
406
+ threshold_b_label_element.innerText = params.threshold_b.name + ":"
407
+ } else {
408
+ ControlNetUnit.setThreshold(index, 'b', 32)
409
+ threshold_b_element.style.display = 'none'
410
+ }
411
+ }
412
+ async function populatePreprocessorMenu() {
413
+ try {
414
+ // debugger
415
+ const modules = await requestControlNetModuleList()
416
+ for (let index = 0; index < g_controlnet_max_models; index++) {
417
+ const menu_element = controlnetElement(
418
+ index,
419
+ '.mModulesMenuControlNet_'
420
+ )
421
+
422
+ menu_element.dataset['index'] = String(index)
423
+ html_manip.populateMenuByElement(
424
+ menu_element,
425
+ 'mModuleMenuItemControlNet_',
426
+ modules,
427
+ (item, item_html_element) => {
428
+ item_html_element.innerHTML = item
429
+ item_html_element.dataset['index'] = index
430
+ },
431
+ false,
432
+ 'Select Module'
433
+ )
434
+
435
+ menu_element.addEventListener('click', (event) => {
436
+ changeModule(
437
+ event.target.innerText,
438
+ event.target.dataset['index']
439
+ )
440
+ })
441
+ }
442
+ } catch (e) {
443
+ console.warn(e)
444
+ }
445
+ }
446
+ function controlnetElement(index, class_) {
447
+ const element = document.querySelector(
448
+ `#controlnet_settings_${index} ${class_}`
449
+ )
450
+ return element
451
+ }
452
+ function getControlNetGuidanceStrengthStart(index) {
453
+ const slider_element = document.querySelector(
454
+ `#controlnet_settings_${index} .slControlNetGuidanceStrengthStart_`
455
+ )
456
+ const sd_value = html_manip.getSliderSdValueByElement(
457
+ slider_element,
458
+ 0,
459
+ 100,
460
+ 0,
461
+ 1
462
+ )
463
+ return sd_value
464
+ }
465
+ function setControlNetGuidanceStrengthStart(index, sd_value) {
466
+ const slider_element = controlnetElement(
467
+ index,
468
+ '.slControlNetGuidanceStrengthStart_'
469
+ )
470
+
471
+ const label_element = controlnetElement(
472
+ index,
473
+ '.lControlNetGuidanceStrengthStart_'
474
+ )
475
+ html_manip.setSliderSdValueByElements(
476
+ slider_element,
477
+ label_element,
478
+ sd_value,
479
+ 0,
480
+ 100,
481
+ 0,
482
+ 1
483
+ )
484
+ }
485
+
486
+ function getControlNetGuidanceStrengthEnd(index) {
487
+ const slider_element = controlnetElement(
488
+ index,
489
+ '.slControlNetGuidanceStrengthEnd_'
490
+ )
491
+ const sd_value = html_manip.getSliderSdValueByElement(
492
+ slider_element,
493
+ 0,
494
+ 100,
495
+ 0,
496
+ 1
497
+ )
498
+ return sd_value
499
+ }
500
+ function setControlNetGuidanceStrengthEnd(index, sd_value) {
501
+ const slider_element = controlnetElement(
502
+ index,
503
+ '.slControlNetGuidanceStrengthEnd_'
504
+ )
505
+
506
+ const label_element = controlnetElement(
507
+ index,
508
+ '.lControlNetGuidanceStrengthEnd_'
509
+ )
510
+ html_manip.setSliderSdValueByElements(
511
+ slider_element,
512
+ label_element,
513
+ sd_value,
514
+ 0,
515
+ 100,
516
+ 0,
517
+ 1
518
+ )
519
+ }
520
+
521
+ // controlnet settings getters
522
+ function getControlNetWeightGuidanceStrengthStart(index = 0) {
523
+ const slider_element = controlnetElement(
524
+ index,
525
+ '.slControlNetGuidanceStrengthStart_'
526
+ )
527
+ const slider_value = slider_element.value
528
+ const sd_value = general.mapRange(slider_value, 0, 100, 0, 1) // convert slider value to SD ready value
529
+ return sd_value
530
+ }
531
+
532
+ function getControlNetWeightGuidanceStrengthEnd(index = 0) {
533
+ const slider_value = controlnetElement(
534
+ index,
535
+ '.slControlNetGuidanceStrengthEnd_'
536
+ ).value
537
+
538
+ const sd_value = general.mapRange(slider_value, 0, 100, 0, 1) // convert slider value to SD ready value
539
+ return sd_value
540
+ }
541
+
542
+ function getWeight(index = 0) {
543
+ const slider_value = document.querySelector(
544
+ `#controlnet_settings_${index} .slControlNetWeight_`
545
+ ).value
546
+
547
+ const sd_value = general.mapRange(slider_value, 0, 100, 0, 2) // convert slider value to SD ready value
548
+ return sd_value
549
+ }
550
+ function setWeight(index = 0, sd_weight) {
551
+ const slider_element = document.querySelector(
552
+ `#controlnet_settings_${index} .slControlNetWeight_`
553
+ )
554
+ const label_element = document.querySelector(
555
+ `#controlnet_settings_${index} .lControlNetWeight_`
556
+ )
557
+
558
+ html_manip.setSliderSdValueByElements(
559
+ slider_element,
560
+ label_element,
561
+ sd_weight,
562
+ 0,
563
+ 100,
564
+ 0,
565
+ 2
566
+ )
567
+ }
568
+ function getUseLowVram(index = 0) {
569
+ const b_result = document.querySelector(
570
+ `#controlnet_settings_${index} .chlowVram_`
571
+ ).checked
572
+ return b_result
573
+ }
574
+ function getEnableControlNet(index = 0) {
575
+ const is_enable = document.querySelector(
576
+ `#controlnet_settings_${index} .chEnableControlNet_`
577
+ ).checked
578
+ return is_enable
579
+ }
580
+ function setEnable(index) {
581
+ document.querySelector(
582
+ `#controlnet_settings_${index} .chEnableControlNet_`
583
+ ).checked = b_live_update
584
+ }
585
+ function getSelectedModule(index = 0) {
586
+ const menu_element = controlnetElement(index, '.mModulesMenuControlNet_')
587
+ // debugger
588
+ const module_name =
589
+ html_manip.getSelectedMenuItemTextContentByElement(menu_element)
590
+
591
+ return module_name
592
+ }
593
+ function getSelectedModel(index = 0) {
594
+ const menu_element = controlnetElement(index, '.mModelsMenuControlNet_')
595
+ const model_name =
596
+ html_manip.getSelectedMenuItemTextContentByElement(menu_element)
597
+ return model_name
598
+ }
599
+ function getUseGuessMode(index = 0) {
600
+ const is_guess_mode = document.querySelector(
601
+ `#controlnet_settings_${index} .chGuessMode_`
602
+ ).checked
603
+
604
+ return is_guess_mode
605
+ }
606
+ function isControlNetModeEnable() {
607
+ let is_tab_enabled = !document.getElementById('chDisableControlNetTab')
608
+ .checked
609
+
610
+ let numOfEnabled = 0
611
+ if (g_controlnet_max_models <= 0) {
612
+ return false
613
+ }
614
+
615
+ if (is_tab_enabled) {
616
+ for (let index = 0; index < g_controlnet_max_models; index++) {
617
+ if (getEnableControlNet(index)) {
618
+ numOfEnabled += 1
619
+ }
620
+ }
621
+ }
622
+ let is_mode_enabled = is_tab_enabled // could be true
623
+ if (is_tab_enabled === false || numOfEnabled === 0) {
624
+ is_mode_enabled = false
625
+ }
626
+ return is_mode_enabled
627
+ }
628
+ // function getControlNetMaxModelsNumber() {
629
+ // return g_controlnet_max_supported_models
630
+ // }
631
+
632
+ function preprocessorData(
633
+ processor_res = 512,
634
+ threshold_a = 64,
635
+ threshold_b = 64,
636
+ a_min = 64,
637
+ a_max = 1024,
638
+ b_min = 64,
639
+ b_max = 1024,
640
+ processor_res_label = 'Annotator resolution',
641
+ threshold_a_label = 'threshold a',
642
+ threshold_b_label = 'threshold b'
643
+ ) {
644
+ return {
645
+ processor_res,
646
+ threshold_a,
647
+ threshold_b,
648
+ a_min,
649
+ a_max,
650
+ b_min,
651
+ b_max,
652
+ processor_res_label,
653
+ threshold_a_label,
654
+ threshold_b_label,
655
+ }
656
+ }
657
+
658
+ function mapPluginSettingsToControlNet(plugin_settings) {
659
+ const ps = plugin_settings // for shortness
660
+ let controlnet_units = []
661
+
662
+ // debugger
663
+ let active_index = 0
664
+ for (let index = 0; index < g_controlnet_max_models; index++) {
665
+ const preprocessor_name = getSelectedModule(index)
666
+
667
+ controlnet_units[active_index] = {
668
+ enabled: getEnableControlNet(index),
669
+ input_image: g_generation_session.controlNetImage[index],
670
+ mask: '',
671
+ module: getSelectedModule(index),
672
+ model: getSelectedModel(index),
673
+ weight: getWeight(index),
674
+ resize_mode: 'Scale to Fit (Inner Fit)',
675
+ lowvram: getUseLowVram(index),
676
+ processor_res: ControlNetUnit.getProcessorRes(index),
677
+ threshold_a: ControlNetUnit.getThreshold(index, 'a'),
678
+ threshold_b: ControlNetUnit.getThreshold(index, 'b'),
679
+ // guidance: ,
680
+ guidance_start: getControlNetWeightGuidanceStrengthStart(index),
681
+ guidance_end: getControlNetWeightGuidanceStrengthEnd(index),
682
+ guessmode: false,
683
+ }
684
+ active_index++
685
+ }
686
+
687
+ if (
688
+ plugin_settings['mode'] === Enum.generationModeEnum['Img2Img'] ||
689
+ plugin_settings['mode'] === Enum.generationModeEnum['Inpaint'] ||
690
+ plugin_settings['mode'] === Enum.generationModeEnum['Outpaint']
691
+ ) {
692
+ const b_use_guess_mode = getUseGuessMode()
693
+ controlnet_units[0]['guessmode'] = b_use_guess_mode
694
+ }
695
+
696
+ const controlnet_payload = {
697
+ ...ps,
698
+ controlnet_units, //keep for backward compatibility for now
699
+ subseed: -1,
700
+ override_settings: {},
701
+ override_settings_restore_afterwards: true,
702
+ alwayson_scripts: {
703
+ controlnet: {
704
+ args: controlnet_units,
705
+ },
706
+ },
707
+ }
708
+
709
+ return controlnet_payload
710
+ }
711
+ function refreshControlNetTab() {}
712
+
713
+ async function populateControlNetPresetMenu() {
714
+ // const default_preset_name = 'Select CtrlNet Preset'
715
+ // const default_preset_settings = {} // empty preset
716
+
717
+ const custom_presets = await preset.getAllCustomPresetsSettings(
718
+ Enum.PresetTypeEnum['ControlNetPreset']
719
+ )
720
+ g_controlnet_presets = {
721
+ 'Select CtrlNet Preset': {},
722
+ ...controlnet_preset.ControlNetNativePresets,
723
+ ...custom_presets,
724
+ }
725
+
726
+ const presets_names = Object.keys(g_controlnet_presets)
727
+
728
+ html_manip.populateMenu(
729
+ 'mControlNetPresetMenu',
730
+ 'mControlNetPresetMenuItem',
731
+ presets_names,
732
+ (preset_name, item_html_element) => {
733
+ item_html_element.innerHTML = preset_name
734
+ }
735
+ )
736
+ html_manip.selectMenuItem('mControlNetPresetMenu', 'Select CtrlNet Preset')
737
+ }
738
+
739
+ document
740
+ .getElementById('mControlNetPresetMenu')
741
+ .addEventListener('change', async (evt) => {
742
+ try {
743
+ ControlNetUnit.resetUnits()
744
+ const preset_index = evt.target.selectedIndex
745
+ const preset_name = evt.target.options[preset_index].textContent
746
+ units_settings = g_controlnet_presets[preset_name]
747
+
748
+ ControlNetUnit.setUnits(units_settings)
749
+ } catch (e) {
750
+ console.warn(e)
751
+ }
752
+ })
753
+
754
+ document
755
+ .getElementById('bSetAllControlImage')
756
+ .addEventListener('click', async () => {
757
+ const selectionInfo = await selection.Selection.getSelectionInfoExe()
758
+ if (selectionInfo) {
759
+ const base64_image =
760
+ await g_generation_session.setControlNetImageHelper()
761
+
762
+ for (index = 0; index < g_controlnet_max_models; index++) {
763
+ await g_generation_session.setControlNetImage(
764
+ index,
765
+ base64_image
766
+ )
767
+ }
768
+ } else {
769
+ await note.Notification.inactiveSelectionArea()
770
+ }
771
+ })
772
+
773
+ function initControlNetUnitsEventListeners(controlnet_max_models) {
774
+ for (let index = 0; index < controlnet_max_models; index++) {
775
+ //event listeners
776
+ controlnetElement(
777
+ index,
778
+ '.slControlNetGuidanceStrengthStart_'
779
+ ).addEventListener('input', (evt) => {
780
+ // debugger
781
+ const sd_value = general.mapRange(evt.target.value, 0, 100, 0, 1) // convert slider value to SD ready value
782
+ controlnetElement(
783
+ index,
784
+ '.lControlNetGuidanceStrengthStart_'
785
+ ).textContent = Number(sd_value).toFixed(2)
786
+ })
787
+
788
+ controlnetElement(
789
+ index,
790
+ '.slControlNetGuidanceStrengthEnd_'
791
+ ).addEventListener('input', (evt) => {
792
+ // debugger
793
+ const sd_value = general.mapRange(evt.target.value, 0, 100, 0, 1) // convert slider value to SD ready value
794
+ controlnetElement(
795
+ index,
796
+ '.lControlNetGuidanceStrengthEnd_'
797
+ ).textContent = Number(sd_value).toFixed(2)
798
+ })
799
+
800
+ document
801
+ .querySelector(`#controlnet_settings_${index} .slControlNetWeight_`)
802
+ .addEventListener('input', (evt) => {
803
+ // debugger
804
+ const sd_value = general.mapRange(
805
+ evt.target.value,
806
+ 0,
807
+ 100,
808
+ 0,
809
+ 2
810
+ ) // convert slider value to SD ready value
811
+ document.querySelector(
812
+ `#controlnet_settings_${index} .lControlNetWeight_`
813
+ ).textContent = Number(sd_value).toFixed(2)
814
+ })
815
+
816
+ // controlnetElement(index, '.slControlNetProcessorRes_').addEventListener(
817
+ // 'input',
818
+ // (evt) => {
819
+ // // debugger
820
+ // const sd_value = general.mapRange(
821
+ // evt.target.value,
822
+ // 64,
823
+ // 2048,
824
+ // 64,
825
+ // 2048
826
+ // ) // convert slider value to SD ready value
827
+ // controlnetElement(index, '.lControlNetProcessorRes_').textContent =
828
+ // Number(sd_value).toFixed(2)
829
+ // }
830
+ // )
831
+ controlnetElement(index, '.slControlNetThreshold_A_').addEventListener(
832
+ 'input',
833
+ (event) => {
834
+ const slider = event.target
835
+ const sd_value = Number(
836
+ general
837
+ .mapRange(
838
+ slider.value,
839
+ slider.min,
840
+ slider.max,
841
+ slider.dataset['sd_min'],
842
+ slider.dataset['sd_max']
843
+ )
844
+ .toFixed(2)
845
+ )
846
+ controlnetElement(index, '.lControlNetThreshold_A_').innerText =
847
+ String(sd_value)
848
+ // ControlNetUnit.setThreshold(index, 'a', sd_value)
849
+ // controlnetElement(index, '.lControlNetThreshold_A_').value =
850
+ // sd_value
851
+ }
852
+ )
853
+ controlnetElement(index, '.slControlNetThreshold_B_').addEventListener(
854
+ 'input',
855
+ (event) => {
856
+ const slider = event.target
857
+ const sd_value = Number(
858
+ general
859
+ .mapRange(
860
+ slider.value,
861
+ slider.min,
862
+ slider.max,
863
+ slider.dataset['sd_min'],
864
+ slider.dataset['sd_max']
865
+ )
866
+ .toFixed(2)
867
+ )
868
+ controlnetElement(index, '.lControlNetThreshold_B_').innerText =
869
+ String(sd_value)
870
+ // ControlNetUnit.setThreshold(index, 'a', sd_value)
871
+ // controlnetElement(index, '.lControlNetThreshold_A_').value =
872
+ // sd_value
873
+ }
874
+ )
875
+ document
876
+ .querySelector(`#controlnet_settings_${index} .bSetControlImage_`)
877
+ .addEventListener('click', async () => {
878
+ const selectionInfo =
879
+ await selection.Selection.getSelectionInfoExe()
880
+ if (selectionInfo) {
881
+ // debugger
882
+ const base64_image =
883
+ await g_generation_session.setControlNetImageHelper()
884
+ await g_generation_session.setControlNetImage(
885
+ index,
886
+ base64_image
887
+ )
888
+ } else {
889
+ await note.Notification.inactiveSelectionArea()
890
+ }
891
+ })
892
+
893
+ document
894
+ .querySelector(`#controlnet_settings_${index} .bControlMask_`)
895
+ .addEventListener('click', async () => {
896
+ await previewAnnotator(index)
897
+ })
898
+ }
899
+ }
900
+ document
901
+ .getElementById('mControlNetPresetMenu')
902
+ .addEventListener('updatePresetMenuEvent', async (event) => {
903
+ // console.log("I'm listening on a custom event")
904
+ await populateControlNetPresetMenu()
905
+ })
906
+
907
+ async function previewAnnotator(index) {
908
+ try {
909
+ const controlnet_init_image =
910
+ g_generation_session.controlNetImage[index]
911
+ const _module = ControlNetUnit.getModule(index)
912
+ const processor_res = ControlNetUnit.getProcessorRes(index)
913
+ const threshold_a = ControlNetUnit.getThreshold(index, 'a')
914
+ const threshold_b = ControlNetUnit.getThreshold(index, 'b')
915
+ if (!controlnet_init_image) {
916
+ const error = 'ControlNet initial image is empty'
917
+ app.showAlert(error)
918
+ throw error
919
+ }
920
+ if (!_module || _module === 'none') {
921
+ const error = 'select a valid controlnet module (preprocessor)'
922
+ app.showAlert(error)
923
+ throw error
924
+ }
925
+
926
+ const detect_map = await requestControlNetDetectMap(
927
+ controlnet_init_image,
928
+ _module,
929
+ processor_res,
930
+ threshold_a,
931
+ threshold_b
932
+ )
933
+
934
+ const rgb_detect_map_url =
935
+ await io.convertBlackAndWhiteImageToRGBChannels3(detect_map)
936
+ const rgb_detect_map = general.base64UrlToBase64(rgb_detect_map_url)
937
+ g_generation_session.controlNetMask[index] = rgb_detect_map
938
+
939
+ html_manip.setControlMaskSrc(rgb_detect_map_url, index)
940
+ } catch (e) {
941
+ console.warn('PreviewAnnotator click(): index: ', index, e)
942
+ }
943
+ }
944
+ async function previewAnnotatorFromCanvas(index) {
945
+ try {
946
+ const _module = ControlNetUnit.getModule(index)
947
+
948
+ const width = html_manip.getWidth()
949
+ const height = html_manip.getHeight()
950
+ const selectionInfo = await psapi.getSelectionInfoExe()
951
+ g_generation_session.control_net_preview_selection_info = selectionInfo
952
+ const base64 = await io.IO.getSelectionFromCanvasAsBase64Interface_New(
953
+ width,
954
+ height,
955
+ selectionInfo,
956
+ true
957
+ )
958
+
959
+ if (!_module || _module === 'none') {
960
+ const error = 'select a valid controlnet module (preprocessor)'
961
+ app.showAlert(error)
962
+ throw error
963
+ }
964
+
965
+ const processor_res = ControlNetUnit.getProcessorRes(index)
966
+ const threshold_a = ControlNetUnit.getThreshold(index, 'a')
967
+ const threshold_b = ControlNetUnit.getThreshold(index, 'b')
968
+
969
+ const detect_map = await requestControlNetDetectMap(
970
+ base64,
971
+ _module,
972
+ processor_res,
973
+ threshold_a,
974
+ threshold_b
975
+ )
976
+
977
+ const rgb_detect_map_url =
978
+ await io.convertBlackAndWhiteImageToRGBChannels3(detect_map)
979
+ g_generation_session.controlNetMask[index] = detect_map
980
+ html_manip.setControlMaskSrc(rgb_detect_map_url, index)
981
+ } catch (e) {
982
+ console.warn('PreviewAnnotator click(): index: ', index, e)
983
+ }
984
+ }
985
+ // document
986
+ // .getElementById('bPreviewAnnotator_0')
987
+ // .addEventListener('click', async (event) => {
988
+ // const index = parseInt(event.target.dataset['controlnet-index'])
989
+ // await previewAnnotator(index)
990
+ // })
991
+
992
+ function initPreviewElement(index) {
993
+ //make init mask image use the thumbnail class with buttons
994
+ // const mask_image_html = document.getElementById(
995
+ // 'control_net_preview_image_0'
996
+ // )
997
+
998
+ const mask_image_html = controlnetElement(index, '.control_net_mask_')
999
+ const mask_parent_element = mask_image_html.parentElement
1000
+
1001
+ this.thumbnail_container = thumbnail.Thumbnail.wrapImgInContainer(
1002
+ mask_image_html,
1003
+ 'viewer-image-container'
1004
+ )
1005
+
1006
+ mask_parent_element.appendChild(thumbnail_container)
1007
+
1008
+ async function toCanvas(index) {
1009
+ // debugger
1010
+ if (
1011
+ g_generation_session.control_net_preview_selection_info &&
1012
+ g_generation_session.controlNetMask[index]
1013
+ ) {
1014
+ const selection_info =
1015
+ g_generation_session.control_net_preview_selection_info
1016
+ const layer = await io.IO.base64ToLayer(
1017
+ g_generation_session.controlNetMask[index],
1018
+ 'ControlNet Mask.png',
1019
+ selection_info.left,
1020
+ selection_info.top,
1021
+ selection_info.width,
1022
+ selection_info.height
1023
+ )
1024
+ } else {
1025
+ // await note.Notification.inactiveSelectionArea()
1026
+ app.showAlert('Mask Image is not available')
1027
+ }
1028
+ }
1029
+ async function toControlNetInitImage(index) {
1030
+ const preview_result_base64 = g_generation_session.controlNetMask[index]
1031
+ g_generation_session.controlNetImage[index] = preview_result_base64
1032
+ g_generation_session.control_net_selection_info =
1033
+ g_generation_session.control_net_preview_selection_info
1034
+
1035
+ const rgb_detect_map_url =
1036
+ await io.convertBlackAndWhiteImageToRGBChannels3(
1037
+ preview_result_base64
1038
+ )
1039
+
1040
+ // g_generation_session.controlNetMask[index] = rgb_detect_map
1041
+
1042
+ html_manip.setControlImageSrc(rgb_detect_map_url, index)
1043
+ }
1044
+ thumbnail.Thumbnail.addSPButtonToContainer(
1045
+ this.thumbnail_container,
1046
+ 'svg_sp_btn',
1047
+ 'use as ControlNet init image',
1048
+
1049
+ toControlNetInitImage,
1050
+ index
1051
+ )
1052
+ thumbnail.Thumbnail.addSPButtonToContainer(
1053
+ this.thumbnail_container,
1054
+ 'svg_sp_btn_canvas',
1055
+ 'move to the canvas',
1056
+
1057
+ toCanvas,
1058
+ index
1059
+ )
1060
+ thumbnail.Thumbnail.addSPButtonToContainer(
1061
+ this.thumbnail_container,
1062
+ 'svg_sp_btn_preview',
1063
+ 'preview selection from canvas',
1064
+
1065
+ previewAnnotatorFromCanvas,
1066
+ index
1067
+ )
1068
+ }
1069
+
1070
+ function initControlNetUnit(index) {
1071
+ document.querySelector(
1072
+ `#controlnet_settings_${index} .controlnet_unit_label_`
1073
+ ).textContent = `Control Net Settings Slot ${index}`
1074
+ }
1075
+ async function initializeControlNetTab(controlnet_max_models) {
1076
+ try {
1077
+ if (controlnet_max_models <= 0) {
1078
+ document.getElementById('controlnetMissingError').style.display =
1079
+ 'block'
1080
+
1081
+ return
1082
+ }
1083
+ document.getElementById('controlnetMissingError').style.display = 'none'
1084
+ // if (controlnet_max_models > g_controlnet_max_models)
1085
+ // controlnet_max_models = g_controlnet_max_models
1086
+
1087
+ await populateControlNetPresetMenu()
1088
+ const parent_container = document.getElementById(
1089
+ 'controlnet_parent_container'
1090
+ )
1091
+ parent_container.innerHTML = ''
1092
+ const controlnet_unit_template = document.getElementById(
1093
+ 'controlnet_settings_template'
1094
+ )
1095
+
1096
+ for (let index = 0; index < controlnet_max_models; index++) {
1097
+ const controlnet_unit = controlnet_unit_template.cloneNode(true)
1098
+ controlnet_unit.id = 'controlnet_settings_' + index
1099
+ controlnet_unit.dataset['index'] = String(index)
1100
+ parent_container.appendChild(controlnet_unit)
1101
+ }
1102
+
1103
+ const full_url = `${g_sd_url}/controlnet/module_list?alias_names=1`
1104
+ let result_json = await api.requestGet(full_url)
1105
+ g_module_detail = result_json['module_detail']
1106
+
1107
+ initControlNetUnitsEventListeners(controlnet_max_models) // add event listener to all units after cloning
1108
+
1109
+ for (let index = 0; index < controlnet_max_models; index++) {
1110
+ await populateModelMenu(index)
1111
+ await populatePreprocessorMenu(index)
1112
+ document.getElementById(
1113
+ 'controlnet_settings_' + index
1114
+ ).style.display = 'block'
1115
+ initPreviewElement(index)
1116
+
1117
+ initControlNetUnit(index)
1118
+ }
1119
+ } catch (e) {
1120
+ console.warn(e)
1121
+ }
1122
+ }
1123
+
1124
+ module.exports = {
1125
+ requestControlNetModelList,
1126
+ populateModelMenu,
1127
+ initializeControlNetTab,
1128
+ getWeight,
1129
+ mapPluginSettingsToControlNet,
1130
+ getEnableControlNet,
1131
+ getSelectedModule,
1132
+ getSelectedModel,
1133
+ // getControlNetMaxModelsNumber,
1134
+ getControlNetGuidanceStrengthStart,
1135
+ setControlNetGuidanceStrengthStart,
1136
+ getControlNetGuidanceStrengthEnd,
1137
+ setControlNetGuidanceStrengthEnd,
1138
+ ControlNetUnit,
1139
+ populateControlNetPresetMenu,
1140
+ isControlNetModeEnable,
1141
+ getModuleDetail() {
1142
+ return g_module_detail
1143
+ }
1144
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/tab/history_tab.js ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const settings_tab = require('./settings')
2
+ const sdapi = require('../../sdapi_py_re')
3
+ const thumbnail = require('../../thumbnail')
4
+
5
+ //REFACTORED: moved to history_tab.js
6
+ function getHistoryMetadata(img) {
7
+ //auto fill the ui with metadata
8
+ const metadata_json = JSON.parse(img.dataset.metadata_json_string)
9
+ console.log('metadata_json: ', metadata_json)
10
+ // document.querySelector('#tiSeed').value = metadata_json.Seed
11
+
12
+ //extract auto_metadata into the preset metadata
13
+ function convertAutoMetadataToPresset(metadata_json) {
14
+ metadata_json['seed'] = metadata_json?.auto_metadata?.Seed
15
+ }
16
+ convertAutoMetadataToPresset(metadata_json)
17
+
18
+ const b_use_original_prompt = settings_tab.getUseOriginalPrompt()
19
+ if (b_use_original_prompt) {
20
+ metadata_json['prompt'] = metadata_json?.original_prompt
21
+ ? metadata_json['original_prompt']
22
+ : metadata_json['prompt']
23
+
24
+ metadata_json['negative_prompt'] =
25
+ metadata_json?.original_negative_prompt
26
+ ? metadata_json['original_negative_prompt']
27
+ : metadata_json['negative_prompt']
28
+ }
29
+ document.querySelector('#historySeedLabel').textContent =
30
+ metadata_json?.seed
31
+
32
+ g_ui_settings_object.autoFillInSettings(metadata_json)
33
+ }
34
+
35
+ document
36
+ .getElementById('btnLoadHistory')
37
+ .addEventListener('click', async function () {
38
+ try {
39
+ const output_dir_relative = './server/python_server/'
40
+ const container = document.getElementById(
41
+ 'divHistoryImagesContainer'
42
+ )
43
+ const uniqueDocumentId = await getUniqueDocumentId()
44
+ const [image_paths, metadata_jsons, base64_images] =
45
+ await sdapi.loadHistory(uniqueDocumentId)
46
+
47
+ while (container.firstChild) {
48
+ container.removeChild(container.firstChild)
49
+ }
50
+
51
+ const length = image_paths.length
52
+ // let i = length -1
53
+
54
+ // for (image_path of image_paths) {
55
+ for (let i = length - 1; i >= 0; --i) {
56
+ const img = document.createElement('img')
57
+ // img.src = `${output_dir_relative}/${image_path}`
58
+ const image_src = `data:image/png;base64, ${base64_images[i]}`
59
+ img.src = image_src
60
+
61
+ img.dataset.path = `${output_dir_relative}/${image_paths[i]}`
62
+ img.className = 'history-image'
63
+ img.dataset.metadata_json_string = JSON.stringify(
64
+ metadata_jsons[i]
65
+ )
66
+ console.log(`metadata_jsons[${i}]: `, metadata_jsons[i])
67
+
68
+ const img_container = thumbnail.Thumbnail.wrapImgInContainer(
69
+ img,
70
+ 'viewer-image-container'
71
+ )
72
+ thumbnail.Thumbnail.addSPButtonToContainer(
73
+ img_container,
74
+ 'svg_sp_btn',
75
+ 'copy metadata to settings',
76
+ history_tab.getHistoryMetadata,
77
+ img
78
+ )
79
+ thumbnail.Thumbnail.addSPButtonToContainer(
80
+ img_container,
81
+ 'svg_sp_btn_datadownload',
82
+ 'place the image on the canvas',
83
+ moveHistoryImageToLayer,
84
+ img
85
+ )
86
+ container.appendChild(img_container)
87
+ // i++
88
+ }
89
+ } catch (e) {
90
+ console.warn(`loadHistory warning: ${e}`)
91
+ }
92
+ })
93
+ document
94
+ .getElementById('btnClearHistoryCache')
95
+ .addEventListener('click', () => {
96
+ const container = document.getElementById('divHistoryImagesContainer')
97
+ container.innerHTML = ''
98
+ })
99
+ module.exports = {
100
+ getHistoryMetadata,
101
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/tab/image_search_tab.js ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const sdapi = require('../../sdapi_py_re')
2
+
3
+ const storage = require('uxp').storage
4
+ const fs = storage.localFileSystem
5
+
6
+ //REFACTOR: move to events.js
7
+ document
8
+ .getElementById('btnImageSearch')
9
+ .addEventListener('click', async function () {
10
+ try {
11
+ // const output_dir_relative = "./server/python_server/"
12
+ const container = document.getElementById(
13
+ 'divImageSearchImagesContainer'
14
+ )
15
+ // const uniqueDocumentId = await getUniqueDocumentId()
16
+ // const [image_paths, metadata_jsons] = await sdapi.loadHistory(uniqueDocumentId)
17
+ const keywords = document.getElementById('imageSearchField').value
18
+ const image_search_objs = await sdapi.imageSearch(keywords)
19
+ while (container.firstChild) {
20
+ container.removeChild(container.firstChild)
21
+ }
22
+
23
+ // let i = 0
24
+ const temp_entry = await fs.getTemporaryFolder()
25
+ for (let image_search_obj of image_search_objs) {
26
+ const img = document.createElement('img')
27
+ // img.src = image_search_obj['image']
28
+
29
+ img.src = image_search_obj['thumbnail']
30
+
31
+ img.className = 'image-search'
32
+ // img.dataset.metadata_json_string = JSON.stringify(metadata_jsons[i])
33
+ container.appendChild(img)
34
+ img.addEventListener('click', async (e) => {
35
+ console.log(`the image url: ${img.src}`)
36
+ const link = img.src
37
+ const image_file_name = 'search_image_temp.png'
38
+ await io.IO.urlToLayer(link, image_file_name)
39
+ // await downloadItExe(link, temp_entry, image_file_name)
40
+
41
+ // const metadata_json = JSON.parse(e.target.dataset.metadata_json_string)
42
+ // console.log("metadata_json: ",metadata_json)
43
+ // document.querySelector('#tiSeed').value = metadata_json.Seed
44
+ // document.querySelector('#historySeedLabel').textContent = metadata_json.Seed
45
+ })
46
+ // i++
47
+ }
48
+ } catch (e) {
49
+ console.warn(`imageSearch warning: ${e}`)
50
+ }
51
+ })
Auto-Photoshop-StableDiffusion-Plugin/utility/tab/lexica_tab.js ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const thumbnail = require('../../thumbnail')
2
+ const api = require('../api')
3
+ const io = require('../io')
4
+ const html_manip = require('../html_manip')
5
+
6
+ class Lexica {
7
+ constructor(items = []) {
8
+ //items is json object return from lexica request
9
+ this.items = items
10
+ }
11
+
12
+ delete() {
13
+ this.items = []
14
+ }
15
+ }
16
+
17
+ async function requestHostedUrl(base64) {
18
+ try {
19
+ const payload = {
20
+ key: '6d207e02198a847aa98d0a2a901485a5',
21
+ source: base64,
22
+ }
23
+ const url = 'https://freeimage.host/api/1/upload'
24
+
25
+ const result_json = await api.requestFormDataPost(url, payload)
26
+
27
+ const image_url = result_json.image.url
28
+ return image_url
29
+ } catch (e) {
30
+ console.warn(e)
31
+ }
32
+ }
33
+
34
+ async function requestLexica(search_query) {
35
+ const lexica_url = `https://lexica.art/api/v1/search?q=${search_query}`
36
+ const url_encoded = encodeURI(lexica_url)
37
+ result = await api.requestGet(url_encoded)
38
+ console.log('result:', result)
39
+ return result
40
+ }
41
+ function displayAllLexicaImages(lexica_items) {
42
+ const lexicaMasterImageContainer = document.getElementById(
43
+ 'divLexicaImagesContainer'
44
+ )
45
+ lexicaMasterImageContainer.innerHTML = ''
46
+ for (item of lexica_items) {
47
+ displayLexicaImage(item)
48
+ }
49
+ }
50
+ function displayLexicaImage(lexica_item) {
51
+ // let lexica_item = {
52
+ // id: '0482ee68-0368-4eca-8846-5930db866b33',
53
+ // gallery: 'https://lexica.art?q=0482ee68-0368-4eca-8846-5930db866b33',
54
+ // src: 'https://lexica-serve-encoded-images.sharif.workers.dev/md/0482ee68-0368-4eca-8846-5930db866b33',
55
+ // srcSmall:
56
+ // 'https://lexica-serve-encoded-images.sharif.workers.dev/sm/0482ee68-0368-4eca-8846-5930db866b33',
57
+ // prompt: 'cute chubby blue fruits icons for mobile game ui ',
58
+ // width: 512,
59
+ // height: 512,
60
+ // seed: '1413536227',
61
+ // grid: false,
62
+ // model: 'stable-diffusion',
63
+ // guidance: 7,
64
+ // promptid: 'd9868972-dad8-477d-8e5a-4a0ae1e9b72b',
65
+ // nsfw: false,
66
+ // }
67
+
68
+ const lexicaMasterImageContainer = document.getElementById(
69
+ 'divLexicaImagesContainer'
70
+ )
71
+
72
+ img_html = document.createElement('img')
73
+ img_html.classList.add('viewer-image')
74
+ img_html.src = lexica_item.srcSmall
75
+ img_html.style.width = '100px'
76
+ const thumbnail_container = thumbnail.Thumbnail.wrapImgInContainer(
77
+ img_html,
78
+ 'viewer-image-container'
79
+ )
80
+
81
+ async function loadOnCanvas(lexica_item) {
82
+ // lexica_item.
83
+ try {
84
+ const link = lexica_item.src
85
+ const image_file_name = 'lexica_image.png'
86
+ await io.IO.urlToLayer(link, image_file_name)
87
+ } catch (e) {
88
+ console.warn('loadOnCanvas(): ', lexica_item, e)
89
+ }
90
+ }
91
+ async function loadSettingsToUI(lexica_item) {
92
+ try {
93
+ const settings = {
94
+ prompt: lexica_item.prompt,
95
+ width: lexica_item.width,
96
+ height: lexica_item.height,
97
+ seed: lexica_item.seed,
98
+ cfg_scale: lexica_item.guidance,
99
+ }
100
+ g_ui_settings_object.autoFillInSettings(settings)
101
+ } catch (e) {
102
+ console.warn(e)
103
+ }
104
+ }
105
+ async function searchForSimilarImage(lexica_item) {
106
+ try {
107
+ document.getElementById('LexicaSearchField').value = lexica_item.src
108
+ const result_json = await requestLexica(lexica_item.src)
109
+
110
+ const lexica_items = result_json.images
111
+
112
+ displayAllLexicaImages(lexica_items)
113
+ } catch (e) {
114
+ console.warn(e)
115
+ }
116
+ }
117
+ thumbnail.Thumbnail.addSPButtonToContainer(
118
+ thumbnail_container,
119
+ 'svg_sp_btn_canvas',
120
+ 'Load on Canvas',
121
+ loadOnCanvas,
122
+ lexica_item
123
+ )
124
+ thumbnail.Thumbnail.addSPButtonToContainer(
125
+ thumbnail_container,
126
+ 'svg_sp_btn',
127
+ 'load settings',
128
+ loadSettingsToUI,
129
+ lexica_item
130
+ )
131
+ thumbnail.Thumbnail.addSPButtonToContainer(
132
+ thumbnail_container,
133
+ 'svg_sp_btn_search',
134
+ 'Search for Similar Image',
135
+ searchForSimilarImage,
136
+ lexica_item
137
+ )
138
+ thumbnail_container.addEventListener('click', () => {
139
+ setLexicaPromptValue(lexica_item.prompt)
140
+ // taLexicaPrompt.style.position = 'fixed'
141
+ const originalPosition = taLexicaPrompt.offsetTop
142
+ const currentPosition = document.querySelector('body > div').scrollTop
143
+
144
+ const isScrolledPast = currentPosition > originalPosition
145
+
146
+ if (isScrolledPast) {
147
+ taLexicaPrompt.style.position = 'fixed'
148
+ } else {
149
+ taLexicaPrompt.style.position = 'static'
150
+ }
151
+ })
152
+
153
+ lexicaMasterImageContainer.appendChild(thumbnail_container)
154
+ }
155
+
156
+ const taLexicaPrompt = document.querySelector('#lexicaPrompt')
157
+ function setLexicaPromptValue(input) {
158
+ taLexicaPrompt.value = input
159
+ }
160
+ function getLexicaPromptValue() {
161
+ return taLexicaPrompt.value
162
+ }
163
+
164
+ document
165
+ .getElementById('btnSearchLexica')
166
+ .addEventListener('click', async () => {
167
+ const search_query = document.getElementById('LexicaSearchField').value
168
+ const result_json = await requestLexica(search_query)
169
+
170
+ const lexica_items = result_json.images
171
+ const lexica_obj = getLexicaObject()
172
+ lexica_obj.items = lexica_items
173
+ displayAllLexicaImages(lexica_items)
174
+ })
175
+
176
+ document
177
+ .getElementById('btnReverseSearchLexica')
178
+ .addEventListener('click', async () => {
179
+ //*) check if selection is valid
180
+ //*) get base64 from selection
181
+ //*) request global url from base64
182
+ //*) request Lexica search
183
+
184
+ try {
185
+ const width = html_manip.getWidth()
186
+ const height = html_manip.getHeight()
187
+ const selectionInfo = await psapi.getSelectionInfoExe()
188
+
189
+ const base64 =
190
+ await io.IO.getSelectionFromCanvasAsBase64Interface_New(
191
+ width,
192
+ height,
193
+ selectionInfo,
194
+ true
195
+ )
196
+ const hosted_url = await requestHostedUrl(base64)
197
+
198
+ const result_json = await requestLexica(hosted_url)
199
+
200
+ const lexica_items = result_json.images
201
+
202
+ displayAllLexicaImages(lexica_items)
203
+ } catch (e) {
204
+ console.warn(e)
205
+ }
206
+ })
207
+
208
+ const windowEventListener = document
209
+ .querySelector('body > div')
210
+ .addEventListener('scroll', () => {
211
+ const originalPosition = taLexicaPrompt.offsetTop
212
+ const currentPosition = document.querySelector('body > div').scrollTop
213
+ taLexicaPrompt.style.position = 'static'
214
+ // const isScrolledPast = currentPosition > originalPosition
215
+
216
+ // if (isScrolledPast) {
217
+ // taLexicaPrompt.style.position = 'fixed'
218
+ // } else {
219
+ // taLexicaPrompt.style.position = 'static'
220
+ // }
221
+ })
222
+
223
+ const g_lexica_obj = new Lexica()
224
+
225
+ function getLexicaObject() {
226
+ return g_lexica_obj
227
+ }
228
+ function setLexicaObject(lexica_obj) {
229
+ g_lexica_obj = lexica_obj
230
+ }
231
+ module.exports = {
232
+ requestLexica,
233
+ displayLexicaImage,
234
+ displayAllLexicaImages,
235
+ requestHostedUrl,
236
+ Lexica,
237
+ getLexicaObject,
238
+ setLexicaObject,
239
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/tab/sd.js ADDED
@@ -0,0 +1,370 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const general = require('../general')
2
+ const thumbnail = require('../../thumbnail')
3
+ const html_manip = require('../html_manip')
4
+ const api = require('../api')
5
+ const psapi = require('../../psapi')
6
+ const sdapi = require('../../sdapi_py_re')
7
+ const Enum = require('../../enum')
8
+ //REFACTOR: move to notification.js
9
+ async function promptForUpdate(header_message, long_message) {
10
+ const shell = require('uxp').shell
11
+
12
+ ;(async () => {
13
+ const buttons = ['Cancel', 'OK']
14
+ const r1 = await dialog_box.prompt(
15
+ header_message,
16
+ long_message,
17
+ buttons
18
+ // 'Please Update you Plugin. it will take about 10 seconds to update',
19
+ // 'update from discord, update from github'[
20
+ // ['Cancel', 'Discord', 'Github']
21
+ // ('Cancel', 'OK')
22
+ // ]
23
+ )
24
+ try {
25
+ let url
26
+ if (r1 === 'Cancel') {
27
+ /* cancelled or No */
28
+ console.log('cancel')
29
+ } else if (r1 === 'Github') {
30
+ url =
31
+ 'https://github.com/AbdullahAlfaraj/Auto-Photoshop-StableDiffusion-Plugin'
32
+ // await py_re.openUrlRequest(url)
33
+ } else if (r1 === 'Discord') {
34
+ console.log('Discord')
35
+ // url = 'https://discord.gg/3mVEtrddXJ'
36
+ // url = 'https://discord.gg/YkUJXYWK3c'
37
+ // await py_re.openUrlRequest(url)
38
+ } else if (r1 === 'Ok') {
39
+ }
40
+ // console.log('url: ', url)
41
+ } catch (e) {
42
+ console.warn(e, url)
43
+ }
44
+ })()
45
+ }
46
+
47
+ async function updateClickEventHandler(current_version) {
48
+ try {
49
+ const online_data = await general.requestOnlineData()
50
+ const b_need_update = general.compareVersions(
51
+ current_version,
52
+ online_data.new_version
53
+ )
54
+
55
+ let header_message = "You're Plugin is up to date."
56
+ let long_message = ''
57
+ if (b_need_update) {
58
+ header_message = `New Version is Available (${online_data.new_version})`
59
+ long_message = online_data.update_message
60
+ }
61
+
62
+ await promptForUpdate(header_message, long_message)
63
+ } catch (e) {
64
+ console.warn(e)
65
+ }
66
+ }
67
+
68
+ function viewMaskExpansion() {
69
+ if (g_generation_session.base64maskExpansionImage) {
70
+ const mask_src = general.base64ToBase64Url(
71
+ g_generation_session.base64maskExpansionImage
72
+ )
73
+ html_manip.setInitImageMaskSrc(mask_src)
74
+ } else {
75
+ console.log(
76
+ 'the mask has not been expanded, g_generation_session.base64maskExpansionImage is empty'
77
+ )
78
+ }
79
+ }
80
+ function viewDrawnMask() {
81
+ //this is the generated mask or user drawn mask, but it's not the mask after expansion
82
+ if (g_generation_session.activeBase64MaskImage) {
83
+ const mask_src = general.base64ToBase64Url(
84
+ g_generation_session.activeBase64MaskImage
85
+ )
86
+ html_manip.setInitImageMaskSrc(mask_src)
87
+ } else {
88
+ console.log('no mask is available')
89
+ }
90
+ }
91
+
92
+ async function clipInterrogate() {
93
+ try {
94
+ const width = html_manip.getWidth()
95
+ const height = html_manip.getHeight()
96
+ const selectionInfo = await psapi.getSelectionInfoExe()
97
+
98
+ const base64 = await io.IO.getSelectionFromCanvasAsBase64Interface_New(
99
+ width,
100
+ height,
101
+ selectionInfo,
102
+ true
103
+ )
104
+
105
+ const url = `${g_sd_url}/sdapi/v1/interrogate`
106
+
107
+ const payload = {
108
+ image: base64,
109
+ model: 'clip',
110
+ }
111
+ const result_json = await api.requestPost(url, payload)
112
+ console.log(result_json)
113
+ return result_json
114
+ } catch (e) {
115
+ console.warn(e)
116
+ }
117
+ }
118
+
119
+ function getHrScaleSliderSDValue() {
120
+ sd_value = html_manip.getSliderSdValue('hrScaleSlider', 1, 100, 1, 4)
121
+ return sd_value
122
+ }
123
+ function setHrScaleSliderSDValue(sd_value) {
124
+ const slider_id = 'hrScaleSlider'
125
+ const label_id = 'hrScaleLabel'
126
+ html_manip.setSliderSdValue(slider_id, label_id, sd_value, 1, 100, 1, 4)
127
+ }
128
+ function getImageCfgScaleSDValue() {
129
+ sd_value = html_manip.getSliderSdValue('slImageCfgScale', 0, 30, 0, 3)
130
+ return sd_value
131
+ }
132
+ function setImageCfgScaleSDValue(sd_value) {
133
+ const slider_id = 'slImageCfgScale'
134
+ const label_id = 'lImageCfgScale'
135
+ // debugger
136
+ html_manip.setSliderSdValue(slider_id, label_id, sd_value, 0, 30, 0, 3)
137
+ }
138
+
139
+ function updateHrScaleFromToLabel() {
140
+ //get width and height
141
+ //get hr scale by
142
+ //find the hr scale and height
143
+
144
+ const hr_scale = getHrScaleSliderSDValue()
145
+ const [width, height] = [html_manip.getWidth(), html_manip.getHeight()]
146
+ const [hr_width, hr_height] = [
147
+ parseInt(width * hr_scale),
148
+ parseInt(height * hr_scale),
149
+ ]
150
+ document.getElementById(
151
+ 'lHrScaleFromTo'
152
+ ).textContent = `${width}x${height} -> ${hr_width}x${hr_height}`
153
+ }
154
+ function getLoraModelPrompt(lora_model_name) {
155
+ return `<lora:${lora_model_name}:1>`
156
+ }
157
+ async function populateLoraModelMenu() {
158
+ try {
159
+ const lora_models_json = await sdapi.requestLoraModels()
160
+ const lora_models_names = Object.keys(lora_models_json)
161
+
162
+ document.getElementById('mLoraModelMenu').innerHTML = ''
163
+
164
+ html_manip.populateMenu(
165
+ 'mLoraModelMenu',
166
+ 'mLoraModelItemClass',
167
+ lora_models_names,
168
+ (item, item_html_element) => {
169
+ item_html_element.innerHTML = item
170
+ item_html_element.onclick = () => {
171
+ const lora_prompt = getLoraModelPrompt(item)
172
+ const prompt = html_manip.getPrompt()
173
+ html_manip.autoFillInPrompt(`${prompt} ${lora_prompt}`)
174
+ }
175
+ },
176
+ false,
177
+ 'Select Lora'
178
+ )
179
+ } catch (e) {
180
+ console.warn('populateLoraModelMenu error: ', e)
181
+ }
182
+ }
183
+
184
+ function displayImageCfgScaleSlider(mode) {
185
+ const b_slider_enabled = document.getElementById(
186
+ 'chUseImageCfgScaleSlider'
187
+ ).checked
188
+ if (b_slider_enabled) {
189
+ if (mode === Enum.generationModeEnum['Txt2Img']) {
190
+ document.getElementById('slImageCfgScale').style.display = 'none'
191
+ } else if (mode === Enum.generationModeEnum['Img2Img']) {
192
+ document.getElementById('slImageCfgScale').style.display = 'block'
193
+ } else if (mode === Enum.generationModeEnum['Inpaint']) {
194
+ document.getElementById('slImageCfgScale').style.display = 'block'
195
+ } else if (mode === Enum.generationModeEnum['Outpaint']) {
196
+ document.getElementById('slImageCfgScale').style.display = 'none'
197
+ } else {
198
+ document.getElementById('slImageCfgScale').style.display = 'none'
199
+ }
200
+ } else {
201
+ document.getElementById('slImageCfgScale').style.display = 'none'
202
+ }
203
+ }
204
+ function initInitMaskElement() {
205
+ //make init mask image use the thumbnail class with buttons
206
+ const mask_image_html = html_manip.getInitImageMaskElement()
207
+ const mask_parent_element = mask_image_html.parentElement
208
+
209
+ this.thumbnail_container = thumbnail.Thumbnail.wrapImgInContainer(
210
+ mask_image_html,
211
+ 'viewer-image-container'
212
+ )
213
+ mask_parent_element.appendChild(thumbnail_container)
214
+ thumbnail.Thumbnail.addSPButtonToContainer(
215
+ this.thumbnail_container,
216
+ 'svg_sp_btn',
217
+ 'view original mask',
218
+
219
+ viewDrawnMask,
220
+ null
221
+ )
222
+ thumbnail.Thumbnail.addSPButtonToContainer(
223
+ this.thumbnail_container,
224
+ 'svg_sp_btn_expand',
225
+ 'view modified mask',
226
+
227
+ viewMaskExpansion,
228
+ null
229
+ )
230
+ populateLoraModelMenu() // no need for await
231
+ }
232
+
233
+ async function refreshSDTab() {
234
+ populateLoraModelMenu()
235
+ }
236
+ document.getElementById('hrScaleSlider').addEventListener('input', (evt) => {
237
+ const sd_value = getHrScaleSliderSDValue()
238
+ setHrScaleSliderSDValue(sd_value.toFixed(2))
239
+ updateHrScaleFromToLabel()
240
+ })
241
+ document.getElementById('btnUpdate').addEventListener('click', async () => {
242
+ await updateClickEventHandler(g_version)
243
+ })
244
+
245
+ document
246
+ .getElementById('slMaskExpansion')
247
+ .addEventListener('change', async (evt) => {
248
+ document.getElementById('slMaskExpansion')
249
+ const original_mask = g_generation_session.activeBase64MaskImage
250
+ if (original_mask) {
251
+ //only if mask is available
252
+ // use blurry and expanded mask
253
+ const iterations = evt.target.value
254
+ const modified_mask = await py_re.maskExpansionRequest(
255
+ original_mask,
256
+ iterations
257
+ )
258
+ if (modified_mask) {
259
+ g_generation_session.base64maskExpansionImage = modified_mask
260
+ viewMaskExpansion()
261
+ }
262
+ }
263
+ })
264
+
265
+ document
266
+ .getElementById('btnInterrogate')
267
+ .addEventListener('click', async () => {
268
+ // start sudo timer after 1 seconds delay
269
+ setTimeout(() => {
270
+ g_generation_session.sudo_timer_id =
271
+ general.sudoTimer('Interrogate')
272
+ }, 1000)
273
+ const interrogate_result = await clipInterrogate()
274
+
275
+ if (interrogate_result.caption) {
276
+ html_manip.autoFillInPrompt(interrogate_result.caption)
277
+ }
278
+
279
+ // after the clipInterrogate finish stop the timer
280
+
281
+ html_manip.updateProgressBarsHtml(0, 'No work in progress')
282
+ g_generation_session.sudo_timer_id = clearInterval(
283
+ g_generation_session.sudo_timer_id
284
+ )
285
+ })
286
+
287
+ function ctrlBackspaceDelete(text) {
288
+ try {
289
+ let index = text.indexOf(String.fromCharCode(127))
290
+ let new_text = text
291
+ if (index >= 0) {
292
+ // Ctrl + Enter pressed
293
+ console.log('Ctrl + Enter pressed')
294
+
295
+ function ctrlBackspaceDeleteLogic(string, index) {
296
+ // if (index < 1) {
297
+ // return string
298
+ // }
299
+ // let i = index - 1
300
+ // while (
301
+ // i > 0 &&
302
+ // /\s/.test(string[i - 1]) === /\s/.test(string[i])
303
+ // ) {
304
+ // i--
305
+ // }
306
+ // return string.slice(0, i) + string.slice(index)
307
+
308
+ if (index < 1) {
309
+ return string
310
+ }
311
+ let i = index - 1
312
+ if (/\s/.test(string[i])) {
313
+ while (i > 0 && /\s/.test(string[i - 1])) {
314
+ i--
315
+ }
316
+ } else {
317
+ while (
318
+ i > 0 &&
319
+ /\s/.test(string[i - 1]) === /\s/.test(string[i])
320
+ ) {
321
+ i--
322
+ }
323
+ }
324
+ return string.slice(0, i) + string.slice(index)
325
+ }
326
+ new_text = ctrlBackspaceDeleteLogic(text, index + 1) //+ 1 to also delete the backspace delete char
327
+ }
328
+
329
+ return new_text
330
+ } catch (error) {
331
+ console.warn(error)
332
+ }
333
+ }
334
+
335
+ // document.getElementById('taPrompt').addEventListener('input', (event) => {
336
+ // // debugger
337
+ // const value = event.target.value
338
+ // console.log('value: ', value)
339
+ // let index = value.indexOf(String.fromCharCode(127))
340
+ // console.log('index:', index)
341
+ // const new_text = ctrlBackspaceDelete(value)
342
+ // event.target.value = new_text
343
+ // console.log('new_text: ', new_text)
344
+ // })
345
+ function initSDTab() {
346
+ initInitMaskElement()
347
+ html_manip.sliderAddEventListener_new(
348
+ 'slImageCfgScale',
349
+ 'lImageCfgScale',
350
+ 0,
351
+ 30,
352
+ 0,
353
+ 3
354
+ )
355
+ }
356
+
357
+ initSDTab()
358
+ module.exports = {
359
+ updateClickEventHandler,
360
+ viewMaskExpansion,
361
+ viewDrawnMask,
362
+ clipInterrogate,
363
+ getHrScaleSliderSDValue,
364
+ getLoraModelPrompt,
365
+ populateLoraModelMenu,
366
+ getImageCfgScaleSDValue,
367
+ setImageCfgScaleSDValue,
368
+ refreshSDTab,
369
+ displayImageCfgScaleSlider,
370
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/tab/settings.js ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const io = require('../io')
2
+
3
+ function getUseSharpMask() {
4
+ const isChecked = document.getElementById('chUseSharpMask').checked
5
+ return isChecked
6
+ }
7
+ function setUseSharpMask() {
8
+ console.warn('setUseSharpMask is not setup')
9
+ }
10
+
11
+ function getUseLiveProgressImage() {
12
+ const b_live_update = document.getElementById('chLiveProgressImage').checked
13
+ return b_live_update
14
+ }
15
+ function setUseLiveProgressImage(b_live_update) {
16
+ document.getElementById('chLiveProgressImage').checked = b_live_update
17
+ }
18
+
19
+ function getExtensionType() {
20
+ return [...document.getElementsByClassName('rbExtensionType')].filter(
21
+ (e) => e.checked == true
22
+ )[0].value
23
+ }
24
+
25
+ document.getElementById('btnGetDocPath').addEventListener('click', async () => {
26
+ const docPath = await io.IOFolder.getDocumentFolderNativePath()
27
+ document.getElementById('tiDocPath').value = docPath
28
+
29
+ const uuid = await getUniqueDocumentId()
30
+ doc_entry = await io.IOFolder.getDocFolder(uuid)
31
+ await shell.openPath(doc_entry.nativePath)
32
+ })
33
+
34
+ document.getElementById('btnSdUrl').addEventListener('click', async () => {
35
+ //change the sdUrl in server in proxy server
36
+ // console.log('you clicked btnSdUrl')
37
+ let new_sd_url = document.getElementById('tiSdUrl').value
38
+ changeSdUrl(new_sd_url)
39
+ })
40
+
41
+ function getSdUrlHtml() {
42
+ let sd_url = document.getElementById('tiSdUrl').value
43
+ return sd_url
44
+ }
45
+ function setSdUrlHtml(sd_url) {
46
+ document.getElementById('tiSdUrl').value = sd_url
47
+ }
48
+ async function changeSdUrl(sd_url) {
49
+ sd_url = sd_url.trim()
50
+ console.log('sd_url.trim(): ', sd_url)
51
+
52
+ if (sd_url.length > 0) {
53
+ //check if the last character of the url has "/" or '\' and remove it
54
+
55
+ last_index = sd_url.length - 1
56
+
57
+ if (sd_url[last_index] === '/' || sd_url[last_index] === '\\') {
58
+ sd_url = sd_url.slice(0, -1)
59
+ }
60
+
61
+ //submit the change
62
+ await sdapi.changeSdUrl(sd_url)
63
+ }
64
+ }
65
+
66
+ async function saveSettings() {
67
+ const settings_tab_settings = {
68
+ use_sharp_mask: getUseSharpMask(),
69
+ extension_type: getExtensionType(),
70
+ sd_url: getSdUrlHtml(),
71
+ }
72
+
73
+ const folder = await io.IOFolder.getSettingsFolder()
74
+ await io.IOJson.saveJsonToFile(
75
+ settings_tab_settings,
76
+ folder,
77
+ 'settings_tab.json'
78
+ )
79
+ }
80
+ async function loadSettings() {
81
+ try {
82
+ const folder = await io.IOFolder.getSettingsFolder()
83
+ let settings_tab_settings = await io.IOJson.loadJsonFromFile(
84
+ folder,
85
+ 'settings_tab.json'
86
+ )
87
+ setSdUrlHtml(settings_tab_settings['sd_url'])
88
+ await changeSdUrl(settings_tab_settings['sd_url'])
89
+ } catch (e) {
90
+ console.warn(e)
91
+ }
92
+ }
93
+
94
+ document.getElementById('chUseSharpMask').addEventListener('change', (ev) => {
95
+ const isChecked = ev.target.checked
96
+ if (isChecked) {
97
+ document.getElementById('slMaskBlur').setAttribute('disabled')
98
+ } else {
99
+ document.getElementById('slMaskBlur').removeAttribute('disabled')
100
+ }
101
+ })
102
+
103
+ document.getElementById('chUseSmartObject').addEventListener('change', (ev) => {
104
+ const isChecked = ev.target.checked
105
+ if (isChecked) {
106
+ g_b_use_smart_object = true
107
+ } else {
108
+ g_b_use_smart_object = false
109
+ }
110
+ })
111
+
112
+ function getUseOriginalPrompt() {
113
+ const b_use_original_prompt = document.getElementById(
114
+ 'chUseOriginalPrompt'
115
+ ).checked
116
+ return b_use_original_prompt
117
+ }
118
+
119
+ document
120
+ .getElementById('btnSaveSettingsTabs')
121
+ .addEventListener('click', async () => {
122
+ await saveSettings()
123
+ })
124
+
125
+ module.exports = {
126
+ getUseSharpMask,
127
+ setUseSharpMask,
128
+ getExtensionType,
129
+ getSdUrlHtml,
130
+ setSdUrlHtml,
131
+ changeSdUrl,
132
+ loadSettings,
133
+ saveSettings,
134
+ getUseLiveProgressImage,
135
+ setUseLiveProgressImage,
136
+ getUseOriginalPrompt,
137
+ }
Auto-Photoshop-StableDiffusion-Plugin/utility/tab/share_tab.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const chLiveProgressImageElements = document.getElementsByClassName(
2
+ 'chLiveProgressImageClass'
3
+ )
4
+
5
+ const default_preview_value = document.getElementById(
6
+ 'chLiveProgressImage'
7
+ ).checked
8
+
9
+ chLiveProgressImageElements.forEach((element) => {
10
+ element.checked = default_preview_value
11
+ element.addEventListener('click', (event) => {
12
+ value = element.checked
13
+ chLiveProgressImageElements.forEach((element) => {
14
+ element.checked = value
15
+ })
16
+ })
17
+ })
Auto-Photoshop-StableDiffusion-Plugin/utility/tips.js ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //tips that will display when you hover over a html element
2
+ const tips_json = {
3
+ snapshot: '',
4
+ txt2img: 'use this mode to generate images from text only',
5
+ img2img: 'use this mode to generate variation of an image',
6
+ inpaint:
7
+ 'use this mode to generate variation of a small area of an image, while keeping the rest of the image intact',
8
+ outpaint:
9
+ 'use this mode to (1) fill any missing area of an image,(2) expand an image',
10
+ generate: 'create',
11
+ discardAll: 'Delete all generated images from the canvas',
12
+ acceptAll: 'Keep all generated images on the canvas',
13
+ acceptSelected: 'Keep only the highlighted images',
14
+ discardSelected: 'Delete only the highlighted images',
15
+ modelMenu: 'select a model',
16
+ refresh: 'Refresh the plugin, only fixes minor issues.',
17
+ prompt_shortcut: 'use {keyword} form the prompts library',
18
+ inpaint: 'use when you need to modify an already existing part of an image',
19
+ img2img: 'use this mode when you want to generate variation of an image',
20
+ txt2img: 'use this mode to generate images based on text only',
21
+ batchNumber:
22
+ 'the number of images to generate at once.The larger the number more VRAM stable diffusion will use.',
23
+ steps: 'how long should stable diffusion take to generate an image',
24
+ selection_mode_ratio:
25
+ 'will auto fill the width and height slider to the same ratio as the selection area',
26
+ selection_mode_precise:
27
+ 'auto fill width and height slider to the size as the selection area',
28
+ selection_mode_ignore:
29
+ 'you will have to fill the width and height slider manually',
30
+ cfg_scale: 'larger value will put more emphasis on the prompt',
31
+ preset_menu:
32
+ 'auto fill the plugin with smart settings, to speed up your working process.',
33
+ width: 'the generated image width',
34
+ height: 'the generated image height',
35
+ mask_expansion:
36
+ "the larger the value the more the mask will expand, '0' means use precise masking, use in combination with the mask blur",
37
+ mask_content_fill: '',
38
+ }
39
+
40
+ module.exports = { tips_json }
Auto-Photoshop-StableDiffusion-Plugin/utility/ui.js ADDED
@@ -0,0 +1,404 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const html_manip = require('./html_manip')
2
+ const presets = require('./presets/preset')
3
+ const layer_util = require('../utility/layer')
4
+ const psapi = require('../psapi')
5
+ const Enum = require('../enum')
6
+ const { executeAsModal } = require('photoshop').core
7
+
8
+ class UI {
9
+ constructor() {}
10
+
11
+ onStartSessionUI() {
12
+ // will toggle the buttons needed when a generation session start
13
+
14
+ const accept_class_btns = Array.from(
15
+ document.getElementsByClassName('acceptClass')
16
+ )
17
+
18
+ const discard_class_btns = Array.from(
19
+ document.getElementsByClassName('discardClass')
20
+ )
21
+
22
+ const discard_selected_class_btns = Array.from(
23
+ document.getElementsByClassName('discardSelectedClass')
24
+ )
25
+
26
+ const accept_selected_class_btns = Array.from(
27
+ document.getElementsByClassName('acceptSelectedClass')
28
+ )
29
+
30
+ //show the accept and discard buttons when a new session is active
31
+ accept_class_btns.forEach(
32
+ (element) => (element.style.display = 'inline-block')
33
+ )
34
+ discard_class_btns.forEach(
35
+ (element) => (element.style.display = 'inline-block')
36
+ )
37
+ discard_selected_class_btns.forEach(
38
+ (element) => (element.style.display = 'inline-block')
39
+ )
40
+ accept_selected_class_btns.forEach(
41
+ (element) => (element.style.display = 'inline-block')
42
+ )
43
+
44
+ this.generateMoreUI()
45
+ }
46
+ onActiveSessionUI() {}
47
+ generateModeUI(mode) {
48
+ const generate_btns = Array.from(
49
+ document.getElementsByClassName('btnGenerateClass')
50
+ )
51
+ generate_btns.forEach((element) => {
52
+ element.textContent = `Generate ${mode}`
53
+ })
54
+ html_manip.setGenerateButtonsColor('generate', 'generate-more')
55
+ }
56
+ generateMoreUI() {
57
+ const generate_btns = Array.from(
58
+ document.getElementsByClassName('btnGenerateClass')
59
+ )
60
+ const generation_mode = g_generation_session.mode
61
+ const generation_name = getCurrentGenerationModeByValue(generation_mode)
62
+ generate_btns.forEach((element) => {
63
+ element.textContent = `Generate More ${generation_name}`
64
+ })
65
+ html_manip.setGenerateButtonsColor('generate-more', 'generate')
66
+ }
67
+
68
+ onEndSessionUI() {
69
+ const accept_class_btns = Array.from(
70
+ document.getElementsByClassName('acceptClass')
71
+ )
72
+
73
+ const discard_class_btns = Array.from(
74
+ document.getElementsByClassName('discardClass')
75
+ )
76
+ const discard_selected_class_btns = Array.from(
77
+ document.getElementsByClassName('discardSelectedClass')
78
+ )
79
+
80
+ const accept_selected_class_btns = Array.from(
81
+ //Node: change customClass to acceptSelectedClass
82
+ document.getElementsByClassName('acceptSelectedClass')
83
+ )
84
+
85
+ accept_class_btns.forEach((element) => (element.style.display = 'none'))
86
+ discard_class_btns.forEach(
87
+ (element) => (element.style.display = 'none')
88
+ )
89
+ discard_selected_class_btns.forEach(
90
+ (element) => (element.style.display = 'none')
91
+ )
92
+
93
+ accept_selected_class_btns.forEach(
94
+ (element) => (element.style.display = 'none')
95
+ )
96
+
97
+ this.generateModeUI(g_sd_mode)
98
+ }
99
+
100
+ setGenerateBtnText(textContent) {
101
+ const generate_btns = Array.from(
102
+ document.getElementsByClassName('btnGenerateClass')
103
+ )
104
+ generate_btns.forEach((element) => {
105
+ element.textContent = textContent
106
+ })
107
+ }
108
+ }
109
+
110
+ // const defaultSettings = {
111
+ // model: null,
112
+ // prompt_shortcut: null,
113
+ // positive_prompt: "",
114
+ // negative_prompt: "",
115
+ // selection_mode: null,
116
+ // batch_number: 1,
117
+ // steps: 20,
118
+ // width: 512 ,
119
+ // height:512,
120
+ // firstphase_width:512,
121
+ // firstphase_height:512,
122
+ // cfg:7,
123
+ // denoising_strength:0.7,
124
+ // hi_res_denoising_strength:0.7,
125
+ // mask_blur: 8,
126
+ // inpaint_at_full_res: false,
127
+ // hi_res_fix:false,
128
+ // inpaint_padding:0,
129
+ // seed:-1,
130
+ // samplers: null,
131
+ // mask_content:null
132
+ // }
133
+
134
+ class UIElement {
135
+ constructor() {
136
+ this.name
137
+ this.html_elem
138
+ this.sd_value
139
+ }
140
+ setValue() {}
141
+ getValue() {}
142
+ }
143
+ function createUIElement(getter, setter) {
144
+ let ui_element_obj = new UIElement()
145
+ ui_element_obj.getValue = getter
146
+ ui_element_obj.setValue = setter
147
+ return ui_element_obj
148
+ }
149
+ class UISettings {
150
+ // get and set the settings of the ui. the stable diffusion settings not the human friendly settings
151
+ constructor() {
152
+ // this.width = new ui.UIElement()
153
+ // this.width.getValue = html_manip.getWidth
154
+ // this.width.setValue = html_manip.autoFillInWidth
155
+ this.width = createUIElement(
156
+ html_manip.getWidth,
157
+ html_manip.autoFillInWidth
158
+ )
159
+ this.height = createUIElement(
160
+ html_manip.getHeight,
161
+ html_manip.autoFillInHeight
162
+ )
163
+ this.steps = createUIElement(
164
+ html_manip.getSteps,
165
+ html_manip.autoFillInSteps
166
+ )
167
+ this.batch_number = createUIElement(
168
+ html_manip.getBatchNumber,
169
+ html_manip.autoFillInBatchNumber
170
+ )
171
+ this.firstphase_width = createUIElement(
172
+ html_manip.getHrWidth,
173
+ html_manip.autoFillInHRWidth
174
+ )
175
+ this.firstphase_height = createUIElement(
176
+ html_manip.getHrHeight,
177
+ html_manip.autoFillInHRHeight
178
+ )
179
+ this.cfg = createUIElement(html_manip.getCFG, html_manip.setCFG)
180
+ this.denoising_strength = createUIElement(
181
+ html_manip.getDenoisingStrength,
182
+ html_manip.autoFillInDenoisingStrength
183
+ )
184
+
185
+ this.mask_content = createUIElement(
186
+ html_manip.getMaskContent,
187
+ html_manip.setMaskContent
188
+ )
189
+ this.seed = createUIElement(html_manip.getSeed, html_manip.setSeed)
190
+ this.prompt = createUIElement(
191
+ html_manip.getPrompt,
192
+ html_manip.autoFillInPrompt
193
+ )
194
+ this.negative_prompt = createUIElement(
195
+ html_manip.getNegativePrompt,
196
+ html_manip.autoFillInNegativePrompt
197
+ )
198
+ this.mask_blur = createUIElement(
199
+ html_manip.getMaskBlur,
200
+ html_manip.setMaskBlur
201
+ )
202
+ this.mask_expansion = createUIElement(
203
+ html_manip.getMaskExpansion,
204
+ html_manip.setMaskExpansion
205
+ )
206
+ this.samplers = createUIElement(
207
+ html_manip.getCheckedSamplerName,
208
+ html_manip.autoFillInSampler
209
+ )
210
+
211
+ this.uiElements = {
212
+ // model: null,
213
+ // prompt_shortcut: null,
214
+ prompt: this.prompt,
215
+ negative_prompt: this.negative_prompt,
216
+ // selection_mode: null,
217
+ batch_size: this.batch_number,
218
+ steps: this.steps,
219
+ width: this.width,
220
+ height: this.height,
221
+ firstphase_width: this.firstphase_width,
222
+ firstphase_height: this.firstphase_height,
223
+ cfg_scale: this.cfg,
224
+ denoising_strength: this.denoising_strength,
225
+ // hi_res_denoising_strength:0.7,
226
+ mask_blur: this.mask_blur,
227
+ mask_expansion: this.mask_expansion,
228
+ // inpaint_at_full_res: false,
229
+ // hi_res_fix:false,
230
+ // inpaint_padding:0,
231
+ seed: this.seed,
232
+ sampler_index: this.samplers,
233
+ mask_content: this.mask_content,
234
+ }
235
+ }
236
+
237
+ autoFillInSettings(settings) {
238
+ for (const [name, value] of Object.entries(settings)) {
239
+ if (this.uiElements.hasOwnProperty(name) && value) {
240
+ //get the values for debugging
241
+ const old_value = this.uiElements[name].getValue()
242
+ console.log(
243
+ '(name,old_value) => newValue:',
244
+ name,
245
+ old_value,
246
+ value
247
+ )
248
+ //set the value
249
+ this.uiElements[name].setValue(value)
250
+ }
251
+ }
252
+ }
253
+ getSettings() {
254
+ let settings = {}
255
+ for (const [name, ui_element] of Object.entries(this.uiElements)) {
256
+ if (ui_element) {
257
+ const value = ui_element.getValue()
258
+ settings[name] = value
259
+ }
260
+ }
261
+ return settings
262
+ }
263
+
264
+ saveAsJson(json_file_name, settings) {
265
+ for (const [name, value] of Object.entries(settings)) {
266
+ if (this.uiElements.hasOwnProperty(name) && value) {
267
+ //get the values for debugging
268
+ const old_value = this.uiElements[name].getValue()
269
+ console.log(
270
+ '(name,old_value) => newValue:',
271
+ name,
272
+ old_value,
273
+ value
274
+ )
275
+
276
+ //set the value
277
+ }
278
+ }
279
+ }
280
+ }
281
+ // const ui_settings = new UISettings()
282
+
283
+ function loadPreset(ui_settings, preset) {
284
+ console.log('preset:', preset)
285
+ ui_settings.autoFillInSettings(preset)
286
+ }
287
+
288
+ function loadLatentNoiseSettings(ui_settings) {
289
+ loadPreset(ui_settings, presets.LatentNoiseSettings)
290
+ }
291
+
292
+ function loadFillSettings(ui_settings) {
293
+ loadPreset(ui_settings, presets.FillSettings)
294
+ }
295
+ function loadOriginalSettings(ui_settings) {
296
+ loadPreset(ui_settings, presets.OriginalSettings)
297
+ }
298
+ async function loadHealBrushSettings(ui_settings) {
299
+ document.getElementById('rbModeInpaint').click()
300
+
301
+ loadPreset(ui_settings, presets.HealBrushSettings)
302
+ }
303
+ function loadCustomPreset(ui_settings_obj, custom_preset_settings) {
304
+ loadPreset(ui_settings_obj, custom_preset_settings)
305
+ }
306
+
307
+ function loadCustomPresetsSettings() {}
308
+ async function mapCustomPresetsToLoaders(ui_settings_obj) {
309
+ const name_to_settings_obj = await presets.getAllCustomPresetsSettings(
310
+ Enum.PresetTypeEnum['SDPreset']
311
+ )
312
+ const preset_name_to_loader_obj = {}
313
+ for (const [preset_name, preset_settings] of Object.entries(
314
+ name_to_settings_obj
315
+ )) {
316
+ preset_name_to_loader_obj[preset_name] = () => {
317
+ loadCustomPreset(ui_settings_obj, preset_settings)
318
+ }
319
+ }
320
+ return preset_name_to_loader_obj
321
+ }
322
+
323
+ const g_nativePresets = {
324
+ fill: loadFillSettings,
325
+ original: loadOriginalSettings,
326
+ 'latent noise': loadLatentNoiseSettings,
327
+ 'Heal Brush': loadHealBrushSettings,
328
+ }
329
+
330
+ async function getLoadedPresets(ui_settings_obj) {
331
+ let customPresets
332
+
333
+ customPresets = await mapCustomPresetsToLoaders(ui_settings_obj)
334
+ console.log('customPresets: ', customPresets)
335
+ let loadedPresets = {
336
+ ...g_nativePresets,
337
+ ...customPresets,
338
+ }
339
+ return loadedPresets
340
+ }
341
+ let g_ui_settings_object = new UISettings()
342
+ function getUISettingsObject() {
343
+ return g_ui_settings_object
344
+ }
345
+
346
+ //REFACTOR: move to ui.js
347
+ function addPresetMenuItem(preset_title) {
348
+ // console.log(model_title,model_name)
349
+ const menu_item_element = document.createElement('sp-menu-item')
350
+ menu_item_element.className = 'mPresetMenuItem'
351
+ menu_item_element.innerHTML = preset_title
352
+
353
+ // menu_item_element.addEventListener('select',()=>{
354
+ // preset_func(g_ui_settings)
355
+ // })
356
+ return menu_item_element
357
+ }
358
+ //REFACTOR: move to ui.js
359
+ async function populatePresetMenu() {
360
+ document.getElementById('mPresetMenu').innerHTML = ''
361
+ const divider_elem = document.createElement('sp-menu-divider')
362
+ const preset_name = 'Select Smart Preset'
363
+ const preset_func = () => {}
364
+ const dummy_preset_item = addPresetMenuItem(preset_name, preset_func)
365
+ dummy_preset_item.setAttribute('selected', 'selected')
366
+ // dummy_preset_item.setAttribute('disabled')
367
+ document.getElementById('mPresetMenu').appendChild(dummy_preset_item)
368
+ document.getElementById('mPresetMenu').appendChild(divider_elem)
369
+ const presets = await getLoadedPresets(g_ui_settings_object)
370
+ for ([key, value] of Object.entries(presets)) {
371
+ const preset_menu_item = addPresetMenuItem(key, value)
372
+ document.getElementById('mPresetMenu').appendChild(preset_menu_item)
373
+ }
374
+ }
375
+
376
+ populatePresetMenu()
377
+ //REFACTOR: move to preset_tab.js
378
+ document
379
+ .getElementById('mPresetMenu')
380
+ .addEventListener('change', async (evt) => {
381
+ const preset_index = evt.target.selectedIndex
382
+ const preset_name = evt.target.options[preset_index].textContent
383
+ const presets = await getLoadedPresets(g_ui_settings_object)
384
+ if (presets.hasOwnProperty(preset_name)) {
385
+ const loader = presets[preset_name]
386
+ if (loader.constructor.name === 'AsyncFunction') {
387
+ await loader(g_ui_settings_object)
388
+ } else {
389
+ loader(g_ui_settings_object)
390
+ }
391
+ }
392
+ })
393
+
394
+ module.exports = {
395
+ UI,
396
+ UIElement,
397
+ UISettings,
398
+ loadLatentNoiseSettings,
399
+ loadFillSettings,
400
+ loadHealBrushSettings,
401
+ getLoadedPresets,
402
+ getUISettingsObject,
403
+ populatePresetMenu,
404
+ }
Auto-Photoshop-StableDiffusion-Plugin/viewer.js ADDED
@@ -0,0 +1,861 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // output image class: one image to one layer
2
+ // * path:
3
+ // * image layer
4
+ // * viewer()
5
+ // * select()
6
+
7
+ // * init image class: has three layers
8
+ // * path :
9
+ // * init image group layer
10
+ // * init image layer
11
+ // * background layer
12
+
13
+ // * mask class:
14
+ // * path
15
+ // * mask group
16
+ // * white mask
17
+ // * balck layer
18
+ // * select()
19
+ // * viewe()
20
+ const general = require('./utility/general')
21
+ const Enum = require('./enum')
22
+ const psapi = require('./psapi')
23
+ const layer_util = require('./utility/layer')
24
+ const thumbnail = require('./thumbnail')
25
+ const ViewerObjState = {
26
+ Delete: 'delete',
27
+ Unlink: 'unlink',
28
+ }
29
+
30
+ class ViewerImage {
31
+ constructor() {
32
+ this.img_html = null //
33
+ this.thumbnail_container = null
34
+ this.is_highlighted = false
35
+ this.can_highlight = true
36
+ this.is_active = false // active is a temporary highlight , the yellow/orang highlight
37
+ this.state = ViewerObjState['Unlink']
38
+
39
+ // true will delete the layers from the layer stacks when the session ends,
40
+ // false mean use this.state to determine whither you delete the layer or not
41
+ this.autoDelete = false
42
+ this.viewerManager = null // store link to the viewer manager of this document
43
+ this.viewerObjectType
44
+ this.objectId
45
+ }
46
+ info() {
47
+ console.log('state: ', this.state)
48
+ }
49
+ visible(visibleOn) {}
50
+ select() {}
51
+ isActive() {
52
+ return this.is_active
53
+ }
54
+ active(isActive) {
55
+ if (isActive) {
56
+ //unlink it if it's active
57
+ // this.state = ViewerObjState['Unlink']
58
+
59
+ this.img_html.classList.add('viewerImgActive')
60
+ } else {
61
+ if (this.getHighlight() === false) {
62
+ // if it's not active and it's not highlighted
63
+ // this.state = ViewerObjState['Delete']
64
+ } else {
65
+ // this.state = ViewerObjState['Unlink'] //it's not active but it's highlighted then keep it
66
+ }
67
+ this.img_html.classList.remove('viewerImgActive')
68
+ }
69
+ this.is_active = isActive
70
+ }
71
+ setAutoDelete(auto_delete) {
72
+ this.autoDelete = auto_delete
73
+ }
74
+
75
+ isSameLayer(layer_id) {}
76
+ setHighlight(is_highlighted) {
77
+ if (this.can_highlight) {
78
+ this.is_highlighted = is_highlighted
79
+ if (this.is_highlighted) {
80
+ // this.state = ViewerObjState['Unlink']
81
+ this.img_html.classList.add('viewerImgSelected')
82
+ } else {
83
+ this.img_html.classList.remove('viewerImgSelected')
84
+ // this.state = ViewerObjState["Delete"]
85
+ }
86
+ }
87
+ }
88
+ getHighlight() {
89
+ return this.is_highlighted
90
+ }
91
+ toggleHighlight() {
92
+ const toggle_value = !this.getHighlight()
93
+ this.setHighlight(toggle_value)
94
+ // this.is_highlighted = !this.is_highlighted
95
+ // this.img_html.classList.toggle("viewerImgSelected")
96
+ }
97
+ setImgHtml() {}
98
+
99
+ async delete() {
100
+ try {
101
+ if (this.img_html) {
102
+ this.img_html.remove() //delete the img html element
103
+ }
104
+ if (this.thumbnail_container) {
105
+ this.thumbnail_container.remove()
106
+ }
107
+
108
+ //1) it's output layer // can_highlight && this.getHighlight()
109
+ //2) it init or mask relate layers // this.autoDelete
110
+ //3) it output layer that been used as init layer // !can_highlight && !this.autoDelete
111
+
112
+ // do 1) and 2) here . test for 3) in InitImage
113
+
114
+ //1)
115
+ if (this.can_highlight && (this.getHighlight() || this.is_active)) {
116
+ //keep if can be highlighted and either is highlighted or active
117
+ this.state = ViewerObjState['Unlink']
118
+ } else {
119
+ //
120
+ this.state = ViewerObjState['Delete']
121
+ }
122
+
123
+ if (this.autoDelete) {
124
+ //remove if it's first automated layer
125
+ this.state = ViewerObjState['Delete']
126
+ }
127
+
128
+ // else {
129
+ // //discard only if it's
130
+ // if (this.autoDelete){
131
+ // this.state = ViewerObjState['Delete']
132
+ // }
133
+ // }
134
+ } catch (e) {
135
+ console.warn(e)
136
+ }
137
+ }
138
+
139
+ unlink() {
140
+ //keep the layer but unlink it from the ui
141
+ try {
142
+ this.img_html.remove() //delete the img html element
143
+ } catch (e) {
144
+ console.warn(e)
145
+ }
146
+ }
147
+
148
+ createThumbnailNew(img, _) {
149
+ this.img_html = img
150
+ this.thumbnail_container = thumbnail.Thumbnail.wrapImgInContainer(
151
+ img,
152
+ 'viewer-image-container'
153
+ )
154
+ thumbnail.Thumbnail.addSPButtonToContainer(
155
+ this.thumbnail_container,
156
+ 'svg_sp_btn',
157
+ 'Use this as an initial image',
158
+
159
+ this.useOutputImageAsInitImage,
160
+ img
161
+ )
162
+ }
163
+ useOutputImageAsInitImage = async () => {
164
+ //set init image event listener, use when settion is active
165
+ // const layer = await app.activeDocument.activeLayers[0]
166
+ try {
167
+ // console.log('this.img_html:', this.img_html)
168
+ // await executeAsModal(() => {
169
+ // this.visible(true)
170
+ // })
171
+ // await this.select(true) //select() does take arguments
172
+ // this.active(true)
173
+ await executeAsModal(async () => {
174
+ await this.click(Enum.clickTypeEnum['Click'])
175
+ })
176
+
177
+ const layer = layer_util.Layer.doesLayerExist(this.layer)
178
+ ? this.layer
179
+ : await app.activeDocument.activeLayers[0]
180
+
181
+ // const layer = this.layer
182
+ const image_info = await psapi.silentSetInitImage(
183
+ layer,
184
+ random_session_id
185
+ )
186
+ const image_name = image_info['name']
187
+ const path = `./server/python_server/init_images/${image_name}`
188
+ g_viewer_manager.addInitImageLayers(layer, path, false)
189
+ await g_viewer_manager.loadInitImageViewerObject(path)
190
+ } catch (e) {
191
+ console.warn(e)
192
+ }
193
+ }
194
+ createThumbnail(img, b_button_visible = true) {
195
+ this.img_html = img
196
+ // Create new container element
197
+ this.thumbnail_container = document.createElement('div')
198
+
199
+ this.thumbnail_container.className = 'viewer-image-container'
200
+
201
+ const elem = document.getElementById('svg_sp_btn')
202
+
203
+ // Create a copy of it
204
+ const clone = elem.cloneNode(true)
205
+ const button = clone
206
+ button.style.display = null
207
+ button.setAttribute(
208
+ 'title',
209
+ 'use this image to generate more variance like it'
210
+ )
211
+
212
+ // Create button element
213
+ // const button = document.createElement('sp-button');
214
+ button.className = 'viewer-image-button'
215
+ // button.innerHTML = "Button";
216
+ if (!b_button_visible) {
217
+ button.style.display = 'none'
218
+ }
219
+ button.addEventListener('click', async () => {
220
+ await useOutputImageAsInitImage()
221
+ })
222
+
223
+ // Append elements to container
224
+ this.thumbnail_container.appendChild(this.img_html)
225
+ this.thumbnail_container.appendChild(button)
226
+
227
+ // this.img_html = container
228
+ }
229
+ }
230
+
231
+ class OutputImage extends ViewerImage {
232
+ constructor(layer, path, viewer_manager) {
233
+ super()
234
+ this.layer = layer
235
+ this.path = path
236
+ this.img_html = null
237
+ this.viewerManager = viewer_manager
238
+ this.viewerObjectType = Enum.ViewerObjectTypeEnum['OutputImage']
239
+ this.objectId = path // the path is unique, so we will use it as an id
240
+ }
241
+ async click(click_type) {
242
+ console.log('click_type: ', click_type)
243
+ // if (this.isActive() && click_type === Enum.clickTypeEnum['Click']) {
244
+ // //convert consecutive clicks to AltClick
245
+ // click_type = Enum.clickTypeEnum['SecondClick']
246
+ // console.log('converted click_type: ', click_type)
247
+ // }
248
+
249
+ if (click_type === Enum.clickTypeEnum['Click']) {
250
+ //select layer
251
+ //turn the layer visible
252
+ //set the layer to active
253
+ this.visible(true)
254
+ await this.select(true) //select() does take arguments
255
+ this.active(true)
256
+ } else if (click_type === Enum.clickTypeEnum['ShiftClick']) {
257
+ this.visible(true)
258
+ await this.select(true) //select() does take arguments
259
+ this.setHighlight(true)
260
+ this.active(true)
261
+ // if (this.viewerManager.last_selected_viewer_obj) {
262
+ // //if the last selected layer is valid then converted last selected layer into highlight layer
263
+ // this.viewerManager.last_selected_viewer_obj.setHighlight(true)
264
+ // }
265
+ } else if (click_type === Enum.clickTypeEnum['AltClick']) {
266
+ // this.viewerManager.last_selected_viewer_obj = null
267
+ this.setHighlight(false)
268
+ this.visible(false)
269
+ this.active(false)
270
+
271
+ await psapi.unselectActiveLayersExe() //Note:can we move to ViewerManager.click()
272
+ } else if (click_type === Enum.clickTypeEnum['SecondClick']) {
273
+ //select layer
274
+ //turn the layer visible
275
+ //set the layer to active
276
+ this.visible(false)
277
+ await this.select(false) //select() does take arguments
278
+ this.active(false)
279
+ }
280
+ this.viewerManager.replaceLastSelection(click_type, this) //pass the click_type and this object
281
+ }
282
+ visible(visibleOn) {
283
+ //turn the visibility for the layer
284
+ try {
285
+ super.visible(visibleOn)
286
+ if (layer_util.Layer.doesLayerExist(this.layer)) {
287
+ this.layer.visible = visibleOn
288
+ }
289
+ } catch (e) {
290
+ console.warn(e)
291
+ }
292
+ }
293
+ async select() {
294
+ //select the layer
295
+ super.select()
296
+ if (layer_util.Layer.doesLayerExist(this.layer)) {
297
+ await psapi.selectLayersExe([this.layer])
298
+ // console.log(`${this.layer.id} got selected`);
299
+ }
300
+ }
301
+
302
+ isSameLayer(layer_id) {
303
+ super.isSameLayer(layer_id)
304
+ const is_same = this.layer.id == layer_id
305
+ return is_same
306
+ }
307
+ isSameObject(object) {
308
+ if (
309
+ layer_util.Layer.doesLayerExist(this.layer) &&
310
+ layer_util.Layer.doesLayerExist(object.layer)
311
+ ) {
312
+ return this.layer.id === object.layer.id
313
+ }
314
+ return false
315
+ }
316
+
317
+ setImgHtml(img_html) {
318
+ super.setImgHtml()
319
+ this.img_html = img_html
320
+ }
321
+ async delete() {
322
+ try {
323
+ await super.delete()
324
+ // this.img_html.remove()//delete the img html element
325
+ if (this.state === ViewerObjState['Delete']) {
326
+ await psapi.cleanLayers([this.layer])
327
+ }
328
+ } catch (e) {
329
+ console.warn(e)
330
+ }
331
+ }
332
+ info() {
333
+ super.info()
334
+ }
335
+ // unlink(){
336
+ // //keep the layer but unlink it from the ui
337
+ // try{
338
+
339
+ // super.unlink()
340
+ // this.img_html.remove()//delete the img html element
341
+
342
+ // }catch(e){
343
+ // console.warn(e)
344
+ // }
345
+ // }
346
+ }
347
+
348
+ class InitImage extends ViewerImage {
349
+ constructor(init_group, init_snapshot, solid_layer, path, viewer_manager) {
350
+ super()
351
+ this.init_group = init_group
352
+ this.init_snapshot = init_snapshot
353
+ this.solid_layer = solid_layer
354
+
355
+ this.path = path
356
+ this.can_highlight = false
357
+ this.viewerManager = viewer_manager
358
+ this.viewerObjectType = Enum.ViewerObjectTypeEnum['InitImage']
359
+ this.objectId = path // the path is unique, so we will use it as an id
360
+ // if (this.autoDelete === false){
361
+ // this.state = ViewerObjState['Unlink']
362
+ // }
363
+ }
364
+ async click(click_type) {
365
+ if (click_type === Enum.clickTypeEnum['Click']) {
366
+ //select layer
367
+ //turn the layer visible
368
+ //set the layer to active
369
+ this.visible(true)
370
+ await this.select(true) //select() does take arguments
371
+ this.active(true)
372
+ click_type = Enum.clickTypeEnum['Click'] // convert all click to Click
373
+ this.viewerManager.replaceLastSelection(click_type, this) //pass the click_type and this object
374
+ } else if (click_type === Enum.clickTypeEnum['ShiftClick']) {
375
+ this.visible(true)
376
+ await this.select(true) //select() does take arguments
377
+ this.active(true)
378
+ // if (this.viewerManager.last_selected_viewer_obj) {
379
+ // //if the last selected layer is valid then converted last selected layer into highlight layer
380
+ // this.viewerManager.last_selected_viewer_obj.setHighlight(true)
381
+ // }
382
+ click_type = Enum.clickTypeEnum['Click'] // convert all click to Click
383
+ this.viewerManager.replaceLastSelection(click_type, this) //pass the click_type and this object
384
+ }
385
+ // else if (click_type === Enum.clickTypeEnum['AltClick']) {
386
+ // // this.viewerManager.last_selected_viewer_obj = null
387
+ // this.setHighlight(false)
388
+ // this.visible(false)
389
+ // this.active(false)
390
+
391
+ // await psapi.unselectActiveLayersExe() //Note:can we move to ViewerManager.click()
392
+ // }
393
+ // this.viewerManager.replaceLastSelection(click_type, this) //pass the click_type and this object
394
+ }
395
+ visible(visibleOn) {
396
+ try {
397
+ super.visible(visibleOn)
398
+
399
+ let visibleValues = []
400
+ if (visibleOn) {
401
+ visibleValues = [true, true, true]
402
+ } else {
403
+ visibleValues = [false, false, false]
404
+ }
405
+
406
+ if (layer_util.Layer.doesLayerExist(this.init_group)) {
407
+ this.init_group.visible = visibleValues[0]
408
+ }
409
+ if (layer_util.Layer.doesLayerExist(this.init_snapshot)) {
410
+ this.init_snapshot.visible = visibleValues[1]
411
+ }
412
+
413
+ if (layer_util.Layer.doesLayerExist(this.solid_layer)) {
414
+ this.solid_layer.visible = visibleValues[2]
415
+ }
416
+
417
+ if (!this.autoDelete) {
418
+ //means it's not the first init image
419
+
420
+ if (layer_util.Layer.doesLayerExist(this.solid_layer)) {
421
+ this.solid_layer.visible = false //turn it off sense the init group is above the output group, and the white solid will hide the init image reference located in the output group
422
+ }
423
+ }
424
+ } catch (e) {
425
+ console.warn(e)
426
+ }
427
+ }
428
+
429
+ async select() {
430
+ super.select()
431
+
432
+ const selectLayers = []
433
+ if (layer_util.Layer.doesLayerExist(this.init_group)) {
434
+ selectLayers.push(this.init_group)
435
+ }
436
+
437
+ await psapi.selectLayersExe(selectLayers)
438
+ // console.log(`${this.layer.id} got selected`);
439
+ }
440
+
441
+ isSameLayer(layer_id) {
442
+ super.isSameLayer(layer_id)
443
+ let is_same = false
444
+ if (layer_util.Layer.doesLayerExist(this.init_group)) {
445
+ is_same = this.init_group.id == layer_id
446
+ }
447
+ return is_same
448
+ }
449
+ setImgHtml(img_html) {
450
+ super.setImgHtml()
451
+ this.img_html = img_html
452
+ }
453
+ async delete() {
454
+ try {
455
+ await super.delete()
456
+
457
+ // this.img_html.remove()//delete the img html element
458
+
459
+ if (!this.autoDelete && !this.can_highlight) {
460
+ //don't delete since it's output layer that is been used as init image
461
+ this.state = ViewerObjState['Unlink']
462
+ }
463
+
464
+ if (this.state === ViewerObjState['Delete']) {
465
+ await psapi.cleanLayers([
466
+ this.init_group,
467
+ this.init_snapshot,
468
+ this.solid_layer,
469
+ ])
470
+ }
471
+ } catch (e) {
472
+ console.warn(e)
473
+ }
474
+ }
475
+ }
476
+
477
+ class InitMaskImage extends ViewerImage {
478
+ constructor(mask_group, white_mark, solid_black, path, viewer_manager) {
479
+ super()
480
+ this.mask_group = mask_group
481
+ this.white_mark = white_mark
482
+ this.solid_black = solid_black
483
+
484
+ this.path = path
485
+ this.can_highlight = false
486
+ this.viewerManager = viewer_manager
487
+ this.viewerObjectType = Enum.ViewerObjectTypeEnum['MaskImage']
488
+ this.objectId = path // the path is unique, so we will use it as an id
489
+ }
490
+ async click(click_type) {
491
+ if (click_type === Enum.clickTypeEnum['Click']) {
492
+ //select layer
493
+ //turn the layer visible
494
+ //set the layer to active
495
+ this.visible(true)
496
+ await this.select(true) //select() does take arguments
497
+ this.active(true)
498
+ click_type = Enum.clickTypeEnum['Click'] // convert all click to Click
499
+ this.viewerManager.replaceLastSelection(click_type, this) //pass the click_type and this object
500
+ } else if (click_type === Enum.clickTypeEnum['ShiftClick']) {
501
+ this.visible(true)
502
+ await this.select(true) //select() does take arguments
503
+ this.active(true)
504
+ // if (this.viewerManager.last_selected_viewer_obj) {
505
+ // //if the last selected layer is valid then converted last selected layer into highlight layer
506
+ // this.viewerManager.last_selected_viewer_obj.setHighlight(true)
507
+ // }
508
+ click_type = Enum.clickTypeEnum['Click'] // convert all click to Click
509
+ this.viewerManager.replaceLastSelection(click_type, this) //pass the click_type and this object
510
+ }
511
+ }
512
+ visible(visibleOn) {
513
+ try {
514
+ super.visible(visibleOn)
515
+
516
+ let visibleValues = []
517
+ if (visibleOn) {
518
+ visibleValues = [true, true, false]
519
+ } else {
520
+ visibleValues = [false, false, false]
521
+ }
522
+
523
+ if (layer_util.Layer.doesLayerExist(this.mask_group)) {
524
+ this.mask_group.visible = visibleValues[0]
525
+ }
526
+ if (layer_util.Layer.doesLayerExist(this.white_mark)) {
527
+ this.white_mark.visible = visibleValues[1]
528
+ }
529
+ if (layer_util.Layer.doesLayerExist(this.solid_black)) {
530
+ this.solid_black.visible = visibleValues[2]
531
+ }
532
+ } catch (e) {
533
+ console.warn(e)
534
+ }
535
+ }
536
+
537
+ async select() {
538
+ super.select()
539
+
540
+ const selectLayers = []
541
+
542
+ if (layer_util.Layer.doesLayerExist(this.white_mark)) {
543
+ selectLayers.push(this.white_mark)
544
+ }
545
+
546
+ await psapi.selectLayersExe(selectLayers)
547
+ // console.log(`${this.layer.id} got selected`);
548
+ }
549
+
550
+ isSameLayer(layer_id) {
551
+ super.isSameLayer(layer_id)
552
+ let is_same = false
553
+ if (layer_util.Layer.doesLayerExist(this.mask_group)) {
554
+ is_same = this.mask_group.id == layer_id
555
+ }
556
+ return is_same
557
+ }
558
+ setImgHtml(img_html) {
559
+ super.setImgHtml()
560
+ this.img_html = img_html
561
+ }
562
+ async delete() {
563
+ try {
564
+ await super.delete()
565
+ // this.img_html.remove()//delete the img html element
566
+ if (this.state === ViewerObjState['Delete']) {
567
+ await psapi.cleanLayers([
568
+ this.mask_group,
569
+ this.white_mark,
570
+ this.solid_black,
571
+ ])
572
+ }
573
+ } catch (e) {
574
+ console.warn(e)
575
+ }
576
+ }
577
+ createThumbnailNew(img, _) {
578
+ this.img_html = img
579
+ this.thumbnail_container = thumbnail.Thumbnail.wrapImgInContainer(
580
+ img,
581
+ 'viewer-image-container'
582
+ )
583
+ thumbnail.Thumbnail.addSPButtonToContainer(
584
+ this.thumbnail_container,
585
+ 'svg_sp_btn',
586
+ 'update the mask',
587
+ setMaskViewer,
588
+ img
589
+ )
590
+ }
591
+ }
592
+
593
+ class initImageLayers {
594
+ //store info about the init image related layers
595
+ constructor(group, snapshot, solid_background, autoDelete) {
596
+ this.group = group
597
+ this.snapshot = snapshot
598
+ this.solid_background = solid_background
599
+ this.autoDelete = autoDelete
600
+ }
601
+ }
602
+ class maskLayers {
603
+ //store info about the init image related layers
604
+ constructor(group, white_mark, solid_background, autoDelete) {
605
+ this.group = group
606
+ this.white_mark = white_mark
607
+ this.solid_background = solid_background
608
+ this.autoDelete = autoDelete
609
+ }
610
+ }
611
+ class ViewerManager {
612
+ //viewer manager will reset after the end of the session
613
+ //it will store
614
+ constructor() {
615
+ this.outputImages = []
616
+ this.initImages = []
617
+ this.initMaskImage
618
+ this.pathToViewerImage = {} // quick way to check if an link image path on disk to ViewerImage object.
619
+ this.initImageLayersJson = {} //{path: initImageLayers}
620
+
621
+ this.selectedOutputImages = {} //store the selected output images {path: outputImage}
622
+
623
+ this.mask_layer
624
+ this.maskLayersJson = {} //{path: MaskLayers}
625
+
626
+ //Note:move initGroup, to GenerationSession
627
+ this.initGroup
628
+ this.init_solid_background
629
+ this.maskGroup
630
+ this.mask_solid_background
631
+
632
+ //last_selected_obj
633
+ this.last_selected_viewer_obj
634
+ this.thumbnail_scaler = 1
635
+ this.isSquareThumbnail = false
636
+ this.init_image_container = document.getElementById(
637
+ 'divInitImageViewerContainer'
638
+ )
639
+ }
640
+
641
+ replaceLastSelection(click_type, clicked_object) {
642
+ if (
643
+ this.last_selected_viewer_obj && // is valid last selected object
644
+ this.last_selected_viewer_obj.objectId !== clicked_object.objectId // not the same object
645
+
646
+ // clicked_object instanceof OutputImage &&
647
+ // !clicked_object.isSameObject(this.last_selected_viewer_obj)
648
+ ) {
649
+ //if the current selection and the last selection are different
650
+
651
+ this.last_selected_viewer_obj.visible(false)
652
+ this.last_selected_viewer_obj.active(false)
653
+ }
654
+ if (click_type === Enum.clickTypeEnum['Click']) {
655
+ this.last_selected_viewer_obj = clicked_object
656
+ } else if (click_type === Enum.clickTypeEnum['ShiftClick']) {
657
+ if (this.last_selected_viewer_obj) {
658
+ //if the last selected layer is valid then converted last selected layer into highlight layer
659
+ this.last_selected_viewer_obj.setHighlight(true)
660
+ }
661
+ this.last_selected_viewer_obj = clicked_object
662
+ } else if (click_type === Enum.clickTypeEnum['AltClick']) {
663
+ this.last_selected_viewer_obj = null
664
+ } else if (click_type === Enum.clickTypeEnum['SecondClick']) {
665
+ this.last_selected_viewer_obj = null
666
+ }
667
+ }
668
+ initializeInitImage(group, snapshot, solid_background, path) {
669
+ console.warn('this method is deprecated, use the session.js method ')
670
+ this.initGroup = group
671
+ this.init_solid_background = solid_background
672
+ this.addInitImageLayers(snapshot, path, true)
673
+ }
674
+ initializeMask(group, white_mark, solid_background, path, base64) {
675
+ this.maskGroup = group
676
+ this.mask_solid_background = solid_background
677
+ this.addMaskLayers(white_mark, path, true, base64)
678
+ }
679
+ addMaskLayers(white_mark, path, auto_delete, base64) {
680
+ try {
681
+ if (!this.maskLayersJson.hasOwnProperty(path)) {
682
+ //it's a new mask, mostly for the first time storing the mask
683
+
684
+ const mask_layers = new maskLayers(
685
+ this.maskGroup,
686
+ white_mark,
687
+ this.mask_solid_background,
688
+ auto_delete
689
+ )
690
+ this.maskLayersJson[path] = mask_layers
691
+ } else {
692
+ //for updating the mask
693
+
694
+ //just update the html
695
+ const new_path = `${path}?t=${new Date().getTime()}`
696
+ console.log('new mask path: ', new_path)
697
+ // this.maskLayersJson[path].img_html.src = new_path
698
+
699
+ // this.pathToViewerImage[path].img_html.src = new_path
700
+ this.pathToViewerImage[path].img_html.src = base64ToSrc(base64)
701
+ }
702
+ } catch (e) {
703
+ console.warn(e)
704
+ }
705
+ }
706
+ updateMaskLayer() {}
707
+ addInitImageLayers(snapshot, path, auto_delete) {
708
+ try {
709
+ if (!this.initImageLayersJson.hasOwnProperty(path)) {
710
+ //if this is a new init image
711
+ //store it all of layers in a container object
712
+ const init_image_layers = new initImageLayers(
713
+ this.initGroup,
714
+ snapshot,
715
+ this.init_solid_background,
716
+ auto_delete
717
+ )
718
+ this.initImageLayersJson[path] = init_image_layers
719
+ }
720
+ } catch (e) {
721
+ console.warn(e)
722
+ }
723
+ }
724
+
725
+ hasViewerImage(path) {
726
+ if (this.pathToViewerImage.hasOwnProperty(path)) {
727
+ return true
728
+ }
729
+ return false
730
+ }
731
+ addOutputImage(layer, path) {
732
+ const outputImage = new OutputImage(layer, path, this)
733
+
734
+ this.outputImages.push(outputImage)
735
+ this.pathToViewerImage[path] = outputImage //
736
+ return outputImage
737
+ }
738
+ addInitImage(group, snapshot, solid_background, path, auto_delete) {
739
+ const initImage = new InitImage(
740
+ group,
741
+ snapshot,
742
+ solid_background,
743
+ path,
744
+ this
745
+ )
746
+ initImage.setAutoDelete(auto_delete)
747
+ this.initImages.push(initImage)
748
+ this.pathToViewerImage[path] = initImage
749
+ return initImage
750
+ }
751
+ addMask(group, white_mark, solid_background, path) {
752
+ const mask = new InitMaskImage(
753
+ group,
754
+ white_mark,
755
+ solid_background,
756
+ path,
757
+ this
758
+ )
759
+
760
+ this.initMaskImage = mask
761
+ this.pathToViewerImage[path] = mask
762
+ return mask
763
+ }
764
+
765
+ scaleThumbnails(
766
+ original_width,
767
+ original_height,
768
+ min_width,
769
+ min_height,
770
+ scaler
771
+ ) {
772
+ //calculate the new width and height
773
+
774
+ const image_width = this.isSquareThumbnail
775
+ ? 100
776
+ : g_generation_session.last_settings.width
777
+ const image_height = this.isSquareThumbnail
778
+ ? 100
779
+ : g_generation_session.last_settings.height
780
+
781
+ const [new_width, new_height] = general.scaleToClosestKeepRatio(
782
+ image_width,
783
+ image_height,
784
+ 100,
785
+ 100
786
+ )
787
+ const [scaled_width, scaled_height] = [
788
+ new_width * scaler,
789
+ new_height * scaler,
790
+ ]
791
+
792
+ for (let outputImage of this.outputImages) {
793
+ //get the image and it's container
794
+ const img = outputImage.img_html
795
+ const img_container = img.parentElement
796
+
797
+ img_container.style.width = scaled_width
798
+ img_container.style.height = scaled_height
799
+ img.style.width = scaled_width
800
+ img.style.height = scaled_height
801
+ //scale them to the new dimensions
802
+ }
803
+ }
804
+ onSessionEnd() {
805
+ this.outputImages = []
806
+ this.initImages = []
807
+ this.initMaskImage = null
808
+
809
+ this.pathToViewerImage = {} // quick way to check if an link image path on disk to ViewerImage object.
810
+ this.initImageLayersJson = {} //{path: initImageLayers}
811
+
812
+ this.selectedOutputImages = {} //store the selected output images {path: outputImage}
813
+
814
+ this.mask_layer = null
815
+ this.maskLayersJson = {} //{path: MaskLayers}
816
+
817
+ //Note:move initGroup, to GenerationSession
818
+ this.initGroup = null
819
+ this.init_solid_background = null
820
+ this.maskGroup = null
821
+ this.mask_solid_background = null
822
+
823
+ //last_selected_obj
824
+ this.last_selected_viewer_obj = null
825
+ // this.thumbnail_scaler = 1
826
+ // this.isSquareThumbnail = false
827
+ }
828
+
829
+ async loadInitImageViewerObject(path) {
830
+ if (!g_viewer_manager.hasViewerImage(path)) {
831
+ const group = this.initImageLayersJson[path].group
832
+ const snapshot = this.initImageLayersJson[path].snapshot
833
+ const solid_background =
834
+ this.initImageLayersJson[path].solid_background
835
+ const auto_delete = this.initImageLayersJson[path].autoDelete
836
+ const base64_image = g_generation_session.base64initImages[path]
837
+ await loadInitImageViewerObject(
838
+ group,
839
+ snapshot,
840
+ solid_background,
841
+ path,
842
+ auto_delete,
843
+ base64_image
844
+ )
845
+ }
846
+ }
847
+ deleteAll() {}
848
+ keepAll() {}
849
+ keepSelected() {}
850
+ deleteSelected() {}
851
+ deleteInitImages() {}
852
+ deleteMask() {}
853
+ }
854
+
855
+ module.exports = {
856
+ OutputImage,
857
+ InitImage,
858
+ InitMaskImage,
859
+ ViewerObjState,
860
+ ViewerManager,
861
+ }
Stable-Diffusion-Webui-Civitai-Helper/.github/ISSUE_TEMPLATE/simple-issue-template.md ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ name: Simple Issue template
3
+ about: Describe this issue template's purpose here.
4
+ title: ''
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ ## Have you read document?
11
+
12
+ ## Have you checked console log window's msg?
13
+
14
+ ## Describe Issue
15
+
16
+
17
+ ## Screenshot for UI issue
18
+
19
+
20
+ ## Console log's msg or screenshot for function issue
Stable-Diffusion-Webui-Civitai-Helper/.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ scripts/__pycache__/
2
+ scripts/ch_lib/__pycache__/
3
+ setting.json
Stable-Diffusion-Webui-Civitai-Helper/README.cn.md ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## 关于Civitai Helper2: Model Info Helper
2
+ Civitai助手2将改名为**Model Info助手**。目前还在缓慢开发中。你可以查看它的UI演示视频,了解它会是什么样子:
3
+ [https://youtu.be/mPcKwQDDH8s](https://youtu.be/mPcKwQDDH8s)
4
+
5
+
6
+ # Civitai Helper
7
+ Stable Diffusion Webui 扩展Civitai助手,用于更轻松的管理和使用Civitai模型。
8
+
9
+ [Civitai Url](https://civitai.com/models/16768/civitai-helper-sd-webui-civitai-extension)
10
+
11
+ # 注意
12
+ **本插件现在非常稳定,很多人用得很好,如果碰到问题,先看[常见问题](#常见问题),并检查命令行窗口的详情。**
13
+ 开issue前,请先看文档。找茬行为的用户将会被拉黑,参考:[找茬行为会被拉黑](https://github.com/butaixianran/Stable-Diffusion-Webui-Civitai-Helper/issues/96#issuecomment-1500310981)
14
+
15
+
16
+ # 功能
17
+ [中文介绍视频(非官方)](https://youtu.be/x4tPWPmeAgM?t=373)
18
+
19
+ * 扫描所有模型,从Civitai下载模型信息和预览图
20
+ * 通过civitai模型页面url,连接本地模型和civitai模型信息
21
+ * 通过Civitai模型页面url,下载模型(含信息和预览图)到SD目录或子目录。
22
+ * 下载支持断点续传
23
+ * 批量检查本地模型,在civitai上的新版本
24
+ * 直接下载新版本模型到SD模型目录内(含信息和预览图)
25
+ * 修改了内置的"Extra Network"模型卡片,每个卡片增加了如下功能按钮:
26
+ - 🖼: 修改文字"replace preview"为这个图标
27
+ - 🌐: 在新标签页打开这个模型的Civitai页面
28
+ - 💡: 一键添加这个模型的触发词到关键词输入框
29
+ - 🏷: 一键使用这个模型预览图所使用的关键词
30
+ * 以上额外功能按钮支持thumbnail模式
31
+ * 增加一直显示按钮的选项,以供触屏用户使用
32
+
33
+
34
+ # 安装
35
+ 下载本项目为zip文件,解压到`你的SD webui目录/extensions`下即可。
36
+
37
+ 不管是安装还是升级本插件,都要整个关闭SD Webui,重新启动它。只是Reload UI不起作用。
38
+
39
+ (如果用SD webui的插件界面安装,请先给git配置代理。它不是通过浏览器下载,是通过git下载。)
40
+
41
+
42
+ # 使用方法
43
+
44
+ ## 更新你的SD webui
45
+ 本扩展需要取到 Extra Network的卡片列表id。**这个是2023-02-06,才添加到SD webui里面的。**
46
+
47
+ 所以,如果你用的版本比这个早,你就需要先更新你的SD Webui!
48
+
49
+
50
+ ## 扫描模型
51
+ 前往扩展页面"Civitai Helper",有个按钮叫:"Scan Model"
52
+
53
+ ![](img/extension_tab.jpg)
54
+
55
+ 点击,就会扫描所有模型,生成SHA256码,用于从civitai获取模型信息和预览图。**扫描需要很久,耐心等待**。
56
+
57
+ 每个模型,本扩展都会创建一个json文件,用来保存从civitai得到的模型信息。这个文件会保存在模型同目录下,名称为:"模型名字.civitai.info"。
58
+
59
+ ![](img/model_info_file.jpg)
60
+
61
+ 如果模型信息文件已经存在,扫描时就会跳过这个模型。如果模型不是civitai的,就会创建个空信息文件,以避免以后重复扫描。
62
+
63
+ ### 添加新模型
64
+ 当你下载了新模型之后,只要再次点击扫描按钮即可。已经扫描过的文件不会重复扫描,会自动得到新模型的信息和预览图。无须重启SD webui。
65
+
66
+ ## 模型卡片
67
+ **(先完成扫描,再使用卡片功能)**
68
+ 打开SD webui's 内置的 "Extra Network" 页面,显示模型卡片
69
+
70
+ ![](img/extra_network.jpg)
71
+
72
+
73
+ 移动鼠标到模型卡片底部,就会显示4个按钮:
74
+ - 🖼: 修改文字"replace preview"为这个图标
75
+ - 🌐: 在新标签页打开这个模型的Civitai页面
76
+ - 💡: 一键添加这个模型的触发词到关键词输入框
77
+ - 🏷: 一键使用这个模型预览图所使用的关键词
78
+
79
+ ![](img/model_card.jpg)
80
+
81
+ 如果你没有看到这些额外的按钮,只要点击`Refresh Civitai Helper`,他们就会被重新添加到卡片上。
82
+
83
+ ![](img/refresh_ch.jpg)
84
+
85
+ 每次当Extra Network刷新,他都会删除掉额外的修改,我们的按钮就会消失。这时你就需要点击`Refresh Civitai Helper`把这些功能添加回去。
86
+
87
+
88
+ ### 小图模式
89
+ 以上功能按钮支持小图模式,但受制于SD Webui的CSS问题,目前,只能要么一直显示,要么一直不显示,不能鼠标滑过才显示。
90
+ ![](img/thumb_mode.jpg)
91
+
92
+ ## 下载
93
+ **(单任务,下载完一个再下另一个)**
94
+ 通过Civitai模型页面Url下载模型,要3个步骤:
95
+ * 填入url,点击按钮获取模型信息
96
+ * 扩展会自动填入模型名称和类型,你需要选择下载的子目录和模型版本。
97
+ * 点击下载
98
+ ![](img/download_model.jpg)
99
+
100
+ 下载过程会显示在命令行界面带个进度条。
101
+ 支持断点续传,无畏大文件。
102
+
103
+
104
+ ## 批量检查模型新版本
105
+ 你可以按照模型类型,批量检查你的本地模型,在civitai上的新版本。你可以选择多个模型类型。
106
+ ![](img/check_model_new_version.jpg)
107
+
108
+ 检查新版本的时候,每检查完一个模型,都会有一个1秒的延迟,所以速度有点慢。
109
+
110
+ 这是为了保护Civitai避免因为本插件而短暂陷入类似DDos的局面。有些云服务商,有类似“免费用户每秒API请求不能超过1次”的保护机制。Civitai还没有这种设置。但我们还是得自觉保护它。因为如果它挂了,对大家都没有好处。
111
+
112
+ **检查完毕之后**,就会如下图,在UI上显示所有找到的新版本的信息。
113
+
114
+ 每个模型新版本,都有3个链接。
115
+ * 第一个是这个模型的网页。
116
+ * 第二个是这个新版本的下载地址。
117
+ * 第三个是个按钮,在python端,直接下载新版本到模型目录内。
118
+ 这种方式下载,下载详情显示在"Download Model"的区域和命令行窗口中。一次一个任务,不支持多任务。
119
+ ![](img/check_model_new_version_output.jpg)
120
+
121
+
122
+
123
+ ## 根据URL获取模型信息
124
+ 如果无法在civitai上找到你的模型的SHA256,但你还是希望能把你的模型连接到一个civitai模型,你可以在本扩展页面,从列表中选择你的模型,并提供一个civitai模型页面的url。
125
+
126
+ 点击按钮之后,扩展就会下载那个civitai模型的信息,作为你这个本地模型的信息使用。
127
+
128
+ ![](img/get_one_model_info.jpg)
129
+
130
+ ## 代理
131
+ **如果你是刚更新新版本,你需要重启SD webui再来使用**
132
+
133
+ 代理输入框在插件页面最下方。
134
+
135
+ **每次填入或清除代理后,都要保存,并用SDwebui设置页面的Reload UI按钮刷新UI**
136
+
137
+ 然后所有发到civitai的请求就会用代理。
138
+
139
+ 有些sock5代理, 需要使用socks5h开头的形式"socks5h://xxxxx"才能生效。
140
+
141
+
142
+
143
+ ## 其他设置
144
+ **保存设置按钮, 会保存扫描模型区域,以及其他设置 这两个区域的选项**
145
+
146
+ * "一直显示按钮" 是为了方便触屏。
147
+ * "小图模式显示功能按钮" 会开关功能按钮在小图模式的显示
148
+ ![](img/other_setting.jpg)
149
+
150
+ ## 预览图
151
+ Extra Network支持两种预览图命名:`model_name.png` 和 `model_name.preview.png`。其中,`model_name.png`优先级较高。
152
+
153
+ 当优先级较高的预览图不存在,他就会自动使用`model_name.preview.png`。
154
+
155
+ 这样,你自己创建的预览图 和 网络下载的预览图,能够同时存在,并优先使用你自己创建的。
156
+
157
+ ## 关键词
158
+ 卡片上,添加关键词按钮,是添加从civitai预览图中得到的关键词,而不是你自己创建的图片的关键词。
159
+
160
+ civitai不是每个图片都有关键词,一个模型中,也不是所有预览图关键词都一样。所以这里是遍历所有civitai预览图信息,加载第一个有关键词的。
161
+
162
+
163
+ ## SHA256
164
+ 为了创建文件的SHA256,插件需要读取整个文件。对于大尺寸文件,就会很慢。
165
+
166
+ 有两种情况,这个SHA256无法从civitai找到对应模型:
167
+ * 太老的模型,civitai没有存储SHA256.
168
+ * 模型作者,静静的换掉了模型文件,但没有修改描述和版本。所以,虽然网页上看不出来,但实际上civitai上的 和你本地的模型文件,已经不是同一个文件了。
169
+
170
+ 这些情况下,你可以在插件上,通过提供模型页面的url,来获取模型信息文件。
171
+
172
+
173
+
174
+ ## 新特性
175
+ 从v1.5开始,v1.x不再接受任何新特性。所有新特性进入2.x。
176
+
177
+ 2.x专注于自定义模型信息,并可能改名为"Model Info Helper"。因为不再是专注Civitai了。
178
+
179
+ 从v1.5开始。v1.x进入维护阶段。
180
+
181
+
182
+ Enjoy!
183
+
184
+
185
+ ## 常见问题
186
+ ### 4个卡片按钮不显示
187
+ #### 汉化原因
188
+ 下载新版,最新版已经处理汉化导致的问题。**双语汉化插件需要v1.6.1.1之后的版本才开始支持。**
189
+
190
+ #### 使用了云端汉化功能
191
+ 如果是秋叶启动器,就关闭启动器“云端汉化”功能。如果是专门的云端汉化插件,就换用普通汉化插件。
192
+
193
+ #### 其他情况
194
+ 首先,确保你点过了"Refresh Civitai Helper"刷新按钮。
195
+
196
+ 然后,如果还有这个问题,那么唯一原因,是你没有使用最新版SD webui。
197
+
198
+ 如果你修改过SD webui的文件, 你的更新操作可能会失败。你需要检查git命令行的输出信息,来确定你更新成功了。
199
+
200
+ git在很多时候,会拒绝升级,并告诉你有些冲突需要你手动先解决。如果你不看命令行输出,你就会以为你已经更新成功了,但其实并没有。
201
+
202
+
203
+ ### Request model info from civitai
204
+ 意思就是正在连接civitai,如果没有后面的信息,就是连不上,请挂代理。
205
+
206
+
207
+ ### 扫描或获取模型信息失败
208
+ 这个插件现在很稳定,所以,这个问题的原因,基本是是因为Civitai拒绝了你的连接请求。
209
+
210
+ Civitai不像那些大网站那么稳定。他网站会挂,会拒绝API连接,还会把API请求转到真人验证页面,来挡住。
211
+
212
+ Civitai还有连接池的设定。基本上,就是同时能允许的最大连接数。一旦达到这个数字,接下来的API连接请求,都会被拒绝。
213
+
214
+ 所以,这种时候你只能等一下再试。
215
+
216
+ 另外,对于国��用户,还有代理问题。现在国内都要用代理才能连上。
217
+
218
+
219
+ ### 扫描之后得到了错误的预览图和模型信息
220
+ 坏消息是,有些模型在civitai数据库中,保存的sha256完全是错的。查看下面的issue了解详情:
221
+ [https://github.com/civitai/civitai/issues/426](https://github.com/civitai/civitai/issues/426)
222
+
223
+ 对于这种模型,那这个插件自然就无法获得正确的模型信息和预览图。
224
+
225
+ 这种情况下,请删除扫描得到的模型信息和预览图,在插件界面提供正确的模型url来获取。
226
+
227
+ 另外,civitai官方有个页面,专门用于回报带有错误sha256的模型:
228
+ [https://discord.com/channels/1037799583784370196/1096271712959615100/1096271712959615100](https://discord.com/channels/1037799583784370196/1096271712959615100/1096271712959615100)
229
+
230
+ 请把这类模型反馈给civitai,好让他们进行修复。
231
+
232
+
233
+
234
+
235
+ ### 使用colab时扫描失败
236
+ 首先,在google中搜索你看到的错误信息。更有可能是,你碰到的是个colab的问题。
237
+
238
+ 然后,如果colab连接了google drive,会有一次性访问文件数量的限制,而导致扫描失败。这是google drive的限制,请自行google搜索了解详情。
239
+
240
+
241
+
Stable-Diffusion-Webui-Civitai-Helper/README.jp.md ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ### Language
2
+ [中文](README.cn.md)
3
+ [English](README.md)
4
+ [한국어(ChatGPT)](README.kr.md)
5
+
6
+ ## About Civitai Helper2: Model Info Helper
7
+ Civitai Helper 2は、**ModelInfo Helper**に改名されます。現在開発中です。デモをご覧ください:
8
+ [YouTube](https://youtu.be/mPcKwQDDH8s)
9
+
10
+ # お知らせ
11
+ **この拡張機能は現在、非常に安定しています。もし問題があれば、コンソールログの詳細を確認し、[よくある質問](#よくある質問)を確認してください。**
12
+
13
+ # Civitai Helper
14
+ この拡張機能は、Civitaiのモデルをより簡単に扱えるようにするためのものです。
15
+
16
+ Civitai: [Civitai Url](https://civitai.com/models/16768/civitai-helper-sd-webui-civitai-extension)
17
+
18
+ # 機能
19
+ * 全てのモデルをスキャンし、Civitaiからモデル情報とプレビューをダウンロード
20
+ * CivitaiモデルページのURLを使って、ローカルモデルとCivitaiモデル情報を取得
21
+ * CivitaiモデルページのURLから、モデル(情報とプレビューを含む)をSDディレクトリまたはサブディレクトリにダウンロードする。
22
+ * ダウンロードは途中から再開可能
23
+ * ローカルのモデルとCivitai上の新しいバージョンを一括でチェック
24
+ * 新しいバージョンのモデルを直接モデルのディレクトリにダウンロード(情報とプレビュー画像を含む)
25
+ * 内蔵の**Extra Network**モデルカードを変更し、各カードに以下の機能ボタンを追加しました。
26
+ - 🖼: `replace preview`のテキストをこのアイコンに変更
27
+ - 🌐: このモデルのCivitaiページを新しいタブで開く
28
+ - 💡: このモデルのトリガーワードをキーワード入力欄に一括で追加する
29
+ - 🏷: このモデルのプレビュー画像で使用されているキーワードを一括で使用する
30
+ * 上記の追加機能ボタンは、サムネイルモードにも対応しています。
31
+ * タッチスクリーンデバイス向けに、常に表示されるボタンのオプションを追加しました。
32
+
33
+
34
+ # インストール
35
+ SD webui's extensionタブから、`Install from url`のタブに移動。
36
+ このリポジトリのURLをコピーペーストし、インストールする。
37
+
38
+ または、このリポジトリをzipでダウンロードし、`./webui/extensions`へ展開してください。
39
+
40
+ この拡張機能をインストール、またはアップデートするたびに、SD Webui再起動する必要があります。
41
+ この拡張機能は、**UIを再読み込みする**だけでは動作しません。
42
+
43
+ # 使い方
44
+
45
+ ## WebUIをアップデート
46
+ この拡張機能は`network cards id`を取得する必要があります。この機能は**2023-02-06**に追加されました。
47
+ **SD webuiがこれより前のバージョンである場合は、アップデートする必要があります!**
48
+
49
+ ## モデルのスキャン
50
+ 拡張機能タブから<kbd>Civitai Helper</kbd>へ。
51
+ <kbd>Scan model</kbd>というボタンがあります。
52
+
53
+ ![](img/extension_tab.jpg)
54
+
55
+ これをクリックすると、拡張機能がすべてのモデルをスキャンしてSHA256ハッシュを生成し、それを使ってCivitaiからモデル情報とプレビュー画像を取得します。
56
+ **スキャンには時間がかかります。 終了までお待ちください。**
57
+
58
+ 各モデルに対して、Civitaiからすべてのモデル情報を保存するためのjsonファイルを作成します。このモデル情報ファイルは、modelsディレクトリ内の`Your_model_name.civitai.info`となります。
59
+
60
+ ![](img/model_info_file.jpg)
61
+
62
+ モデル情報ファイルがすでに存在する場合は、スキップされます。Civitaiでモデルが見つからない場合、空のモデル情報ファイルを作成するので、モデルが2回スキャンされることはありません。
63
+
64
+ ### 新しいモデルを追加
65
+ 新規のモデルがある場合、もう一度スキャンボタンをクリックするだけで、新しいモデルの情報とプレビューを取得できます。同じモデルを2回スキャンすることはありません。
66
+
67
+ ## モデルカード
68
+ **(スキャン終了後に使用)**
69
+ SD webuiの`Extra Network`タブを開き、モデルカードを表示します。
70
+
71
+ ![](img/extra_network.jpg)
72
+
73
+
74
+ マウスをモデルカードの下部に移動すると、4つのボタンが表示されます。
75
+ - 🖼: プレビューを置き換えるためのテキストを`replace preview`からこのアイコンに変更します
76
+ - 🌐: このモデルのCivitaiページを新しいタブで開きます
77
+ - 💡: このモデルのトリガーワードをキーワード入力欄に一括追加します
78
+ - 🏷: このモデルのプレビュー画像に使用されているキーワードを一括で使用します
79
+
80
+ ![](img/model_card.jpg)
81
+
82
+ これらのボタンが表示���れない場合は、<kbd>Refresh Civitai Helper</kbd>をクリックすると、ボタンがカードに再追加されます。
83
+
84
+ ![](img/refresh_ch.jpg)
85
+
86
+ `Extra Network`が更新されるたびに、余分な変更が削除され、ボタンが消えてしまいます。その場合は、「Refresh Civitai Helper」をクリックして、これらの機能を再度追加する必要があります。
87
+
88
+
89
+ ### サムネイル
90
+ これらのボタンは、サムネイルをサポートしていますが、SD WebuiのCSSの問題により、現在は常に表示か非表示かのどちらかに制限されています。マウスをスライドして表示することはできません。
91
+ ![](img/thumb_mode.jpg)
92
+
93
+
94
+ ## ダウンロード
95
+ **(タスクが一つ完了してから、次のタスクをダウンロードしてください)**
96
+ CivitaiモデルページのURLを使用してモデルをダウンロードするには、3つのステップが必要です。
97
+ 1. URLを入力し、モデル情報を取得するためにボタンをクリック
98
+ 2. 拡張機能が自動的にモデル名とタイプを入力します。ダウンロードするサブディレクトリとモデルバージョンを選択
99
+ 3. ダウンロードをクリックします
100
+ ![](img/download_model.jpg)
101
+
102
+ ダウンロード状況は、CLIに進行状況バーを表示します。
103
+ 断片的に再開することができ、大きなファイルをダウンロードする際にも心配する必要はありません。
104
+
105
+
106
+ ## 新しいモデルのバージョンを確認する
107
+ モデルの種類に従って、ローカルのモデルを一括でCivitaiの新バージョンがないかをチェックすることができます。複数のモデルの種類を選択できます。
108
+ ![](img/check_model_new_version.jpg)
109
+
110
+ これを押すと、各モデルをチェックするたびに1秒の遅延が発生するため、速度がやや遅くなります。
111
+
112
+ これは、本拡張機能のユーザーの過失によるDDoSを回避し、Civitaiを保護するために行われます。
113
+ 一部のクラウドサービスプロバイダーには、「無料ユーザーのAPIリクエストは1秒あたり1回を超えてはいけない」というような保護があります。Civitaiにはまだこのような設定がありませんが、我々はそれを自衛しなければなりません。
114
+ なぜなら、もしCivitaiがダウンした場合、誰にとっても良いことではないからです。
115
+
116
+ チェックが完了すると、すべての新しいバージョンがUIに表示されます。
117
+
118
+ 各モデルの新しいバージョンには、3つのリンクがあります。
119
+ * 最初のものは、このモデルのWebページです。
120
+ * 2つ目は、この新しいバージョンのダウンロードアドレスです。
121
+ * 3つ目は、Python(拡張機能)側で新しいバージョンをモデルディレクトリに直接ダウンロードするボタンです。
122
+ この方法でダウンロードすると、ダウンロードの詳細が「Download Model」の領域とコマンドラインに表示されます。一度に1つのタスクしかサポートされていません。
123
+ ![](img/check_model_new_version_output.jpg)
124
+
125
+
126
+
127
+ ## URLからモデル情報を取得する
128
+ Civitai上で自分のモデルのSHA256が見つからない場合でも、自分のモデルをCivitaiモデルに接続したい場合は、この拡張機能のページから、モデルをリストから選択し、CivitaiモデルページのURLを提供することができます。
129
+
130
+ ボタンをクリックすると、拡張機能はCivitaiモデルの情報をダウンロードし、それをローカルモデルの情報として使用します。
131
+
132
+ ![](img/get_one_model_info.jpg)
133
+
134
+
135
+
136
+ ## その他の設定
137
+ **設定保存ボタンを押すと、<kbd>Scan Model</kbd>の設定とその他の設定の両方が保存されます。**
138
+
139
+ * <kbd>Always Display Button</kbd>は、タッチデバイスでの操作を容易にするためです。
140
+ * <kbd>Show Buttons on Thumb Mode</kbd>は、小さな画像モードでの機能ボタンの表示を切り替えます。
141
+ ![](img/other_setting.jpg)
142
+
143
+ ## プレビュー
144
+ Extra Networkは、2つのプレビュー画像の命名をサポートしています:`model_name.png`と`model_name.preview.png`。
145
+ デフォルトでは自動で`model_name.png`が優先的に使われます。
146
+
147
+ 優先度が高いプレビュー画像が存在しない場合は、自動的に`model_name.preview.png`が使用されます。
148
+
149
+ これにより、自分で作成したプレビュー画像とネットからダウンロードしたプレビュー画像を同時に使用し、自分で作成したプレビュー画像を優先的に使用できます。
150
+
151
+ ## プロンプト
152
+ カード上の<kbd>Use prompt from preview image</kbd>ボタンは、Civitaiプレビュー画像から取得したキーワードであり、自分で作成した画像のキーワードではありません。
153
+
154
+ Civitaiにはすべての画像にキーワードがあるわけではなく、1つのモデルに含まれるすべてのプレビュー画像のキーワードが同じであるわけでもありません。したがって、ここではすべてのCivitaiプレビュー画像情報を走査し、最初にキーワードがあるものを読み込みます。
155
+
156
+
157
+ ## SHA256
158
+ ファイルのSHA256を作成するために、はファイル全体を読み取る必要があります。大きなファイルの場合、処理が遅くなります。
159
+
160
+ Civitaiで対応するモデルのSHA256が見つからない場合は、次の2つの場合が考えられます:
161
+ * 古すぎるモデルには、SHA256が保存されていません。
162
+ * モデルの作成者が静かにモデルファイルを変更しましたが、説明やバージョンを変更していないため、サイト上ではわかりませんが、実際にはCivitaiに保存されているモデルファイルとローカルのモデルファイルは異なるものとなっています。
163
+
164
+ これらの場合は、拡張機能にモデルページのURLを提供することで、モデルの情報ファイルを取得できます。
165
+
166
+ ## Feature Request
167
+ v1.5以降のv1.xには新機能はありません。すべての新機能は2.xに移行されます。
168
+ 2.xでは、カスタムモデル情報にフォーカスし、Civitaiだけではなく、`Model Info Helper`という名称に変更する可能性があります。
169
+ v1.5からv1.xはメンテナンスのフェーズに入ります。
170
+
171
+ お楽しみに!
172
+
173
+
174
+ ## よくある質問
175
+ ### 4つのカードボタンが表示されない
176
+ #### ローカライズの問題
177
+ 新しいバージョンをダウンロードしてください。
178
+ 最新バージョンでは、ローカライズによる問題が解決されています。
179
+ [バイリンガル拡張機能](https://github.com/journey-ad/sd-webui-bilingual-localization)は、v1.6.1.1以降のバージョンでサポートされるようになりました。
180
+
181
+ #### クラウドサービスベースの翻訳機能を使用した
182
+ クラウドサービスベースの翻訳機能を使用している場合は、通常のローカライズに変更してください。
183
+
184
+ #### その他の場合
185
+ まず、<kbd>Refresh Civitai Helper</kbd>をクリックして更新しましたか?
186
+
187
+ それでもこの問題が発生する場合は、おそらく最新バージョンのSD webuiを使用していないためです。
188
+
189
+ SD webuiのファイルを変更した場合、更新操作が失敗する可能性があります。更新が成功したかどうかを確認するには、gitコマンドラインの出力情報を確認する必要があります。
190
+
191
+ gitは、多くの場合、アップグレードを拒否し、手動で解決する必要があるいくつかの競合状態を示します。コマンドライン出力を見ない場合、更新が成功したと思うかもしれませんが、実際には成功していません。
192
+
193
+
194
+ ### Request model info from civitai
195
+ これはcivitaiに接続しています。情報がない場合は接続できないため、プロキシを使用してください。
196
+
197
+
198
+ ### スキャンまたはモデル情報の取得に失敗しました
199
+ この拡張機能は現在非常に安定しているため、この問題の原因は基本的にはCivitaiが接続要求を拒否したためです。
200
+
201
+ Civitaiは大きなウェブサイトとは異なり、安定していません。彼らのウェブサイトはダウンしたり、API接続を拒否したり、APIリクエストをCpatchaページに転送してブロックしたりすることがあります。
202
+
203
+ Civitaiには接続プールの上限もあります。基本的に、同時に許可される最大接続数です。この数字に達すると、以降のAPI接続要求はすべて拒否されます。
204
+
205
+ そのため、このような場合はしばらく待ってから再試行するしかありません。
206
+
207
+ ### civitaiから誤ったモデル情報とプレビュー画像を取得する(Translated by ChatGPT)
208
+ 悪いニュースですが、civitaiのデータベースには誤ったsha256で保存されたモデルがいくつかあります。詳細についてはこちらをご覧ください:
209
+ [https://github.com/civitai/civitai/issues/426](https://github.com/civitai/civitai/issues/426)
210
+
211
+ したがって、これらのモデルについては、この拡張機能では正しいモデル情報やプレビュー画像を取得できません。
212
+
213
+ この場合、モデル情報ファイルを削除し、この拡張機能のタブページでcivitaiのURLから正しいモデル情報を取得する必要があります。
214
+
215
+ また、誤ったsha256を持つこれらのモデルをcivitaiに報告することもできます。
216
+ [https://discord.com/channels/1037799583784370196/1096271712959615100/1096271712959615100](https://discord.com/channels/1037799583784370196/1096271712959615100/1096271712959615100)
217
+
218
+ civitaiにそのモデルを報告して修正してもらうようにしてください。
219
+
220
+
221
+ ### colabを使用した際にスキャンに失敗する
222
+ まず、表示されたエラーメッセージをGoogleで検索してください。おそらくcolabの問題が発生している可能性があります。表示されたエラーメッセージを検索して、原因を特定してください。
223
+
224
+ Google Driveに接続する際には、ファイルへのアクセス数に制限があるため、スキャンが失敗することがよくあります。これはGoogle Drive側の制限です。詳細についてはインターネットで[検索](https://google.com)してください。
Stable-Diffusion-Webui-Civitai-Helper/README.kr.md ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Civitai Helper
2
+ Stable Diffusion Webui는 Civitai 모델을 더 쉽게 관리하고 사용하기 위한 Civitai Assistant 확장 기능입니다.
3
+
4
+ [Civitai Url](https://civitai.com/models/16768/civitai-helper-sd-webui-civitai-extension)
5
+
6
+ # 주의사항
7
+ **이 플러그인은 지금 매우 안정적이며 많은 사용자들이 잘 사용하고 있습니다. 문제가 발생하면, [자주 묻는 질문](#자주-묻는-질문)을 먼저 확인하고 명령 프롬프트 창의 세부 정보를 확인하세요.**
8
+
9
+
10
+
11
+
12
+ # 기능
13
+ * 모든 모델을 스캔하여 Civitai에서 모델 정보 및 미리보기 이미지 다운로드
14
+ * Civitai 모델 페이지 URL을 통해 로컬 모델 및 Civitai 모델 정보 연결
15
+ * Civitai 모델 페이지 URL을 통해 모델(정보 및 미리보기 이미지 포함) 다운로드하여 SD 디렉토리 또는 하위 디렉토리에 저장
16
+ * 이어받기 지원 다운로드
17
+ * 로컬 모델에서 Civitai에 새 버전이 있는지 일괄 확인
18
+ * 새 버전 모델을 SD 모델 디렉토리에 직접 다운로드(정보 및 미리보기 이미지 포함)
19
+ * "Extra Network" 모델 카드 내부를 수정하여 다음과 같은 기능 버튼을 추가:
20
+ - 🖼: "replace preview" 텍스트를 이 아이콘으로 변경
21
+ - 🌐: 해당 모델의 Civitai 페이지를 새 탭에서 열기
22
+ - 💡: 이 모델의 트리거 단어를 키워드 입력란에 일괄 추가
23
+ - 🏷: 이 모델 미리보기 이미지에 사용된 키워드 사용
24
+ * 위의 추가 기능 버튼은 썸네일 모드를 지원합니다.
25
+ * 터치 스크린 사용자를 위해 항상 표시되는 버튼 옵션 추가
26
+
27
+
28
+ # 설치
29
+ 이 프로젝트를 zip 파일로 다운로드하고 SD webui 디렉토리/extensions에 압축 해제하면 됩니다.
30
+
31
+ 이 플러그인을 설치하거나 업그레이드하려면 SD Webui를 완전히 종료하고 다시 시작해야 합니다. UI 다시로드는 작동하지 않습니다.
32
+
33
+ # 사용 방법
34
+
35
+ ## SD Webui 업데이트
36
+ 이 확장 기능은 Extra Network 카드 목록 ID를 가져와야 합니다. 이것은 2023-02-06에 SD Webui에 추가된 것입니다.
37
+
38
+ 따라서, 만약 사용 중인 버전이 이보다 이전 버전이라면, 먼저 SD Webui를 업데이트해야 합니다!
39
+
40
+
41
+ ## 모델 스캔
42
+ 확장 페이지 "Civitai Helper"로 이동하고 "Scan Model"이라는 버튼을 클릭합니다.
43
+
44
+ ![](img/extension_tab.jpg)
45
+
46
+ 클릭하면 모든 모델을 스캔하고 SHA256 코드를 생성하여 Civitai에서 모델 정보 및 미리보기 이미지를 가져옵니다. 스캔에는 시간이 걸리므로 인내심을 가지고 기다려주세요.
47
+
48
+ 이 확장 기능은 각 모델마다 Civitai에서 얻은 모델 정보를 저장하는 JSON 파일을 생성합니다. 이 파일은 모델이 있는 디렉토리에 "모델 이름.civitai.info"라는 이름으로 저장됩니다.
49
+
50
+ ![](img/model_info_file.jpg)
51
+
52
+ 모델 정보 파일이 이미 존재하는 경우 해당 모델은 스캔하지 않습니다. 모델이 Civitai가 아닌 경우 빈 정보 파일이 생성되어 나중에 중복 스캔을 피합니다.
53
+
54
+ ### 새 모델 추가
55
+ 새 모델을 다운로드한 후 스캔 버튼을 다시 클릭하면 됩니다. 이미 스캔된 파일은 다시 스캔하지 않으며 새 모델의 정보와 미리보기 이미지를 자동으로 얻을 수 있습니다. SD Webui를 다시 시작할 필요가 없습니다.
56
+
57
+ ## 모델 카드
58
+ **(스캔을 완료한 후에 카드 기능을 사용하세요)**
59
+ SD Webui의 내장 "Extra Network" 페이지를 열어 모델 카드를 표시합니다.
60
+
61
+ ![](img/extra_network.jpg)
62
+
63
+
64
+ 마우스를 모델 카드 아래쪽으로 이동하면 4개의 버튼이 표시됩니다:
65
+ - 🖼: "replace preview" 텍스트를이 아이콘으로 변경
66
+ - 🌐:이 모델의 Civitai 페이지를 새 탭에서 엽니다.
67
+ - 💡:이 모델의 트리거 단어를 키워드 입력 상자에 추가합니다.
68
+ - 🏷:이 모델 미리보기에 사용되는 키워드를 사용합니다.
69
+
70
+ ![](img/model_card.jpg)
71
+
72
+ 이러한 추가 버튼이 표시되지 않으면 Refresh Civitai Helper를 클릭하여 다시 추가하십시오.
73
+
74
+ ![](img/refresh_ch.jpg)
75
+
76
+ Extra Network가 새로 고침될 때마다이 추가 수정이 제거되므로 버튼이 사라지면 Refresh Civitai Helper를 클릭하여 기능을 다시 추가해야합니다.
77
+
78
+
79
+ ### 작은 미리보기 모드
80
+ 이러한 기능 버튼은 작은 미리보기 모드를 지원하지만 SD Webui의 CSS 문제로 인해 현재 항상 표시하거나 항상 표시하지 않아야합니다.
81
+ ![](img/thumb_mode.jpg)
82
+
83
+ ## 다운로드
84
+ **(한 번에 하나씩, 하나를 다운로드하고 다른 것을 다운로드하세요)**
85
+ Civitai 모델 페이지 URL을 통해 모델을 다운로드하려면 3 단계가 필요합니다:
86
+ * URL을 입력하고 모델 정보를 가져 오는 버튼을 클릭합니다.
87
+ * 확장 프로그램이 모델 이름과 유형을 자동으로 입력합니다. 다운로드 할 하위 디렉토리와 모델 버전을 선택해야합니다.
88
+ * 다운로드를 클릭하십시오.
89
+ ![](img/download_model.jpg)
90
+
91
+ 다운로드 과정은 진행률 표시 줄이있는 명령 줄 인터페이스에서 표시됩니다.
92
+ 일시 중지 및 다시 시작을 지원하며 대용량 파일도 문제없이 처리합니다.
93
+
94
+
95
+ ## 일괄적으로 모델 새 버전 확인
96
+ Civitai에서 새 버전을 확인하기 위해 로컬 모델을 모델 유형 별로 일괄적으로 확인할 수 있습니다. 여러 모델 유형을 선택할 수 있습니다.
97
+ ![](img/check_model_new_version.jpg)
98
+
99
+ 새 버전을 확인 할 때마다 모델이 모두 확인 될 때까지 1 초의 지연이 있으므로 속도가 다소 느립니다.
100
+
101
+ 이것은 Civitai가 이 플러그인으로 인해 일시적으로 DDos와 유사한 상황에 빠지지 않도록 보호하기 위한 것입니다. 일부 클라우드 서비스 제공 업체는 "무료 사용자의 초당 API 요청 수는 1 회를 초과 할 수 없다"는 보호 메커니즘이 있습니다. Civitai는 이러한 설정이 없습니다. 그러나 우리는 여전히 그것을 보호해야합니다. 왜냐하면 그것이 다운되면 모두에게 좋지 않기 때문입니다.
102
+
103
+ 확인이 완료되면 다음과 같이 UI에 모든 새 버전을 찾은 정보가 표시됩니다.
104
+
105
+ 각 모델 새 버전에는 3 개의 링크가 있습니다.
106
+ * 첫 번째는 이 모델의 웹 페이지입니다.
107
+ * 두 번째는이 새 버전의 다운로드 주소입니다.
108
+ * 세 번째는 버튼입니다. Python 측에서 새 버전을 모델 디렉토리로 직접 다운로드합니다.
109
+ 이 방식으로 다운로드하면 "모델 다운로드" 영역과 명령 줄 창에 다운로드 세부 정보가 표시됩니다. 한 번에 하나의 작업만 지원됩니다.
110
+ ![](img/check_model_new_version_output.jpg)
111
+
112
+
113
+
114
+ ## URL을 기반으로 모델 정보 가져오기
115
+ Civitai에서 모델의 SHA256을 찾을 수 없지만 여전히 Civitai 모델에 모델을 연결하고 싶다면 해당 확장 프로그램 페이지에서 모델을 선택하고 Civitai 모델 페이지의 URL을 제공할 수 있습니다.
116
+
117
+ 버튼을 클릭하면 확장 프로그램이 해당 Civitai 모델의 정보를 다운로드하여 로컬 모델의 정보로 사용합니다.
118
+
119
+ ![](img/get_one_model_info.jpg)
120
+
121
+
122
+
123
+ ## 기타 설정
124
+ **설정 저장 버튼은 스캔 모델 영역 및 기타 설정 두 영역의 옵션을 저장합니다.**
125
+
126
+ * "항상 표시 버튼"은 터치 스크린에서 편리하게 사용하기 위한 것입니다.
127
+ * "작은 그림 모드에서 기능 버튼 표시"는 작은 그림 모드에서 기능 버튼을 표시할지 여부를 전환합니다.
128
+ ![](img/other_setting.jpg)
129
+
130
+ ## 미리보기 이미지
131
+ Extra Network는 model_name.png 및 model_name.preview.png 두 가지 미리보기 이미지 이름을 지원합니다. 여기서 model_name.png이 우선순위가 높습니다.
132
+
133
+ 우선순위가 높은 미리보기 이미지가 없으면 자동으로 model_name.preview.png를 사용합니다.
134
+
135
+ 이렇게 하면 직접 만든 미리보기 이미지와 인터넷에서 다운로드한 미리보기 이미지를 함께 사용할 수 있으며, 우선순위는 직접 만든 이미지가 높습니다.
136
+
137
+ ## 키워드
138
+ 카드에 키워드 추가 버튼은 civitai 미리보기 이미지에서 얻은 키워드를 추가하는 것이며, 사용자가 직접 만든 이미지의 키워드가 아닙니다.
139
+
140
+ 모든 이미지에 키워드가 있는 것은 아니며, 모델에 따라 미리보기 이미지의 키워드가 모두 같지 않을 수 있습니다. 따라서 여기서는 civitai 모든 미리보기 이미지 정보를 탐색하여 첫 번째 키워드가 있는 이미지를 로드합니다.
141
+
142
+
143
+ ## SHA256
144
+ 파일의 SHA256을 생성하려면 플러그인에서 전체 파일을 읽어야 합니다. 대형 파일의 경우 시스템이 느려질 수 있습니다.
145
+
146
+ SHA256은 civitai에서 해당 모델을 찾을 수 없는 두 가지 경우가 있습니다.
147
+ * 너무 오래된 모델이므로 civitai에 SHA256이 저장되어 있지 않습니다.
148
+ * 모델 작성자가 모델 파일을 조용히 교체했지만 설명 및 버전을 수정하지 않았습니다. 따라서 웹 페이지에서는 확인할 수 없지만 civitai 및 로컬 모델 파일은 이미 다른 파일입니다.
149
+
150
+ 이러한 경우에는 플러그인에서 모델 페이지 URL을 제공하여 모델 정보 파일을 얻을 수 있습니다
151
+
152
+
153
+ ## 자주 묻는 질문
154
+ ### 4개의 카드 버튼이 표시되지 않습니다.
155
+ #### 한국어 플러그인을 사용했습니다
156
+ 새 버전을 다운로드하면, 최신 버전에서 한국어 번역으로 인한 문제가 해결되었습니다. 양방향 다국어 플러그인은 v1.6.1.1 이후 버전부터 지원됩니다.
157
+
158
+ #### 클라우드 기반 한국어 플러그인을 사용했습니다
159
+ 클라우드 기반 한국어 플러그인을 사용한 경우 일반적인 한국어 플러그인으로 변경���십시오.
160
+
161
+
162
+ #### 다른 경우
163
+ 먼저 "Refresh Civitai Helper" 버튼을 클릭하여 Civitai Helper를 새로고침했는지 확인하세요.
164
+
165
+ 그런 다음 이 문제가 계속되는 경우, 유일한 이유는 최신 버전의 SD webui를 사용하지 않았기 때문입니다.
166
+
167
+ 만약 SD webui의 파일을 수정했다면, 업데이트 작업이 실패할 수 있습니다. 업데이트가 제대로 이루어졌는지 확인하려면 git 명령 줄의 출력 정보를 확인해야 합니다.
168
+
169
+ git은 종종 업그레이드를 거부하고, 일부 충돌을 수동으로 해결해야 한다는 메시지를 보여줍니다. 명령 줄 출력을 확인하지 않으면 업그레이드가 성공했다고 잘못 생각할 수 있습니다.
170
+
171
+
172
+ ### Request model info from civitai
173
+ 이것은 Civitai에 연결하고 있음을 나타내며, 정보가 없으면 연결할 수 없으므로 프록시를 사용해야 합니다.
174
+
175
+
176
+ ### 모델 정보 스캔 또는 가져오기 실패
177
+ 이 플러그인은 이제 매우 안정적이므로, 이 문제의 원인은 대부분 Civitai가 연결 요청을 거부했기 때문입니다.
178
+
179
+ Civitai는 대형 웹사이트와 같이 안정적이지 않습니다. 웹사이트가 다운되거나 API 연결을 거부할 수 있고, API 요청을 실제 검증 페이지로 전환하여 차단할 수도 있습니다.
180
+
181
+ Civitai에는 연결 풀 설정이 있습니다. 이는 동시에 허용되는 최대 연결 수입니다. 이 수치에 도달하면 다음 API 연결 요청은 모두 거부됩니다. 이 때는 잠시 기다렸다가 다시 시도해야 합니다.
182
+
183
+ 또한 국내 사용자들에게는 프록시 문제가 있습니다. 대개는 프록시를 사용해야만 연결할 수 있습니다.
184
+
185
+
186
+ ### civitai에서 잘못된 모델 정보 및 미리보기 이미지 가져오기
187
+ 안타깝게도, civitai의 데이터베이스에 일부 모델이 잘못된 sha256으로 저장되어 있습니다. 자세한 내용은 여기를 확인하십시오:
188
+ [https://github.com/civitai/civitai/issues/426](https://github.com/civitai/civitai/issues/426)
189
+
190
+ 따라서 이 확장 프로그램은 해당 모델의 올바른 모델 정보나 미리보기 이미지를 가져올 수 없습니다.
191
+
192
+ 이 경우 모델 정보 파일을 제거하고 이 확장 프로그램의 탭 페이지에서 civitai url로 올바른 모델 정보를 가져와야 합니다.
193
+
194
+ 또한, 잘못된 sha256을 가진 해당 모델을 civitai에 신고할 수 있습니다.
195
+ [https://discord.com/channels/1037799583784370196/1096271712959615100/1096271712959615100](https://discord.com/channels/1037799583784370196/1096271712959615100/1096271712959615100)
196
+
197
+ civitai에 그 모델을 신고하여 수정할 수 있도록 해주시기 바랍니다.
198
+
199
+
200
+ ### Colab 사용시 스캔 실패
201
+ 먼저 보이는 오류 메시지를 Google에서 검색해보세요. 대개 Colab의 문제일 가능성이 높습니다.
202
+
203
+ 그리고 Colab이 Google 드라이브에 연결되어 있다면, 파일에 대한 일회성 액세스 제한으로 인해 스캔이 실패할 수 있습니다. 이는 Google 드라이브의 제한 사항으로, 자세한 내용은 Google 검색을 통해 알아보세요.
204
+
205
+
206
+
Stable-Diffusion-Webui-Civitai-Helper/README.md ADDED
@@ -0,0 +1,329 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ### Language
2
+ [中文](README.cn.md)
3
+ [日本語](README.jp.md)
4
+ [한국어(ChatGPT)](README.kr.md)
5
+
6
+ ## About Civitai Helper2: Model Info Helper
7
+ Civitai Helper 2 will be renamed to **ModelInfo Helper**. It is under development, you can watch its UI demo video to see how it gonna look like:
8
+ [YouTube](https://youtu.be/mPcKwQDDH8s)
9
+
10
+ # Notice
11
+ **This extension now is very stable and works well for many people. If you have an issue, check console log window's detail and read [common issue](#common-issue) part**
12
+
13
+ If you want to claim it doesn't work, check this first: [Claim Wall](claim_wall.md)
14
+
15
+
16
+ # Civitai Helper
17
+ Stable Diffusion Webui Extension for Civitai, to handle your models much more easily.
18
+
19
+ Civitai: [Civitai Url](https://civitai.com/models/16768/civitai-helper-sd-webui-civitai-extension)
20
+
21
+ # Features
22
+ * Scans all models to download model information and preview images from Civitai.
23
+ * Link local model to a civitai model by civitai model's url
24
+ * Download a model(with info+preview) by Civitai Url into SD's model folder or subfolder.
25
+ * Downloading can resume at break-point, which is good for large file.
26
+ * Checking all your local model's new version from Civitai
27
+ * Download a new version directly into SD model folder (with info+preview)
28
+ * Modified Built-in "Extra Network" cards, to add the following buttons on each card:
29
+ - 🖼️: Modified "replace preview" text into this icon
30
+ - 🌐: Open this model's Civitai url in a new tab
31
+ - 💡: Add this model's trigger words to prompt
32
+ - 🏷️: Use this model's preview image's prompt
33
+ * Above buttons support thumbnail mode of Extra Network
34
+ * Option to always show additional buttons, to work with touchscreen.
35
+
36
+
37
+ # Install
38
+ Go to SD webui's extension tab, go to `Install from url` sub-tab.
39
+ Copy this project's url into it, click install.
40
+
41
+ Alternatively, download this project as a zip file, and unzip it to `Your SD webui folder/extensions`.
42
+
43
+ Everytime you install or update this extension, you need to shutdown SD Webui and Relaunch it. Just "Reload UI" won't work for this extension.
44
+
45
+ Done.
46
+
47
+ # How to Use
48
+
49
+ ## Update Your SD Webui
50
+ This extension need to get extra network's cards id. Which is added since **2023-02-06**.
51
+ **If your SD webui is an earlier version, you need to update it!**
52
+
53
+ ## Scanning Models
54
+ Go to extension tab "Civitai Helper". There is a button called "Scan model".
55
+
56
+ ![](img/extension_tab.jpg)
57
+
58
+ Click it and the extension will scan all your models to generate SHA256 hashes, using them to retreive model information and preview images from Civitai.
59
+
60
+ **Scanning takes time, just wait it finish**
61
+
62
+ For each model, it will create a json file to save all model info from Civitai. This model info file will be "Your_model_name.civitai.info" in your model folder.
63
+
64
+ ![](img/model_info_file.jpg)
65
+
66
+ If a model info file already exists, it will be skipped. If a model cannot be found in Civitai, it will create an empty model info file, so the model won't be scanned twice.
67
+
68
+ ### Adding New Models
69
+ When you have some new models, just click scan button again, to get new model's information and preview images. It won't scan the same model twice.
70
+
71
+ ## Model Card
72
+ **(Use this only after scanning finished)**
73
+ Open SD webui's build-in "Extra Network" tab, to show model cards.
74
+
75
+ ![](img/extra_network.jpg)
76
+
77
+
78
+ Move your mouse on to the bottom of a model card. It will show 4 icon buttons:
79
+ - 🖼: Replace preview (a build-in button, modified from text to icon)
80
+ - 🌐: Open this model's Civitai url in a new tab
81
+ - 💡: Add this model's trigger words to prompt
82
+ - 🏷: Use this model's preview image's prompt
83
+
84
+ ![](img/model_card.jpg)
85
+
86
+ **If these additional buttons are not there**, click the `Refresh Civitai Helper` button to bring them back.
87
+
88
+ ![](img/refresh_ch.jpg)
89
+ Everytime after Extra Network tab refreshed, it will remove all these additional buttons. So, you need to click `Refresh Civitai Helper` button to bring them back.
90
+
91
+ ### Thumbnail Mode
92
+ Additional buttons work on thumbnail too, but due to SD webui's CSS issue, for now, they must be always displayed on thumbnail or don't display at all.
93
+ ![](img/thumb_mode.jpg)
94
+
95
+
96
+ ## Download
97
+ To download a model by Civitai Model Page's Url, you need 3 steps:
98
+ * Fill url, click button to get model info
99
+ * It will show model name and type automatically. Just choose sub-folder and model version
100
+ * Click download.
101
+ ![](img/download_model.jpg)
102
+
103
+ Detail will be displayed on console log, with a progress bar.
104
+ Downloading can resume from break-point, so no fear for large file.
105
+
106
+ ## Checking Model's New Version
107
+ You can checking your local model's new version from civitai by model types. You can select multiple model types.
108
+ ![](img/check_model_new_version.jpg)
109
+
110
+ The checking process has a "1 second delay" after each model's new version checking request. So it is a little slow.
111
+
112
+ This is to protect Civitai from issue like DDos from this extension. Some cloud service provider has a rule as "no more than 1 API request in a second for free user". Civitai doesn't have this rule yet, but we still need to protect it. There is no good for us if it is down.
113
+
114
+ **After checking process done**, it will display all new version's information on UI.
115
+
116
+ There are 3 urls for each new version.
117
+ * First one is model's civitai page.
118
+ * Second one is new version's download url.
119
+ * Third one is a button to download it into your SD's model folder with python.
120
+ With this one, output information is on "Download Model" section's log and console log. **One task at a time**.
121
+
122
+ ![](img/check_model_new_version_output.jpg)
123
+
124
+
125
+ ## Get Model Info By Url
126
+ This is used to force a local model links to a Civitai model. For example, you converted a model's format or pruned it. Then it can not be found on civitai when scanning.
127
+
128
+ In that case, if you still want to link it to a civitai model. You can use this funcion.
129
+
130
+ Choose this model from list, then offer a civitai model page's url.
131
+
132
+ After clicking button, extension will download that civitai model's info and preview image for the local file you picked.
133
+
134
+ ![](img/get_one_model_info.jpg)
135
+
136
+ ## Proxy
137
+ **If you are updating to new version, you need to re-lanuch SD webui before using it.**
138
+
139
+ Proxy textbox is at the bottom of extension tab.
140
+
141
+ **Each time you fill or clear a proxy value, you need to save setting, and Re-load UI with setting tab's reload button.**
142
+
143
+ Then all requests to civitai will use the proxy.
144
+
145
+ For some sock5 proxy, need to be used as "socks5h://xxxxx".
146
+
147
+
148
+
149
+
150
+ ## Other Setting
151
+ **The Save Setting button, will save both "Scan Model"'s setting and other setting.**
152
+
153
+ * "Always Display Button" is good for touch screen.
154
+ * "Show Buttons on Thumb Mode" will turn on/off additional Buttons on thumbnail.
155
+ ![](img/other_setting.jpg)
156
+
157
+
158
+
159
+
160
+ ## Preview Image
161
+ Extra network uses both `model_file.png` and `model_file.preview.png` as preview image. But `model_file.png` has higher priority, because it is created by yourself.
162
+
163
+ When you don't have the higher priority one, it will use the other automatically.
164
+
165
+ ## Prompt
166
+ When you click the button "Use prompt from preview image", it does not use the prompt from your own preview image. It uses the one from civitai's preview image.
167
+
168
+ On civitai, a model's preview images may not has prompt. This extension will check this model's all civitai preview images' information and use the first one has prompt in it.
169
+
170
+ ## SHA256
171
+ To create a file SHA256, it need to read the whole file to generate a hash code. It gonna be slow for large files.
172
+
173
+ Also, extension uses Memory Optimized SHA256, which won't stuck your system and works with colab.
174
+
175
+ There are 2 cases this hash code can not find the model on civitai:
176
+ * Some old models, which do not have SHA256 code on civitai.
177
+ * The model's owner changed file on civitai, but does not change version name and description. So, the file on civitai is actually not the one on your manchine.
178
+
179
+ In these cases, you can always link a model to civitai by filling its URL in this extension.
180
+
181
+
182
+
183
+ ## Feature Request
184
+ No new feature for v1.x after v1.5. All new feature will go to 2.x.
185
+
186
+ 2.x will focus on custom model information and may change name to "Model Info Helper", because it is not just focus on Civitai anymore.
187
+
188
+ From v1.5, v1.x goes into maintenance phase.
189
+
190
+ Enjoy!
191
+
192
+
193
+ ## Common Issue
194
+ ### 4 Buttons on card didn't show
195
+ #### Localization
196
+ There was a Localization issue if you are not using English version of SD webui. This is fixed in the latest version of this extension. **Bilingual localization extension is supported by PR since v1.6.1.1.**
197
+
198
+ ##### Using cloud based localization extension
199
+ Turn off cloud based localization extension, use normal localization extension.
200
+
201
+ #### Other case
202
+ First of all, make sure you clicked "Refresh Civitai Helper" button.
203
+
204
+ If issue is still there, then only reason is you are not using the latest SD webui. So, Make sure you updated it.
205
+
206
+ Your update could be failed if you have modified SD webui's file. You need to check git command's console log to make sure it is updated.
207
+
208
+ In many cases, git will just refuse to update and tell you there are some conflicts need you to handle manually. If you don't check the consloe log, you will think your SD webui is updated, but it is not.
209
+
210
+ ### Request, Scan or Get model info failed
211
+ This extension is stable. So, the reason for this most likely is your internet connection to Civitai API service.
212
+
213
+ Civitai is not as stable as those rich websites, it can be down or refuse your API connection.
214
+
215
+ Civitai has a connection pool setting. Basicly, it's a max connection number that civitai can have at the same time. So, if there are already too manny connections on civitai, it will refuse your API connection.
216
+
217
+ In those cases, the only thing you can do is just wait a while then try again.
218
+
219
+ ### Get Wrong model info and preview images from civitai
220
+ A bad news is, some models are saved with a wrong sha256 in civitai's database. Check here for more detail:
221
+ [https://github.com/civitai/civitai/issues/426](https://github.com/civitai/civitai/issues/426)
222
+
223
+ So, for those models, this extension can not get the right model info or preview images.
224
+
225
+ In this case, you have to remove the model info file and get the right model info by a civitai url on this extension's tab page.
226
+
227
+ Also, you can report those models with wrong sha256 to civitai at following page:
228
+ [https://discord.com/channels/1037799583784370196/1096271712959615100/1096271712959615100](https://discord.com/channels/1037799583784370196/1096271712959615100/1096271712959615100)
229
+
230
+ Please report that model to civitai, so they can fix it.
231
+
232
+
233
+
234
+
235
+ ### Scanning fail when using colab
236
+ First of, search your error message with google. Most likely, it will be a colab issue.
237
+
238
+ If you are sure it is a out of memory issue when scanning models, and you are using this extension's latest version, then there is nothing we can do.
239
+
240
+ Since v1.5.5, we've already optimized the SHA256 function to the top. So the only 2 choices for you are:
241
+ * try again
242
+ * or use a pro account of colab.
243
+
244
+
245
+
246
+
247
+
248
+ # Change Log
249
+ ## v1.6.4
250
+ * Add "Download All files" checkbox for downloading model section. Uncheck means only download 1 file.
251
+
252
+ ## v1.6.3
253
+ * Support downloading multiple files, not avaiable when checking new version.
254
+
255
+ ## v1.6.2.1
256
+ * when parsing civitai url, remove query string by PR
257
+
258
+ ## v1.6.2
259
+ * When downloading, re-name file if file already exists
260
+
261
+ ## v1.6.1.1
262
+ * Support bilingual localization extension by PR
263
+
264
+ ## v1.6.1
265
+ * Fix Localization issue for 4 addtional buttons on cards. (Forgot that again...)
266
+
267
+ ## v1.6.0
268
+ * Fix some UI issues to work with gradio 3.23.0
269
+ * Support Proxy when connecting to civitai. Check document for detail.
270
+ * check realpath when opening file, to fix error when using junction
271
+ * Fix multiple addtional buttons issue after switching tabs.
272
+
273
+ ## v1.5.7
274
+ * Fix Localization issue for 4 addtional buttons on cards
275
+
276
+ ## v1.5.6
277
+ * update error msg when can not connect to civitai API service
278
+ * update thumb mode for SD webui new version's metadata button
279
+
280
+ ## v1.5.5
281
+ * update SHA256 function, now it just use the code from pip
282
+
283
+ ## v1.5.4
284
+ * set sys.stdout to utf-8
285
+ * Add default header for requests to prevent from being blocked by civitai.
286
+ * merge other v1.5.x change log to v1.5.4
287
+ * When downloading a model by url, check if target model version is already existed in user selected sub-folder.
288
+ * Support scanning only selected model types.
289
+ * Force TI scanning delay 1 second to prevent from civitai treating this extension's requests as attacking.
290
+
291
+ ## v1.5.0
292
+ * Download a model by Civitai model page's url
293
+ * Resume downloading from break-point
294
+ * Download new version into SD Webui's model folder
295
+ * Addtional button now works on thumbnail mode
296
+ * Option to always show addtion button, for touch screen.
297
+
298
+ ## v1.4.2
299
+ * ignore .vae file in model folder when scanning
300
+
301
+ ## v1.4.1
302
+ * When checking new versions, also searching and ignore already existed ones.
303
+ * Add version number to the bottom of this extension's tab
304
+
305
+ ## v1.4
306
+ * Support checking model's new version, display the result in UI and offer download url
307
+ * Remove addintional sub tabs on extension tab. make ui simpler.
308
+
309
+ ## v1.3
310
+ * Open url at client side
311
+ * Link selected model to civitai by url or model id
312
+ * Save and load extension setting to file
313
+ * Show button action's output to UI
314
+ * Code refactoring
315
+
316
+ ## v1.2.1
317
+ * Add more error checking to work with different versions of SD webui.
318
+
319
+ ## v1.2
320
+ * Support customer model folder
321
+ * Support readable model info file
322
+ * Support download preview image with max size
323
+ * Remove card buttons when extra network is in thumbnail mode
324
+
325
+ ## v1.1
326
+ * Support subfolders
327
+ * Check if refresh is needed when clicking "Refresh Civitai Helper"
328
+ * Add space when adding trigger words
329
+ * Add memory Optimized sha256 as an option
Stable-Diffusion-Webui-Civitai-Helper/claim_wall.md ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Claim Wall
2
+
3
+ Since this extension got a little hot, some users come to **claim many other issues to this extension**.
4
+
5
+ Following is a wall, to show a few examples how they claim this extension doesn't work, because they don't read document or forget what they did before.
6
+
7
+ If you are looking for guideline, go to section [What you should do](#what-you-should-do)
8
+
9
+ # Wall
10
+
11
+ ### Didn't even update SD Webui and claim "tried everything"
12
+
13
+ ![](img/user_claim_wall/have_not_update_sdwebui.jpg)
14
+
15
+
16
+ ### Havn't even scanned model
17
+ After 4 replies, find that out, and modified his comment.
18
+
19
+ ![](img/user_claim_wall/have_not_scan_model.jpg)
20
+
21
+ ### Claim "pretty sure" this extension breaks his UI, takes 2days to find out it is not
22
+ Then removed his comment from civitai, but his post on reddit is still there, so you can know what's really going on there.
23
+
24
+ 1. Claim "pretty sure" this extension breaks his UI
25
+
26
+ ![](img/user_claim_wall/css_issue_part1.jpg)
27
+
28
+ ![](img/user_claim_wall/css_issue_part2.jpg)
29
+
30
+ 2. Find out it is not, after 2 days
31
+
32
+ ![](img/user_claim_wall/css_issue_part3.jpg)
33
+
34
+ 3. Still don't remember what he did with other extensions, until another user tells him, about 4 days later.
35
+
36
+ ![](img/user_claim_wall/css_issue_part4.jpg)
37
+
38
+ ### **Blame SD Webui's modification to this extension**
39
+ Latest SD webui removed a button from UI, they claim this extension did that, and want it back by this extension
40
+
41
+ ![](img/user_claim_wall/blame_sdweui_update_to_this_ext.jpg)
42
+
43
+ ### Claim other extension's error to this extension
44
+ Just because both extensions have "Civitai" in extension's name
45
+
46
+ ![](img/user_claim_wall/do_not_even_use_this_ext.jpg)
47
+
48
+
49
+ ### **Didn't even use this extension and request a feature it already has**
50
+
51
+ ![](img/user_claim_wall/request_a_feature_it_already_has.jpg)
52
+
53
+ ### **Renamed model folder's name carelessly and forgot that**
54
+ Takes about 8 hours to find out why this extension doesn't work on his SDwebui and ready to re-install SD webui from beginning.
55
+
56
+ 1. claim this extension can not open civitai url on checkpoint models
57
+
58
+ ![](img/user_claim_wall/changed_model_folder_name_then_forget_part1.jpg)
59
+
60
+ 2. I reply that model he mentioned works well in my SDwebui
61
+
62
+ ![](img/user_claim_wall/changed_model_folder_name_then_forget_part2.jpg)
63
+
64
+ 3. After 6 hours' trying, find out his model folder's name is modified.
65
+
66
+ ![](img/user_claim_wall/changed_model_folder_name_then_forget_part3.jpg)
67
+
68
+ ![](img/user_claim_wall/changed_model_folder_name_then_forget_part4.jpg)
69
+
70
+
71
+ # What you should do
72
+ Above are just a very small piece of this kind of claims. Those claims won't help you. If you have an issue, following is the guidline:
73
+
74
+ * If you want to make your extension work, read the document.
75
+
76
+ * If your SD webui is broken, before you claim it is caused by this extension, you can disable it and try again.
77
+
78
+ * If you followed document, but it still doesn't work well, you can check console log's msg to find out the reason. If you can not understand those msg, you can come and ask for help, with console log's msg or screenshot.
79
+
80
+ * If you are using colab, and get an error from colab, then search that error msg in google. Because it's a colab's issue or limitation.
81
+
82
+ * If you checked console log window's msg and understand what it means, you are welcome to submit your issue.
83
+
84
+
85
+
86
+
87
+
88
+
89
+
90
+
91
+
Stable-Diffusion-Webui-Civitai-Helper/icon/.keep ADDED
File without changes