├── LICENSE ├── README.md ├── assets ├── dog.png ├── enable_checkbox.png ├── example_basic │ ├── command.png │ └── result.png ├── example_console_log │ ├── command.png │ └── result.png ├── example_date │ ├── command.png │ └── resault.png ├── example_interaction_with_dynamic_prompts │ ├── command.png │ └── result.png ├── example_no_braces │ ├── command.png │ └── result.png ├── example_recap │ └── recap.png ├── example_shared_context_across_prompts │ ├── command.png │ └── result.png └── example_shared_context_in_prompt │ ├── command.png │ └── result.png ├── install.py └── scripts └── dynamic_javascript.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Patrick McCuller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sd-dynamic-javascript 2 | Automatic1111 extension that allows embedding Javascript into prompts. 3 | 4 | ## Javascript! 5 | 6 | You can write Javascript now to your heart's content! Examples of how this works after a short preamble, or you can scroll straight to them below. 7 | 8 | ## Motivation 9 | 10 | * Currently you can't embed Javascript in your SD prompts, which is just silly. 11 | * That's sufficent, I think. I can't wait to see what amazing things people come up with. Please share them with me, and others, if you pease. 12 | * There are interfaces for programming with python (via --allow-code and the script dropdown) or with lua (via a different extension) but these fine methods allow you to program the *process* of generating images. Literally there's an 'image.process(512, 512, "a dog playing by the seashore")' kind of API. This extension does not give you the power to program the process or Automatic1111 itself; those do. This extension focuses on embedding & executing Javascript directly in prompts. Whether that is more useful is up to you. 13 | 14 | ## State: Current Problems and TODOs 15 | * None. Unlike everything else in life, this is problem free. 16 | * Except... it might be something of a security issue to run arbitrary Javascript inside your prompt. Just saying. I mean, there's precautions; the extension uses a headless browser, and those are reasonably well sandboxed. Still. 17 | * And you do have to install something on the side, see Requirements just below. Hey, they say nothing free is worthwhile. 18 | 19 | ## Requirements 20 | 21 | This extension uses selenium and requires that you have a browser driver in your path, I recommend chromedriver (Mac, Linux, Windows all supported; if you're using, like, BeOS - well, let me know.) As far as I know you really need this, and you have to do it separately from just installing the extension. Sorry. If you know a better way to execute Javascript in an embedded context like this, please let me know. 22 | The extension should take care of installing selenium itself, let me know if you encounter a problem with how that works. 23 | 24 | ## Capabilities 25 | 26 | It's Javascript, executes as if in a browser. I don't know how crazy you can get, please let me know! 27 | 28 | ## Interation with Other Extension such as Dynamic Prompts 29 | 30 | #### Dynamic Prompts 31 | * That magnificent extension does its own thing and as far as I can tell there is no adverse interaction between this extension and that one. 32 | * Dynamic Javascript prompts runs before Dynamic Prompts, so you can create dynamic prompt material with Javascript. The opposite is not true, but something I am considering. Let me know if you have a use for also being able to execute dynamic javascript _after_ dynamic prompts. 33 | 34 | #### Lua 35 | * This extension currently does not work with the Lua extension, probably because the Lua extension has its own prompt box. If there is demand, I will look into supporting this. 36 | 37 | #### Custom Code Python Script "--allow-code" 38 | * This integration works just fine today. 39 | 40 | ## Code, Evaluation, and Order of Evaluation 41 | 42 | * Dynamic Javascript prompts runs before Dynamic Prompts, so you can create dynamic prompt material with Javascript. 43 | * * But you can't include Javascript from a file with Dynamic Prompts, for example, because that Javascript won't get evaluated. LMK if you need that though. 44 | * Code blocks are demarcated by '%%' at the beginning and end. - if that doesn't work for you let me know. I can make it configurable. 45 | * * e.g. %% console.log(hello world); %% 46 | * Whatever your Javascript returns is what the code block is replaced with. If you return nothing, the code is evaluated but only empty space replaces the code block in the prompt. If you're disappointed by this let me know. 47 | * Positive prompts are evaluated first, then negative prompts. 48 | * Evaluation in each prompt is done in one pass from beginning to end. 49 | * Context for variables is shared among embeddings and between positive and negative prompts (though because of order of exaluation, as mentioned above, it's one-way.) 50 | * Context is cleared between generations. If you expected or need something else, let me know. 51 | * Syntax and other errors will be sent to the console and the prompt. 52 | * * e.g. Message: javascript error: Unexpected identifier 'able' (Session info: headless chrome=114.0.5735.248) 53 | * * * Stacktrace: Backtrace: GetHandleVerifier [0x00F5A813+48355] (No symbol) 54 | * * * ... 55 | * It does cost a bit to run this every time, not too much, and I haven't measured it, data and anecdotes welcomed. 56 | 57 | ## Settings 58 | 59 | There are no settings, but there's an Enable/Disable Dynamic Javascript option in the webui that does exactly what a reasonable person would expect. 60 | 61 | ![](assets/enable_checkbox.png) 62 | 63 | If you disable it, code blocks will appear in your output, and it costs virtually nothing to execute. If you think disabled Javascript code blocks should disappear instead of showing in the output prompt, let me know. 64 | 65 | ## Examples 66 | 67 | Using screenshots is cheap and easy, but not very accessible and may have other problems. If this causes you any kind of difficulty let me know, I will make changes. 68 | 69 | ### Basics 70 | 71 | ```javascript 72 | %% { 73 | return "the code block value in the prompt becomes what's in quotes"; 74 | } %% 75 | ``` 76 | 77 | Begets: 78 | 79 | ``` 80 | the code block value in the prompt becomes what's in quotes 81 | ``` 82 | 83 | You don't need to separate this into separate lines and use braces like I did. 84 | 85 | ```javascript 86 | %% return "the code block value in the prompt becomes what's in quotes"; %% 87 | ``` 88 | 89 | Begets: 90 | 91 | ``` 92 | the code block value in the prompt becomes what's in quotes 93 | ``` 94 | 95 | I just like the braces and such :shrug: 96 | 97 | ### Javascript Functions 98 | 99 | Stuff like Date() and the console are indeed available. 100 | 101 | Date 102 | 103 | ```javascript 104 | %% { 105 | return Date(); 106 | } %% 107 | ``` 108 | 109 | ![](assets/example_date/resault.png) 110 | 111 | Bit of a tangent: the results you get when you use just a date as the prompt are curious. 112 | 113 | Console 114 | 115 | ![](assets/example_console_log/command.png) 116 | 117 | ![](assets/example_console_log/result.png) 118 | 119 | ### Context 120 | 121 | Within a particular generation, variables in one code block can be used by subsequent (later) code blocks. 122 | 123 | ![](assets/example_shared_context_in_prompt/command.png) 124 | 125 | ![](assets/example_shared_context_in_prompt/result.png) 126 | 127 | This works from the positive prompt to the negative too. 128 | 129 | ![](assets/example_shared_context_across_prompts/command.png) 130 | 131 | ![](assets/example_shared_context_across_prompts/result.png) 132 | 133 | ...but only in the positive->negative direction, because that's the order of evaluation. If you need something else, I guess OOE could be a setting? Let me know. 134 | 135 | 136 | ### A little more complicated 137 | 138 | ```javascript 139 | %% 140 | function randomInt(min, max) { // min inclusive, max exclusive 141 | min = Math.ceil(min); 142 | max = Math.floor(max); 143 | return Math.floor(Math.random() * (max - min) + min); 144 | } 145 | 146 | function randomWord() { 147 | randomWord = ""; 148 | length = randomInt(6,11); 149 | for (i = 0; i < length; i++) { 150 | char = 'abcdefghijklmnopqrstuvwxyz1234567890'.charAt([randomInt(0,36)]) 151 | randomWord = randomWord.concat(char); 152 | } 153 | console.log(randomWord); 154 | return randomWord; 155 | } 156 | 157 | function randomNumber() { 158 | randomNumber = ""; 159 | length = randomInt(6,11); 160 | for (i = 0; i < length; i++) { 161 | char = '1234567890'.charAt([randomInt(0,10)]) 162 | randomNumber = randomNumber.concat(char); 163 | } 164 | console.log(randomNumber); 165 | return randomNumber; 166 | } 167 | return randomWord() + " " + randomNumber(); 168 | %% 169 | ``` 170 | 171 | Begets: 172 | 173 | ``` 174 | 4c3ahwn 8893525278 175 | ``` 176 | 177 | I just know you were waiting to have random junk thrown into your prompts. 178 | 179 | ### Dynamic Prompts 180 | 181 | Since this extension runs before Dynamic Prompts, any dynamic prompting you create will work as you might hope. If you need it to run *after*... well, leave a note about that and describe your scenario, please. 182 | 183 | ![](assets/example_interaction_with_dyamic_prompts/command.png) 184 | 185 | ![](assets/example_interaction_with_dyamic_prompts/result.png) 186 | 187 | ## Colophon 188 | 189 | Made for fun. I hope if brings you great joy, and perfect hair forever. Contact me with questions and comments, but not threats, please. And feel free to contribute! Pull requests and ideas in Discussions or Issues will be taken quite seriously! 190 | 191 | A dog playing by the seashore thanks you for your time. 192 | 193 | ![](assets/dog.png) 194 | -------------------------------------------------------------------------------- /assets/dog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmcculler/sd-dynamic-javascript/5eb9cbd0bf0424986a1c6f1aefa78afed628723b/assets/dog.png -------------------------------------------------------------------------------- /assets/enable_checkbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmcculler/sd-dynamic-javascript/5eb9cbd0bf0424986a1c6f1aefa78afed628723b/assets/enable_checkbox.png -------------------------------------------------------------------------------- /assets/example_basic/command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmcculler/sd-dynamic-javascript/5eb9cbd0bf0424986a1c6f1aefa78afed628723b/assets/example_basic/command.png -------------------------------------------------------------------------------- /assets/example_basic/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmcculler/sd-dynamic-javascript/5eb9cbd0bf0424986a1c6f1aefa78afed628723b/assets/example_basic/result.png -------------------------------------------------------------------------------- /assets/example_console_log/command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmcculler/sd-dynamic-javascript/5eb9cbd0bf0424986a1c6f1aefa78afed628723b/assets/example_console_log/command.png -------------------------------------------------------------------------------- /assets/example_console_log/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmcculler/sd-dynamic-javascript/5eb9cbd0bf0424986a1c6f1aefa78afed628723b/assets/example_console_log/result.png -------------------------------------------------------------------------------- /assets/example_date/command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmcculler/sd-dynamic-javascript/5eb9cbd0bf0424986a1c6f1aefa78afed628723b/assets/example_date/command.png -------------------------------------------------------------------------------- /assets/example_date/resault.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmcculler/sd-dynamic-javascript/5eb9cbd0bf0424986a1c6f1aefa78afed628723b/assets/example_date/resault.png -------------------------------------------------------------------------------- /assets/example_interaction_with_dynamic_prompts/command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmcculler/sd-dynamic-javascript/5eb9cbd0bf0424986a1c6f1aefa78afed628723b/assets/example_interaction_with_dynamic_prompts/command.png -------------------------------------------------------------------------------- /assets/example_interaction_with_dynamic_prompts/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmcculler/sd-dynamic-javascript/5eb9cbd0bf0424986a1c6f1aefa78afed628723b/assets/example_interaction_with_dynamic_prompts/result.png -------------------------------------------------------------------------------- /assets/example_no_braces/command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmcculler/sd-dynamic-javascript/5eb9cbd0bf0424986a1c6f1aefa78afed628723b/assets/example_no_braces/command.png -------------------------------------------------------------------------------- /assets/example_no_braces/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmcculler/sd-dynamic-javascript/5eb9cbd0bf0424986a1c6f1aefa78afed628723b/assets/example_no_braces/result.png -------------------------------------------------------------------------------- /assets/example_recap/recap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmcculler/sd-dynamic-javascript/5eb9cbd0bf0424986a1c6f1aefa78afed628723b/assets/example_recap/recap.png -------------------------------------------------------------------------------- /assets/example_shared_context_across_prompts/command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmcculler/sd-dynamic-javascript/5eb9cbd0bf0424986a1c6f1aefa78afed628723b/assets/example_shared_context_across_prompts/command.png -------------------------------------------------------------------------------- /assets/example_shared_context_across_prompts/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmcculler/sd-dynamic-javascript/5eb9cbd0bf0424986a1c6f1aefa78afed628723b/assets/example_shared_context_across_prompts/result.png -------------------------------------------------------------------------------- /assets/example_shared_context_in_prompt/command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmcculler/sd-dynamic-javascript/5eb9cbd0bf0424986a1c6f1aefa78afed628723b/assets/example_shared_context_in_prompt/command.png -------------------------------------------------------------------------------- /assets/example_shared_context_in_prompt/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmcculler/sd-dynamic-javascript/5eb9cbd0bf0424986a1c6f1aefa78afed628723b/assets/example_shared_context_in_prompt/result.png -------------------------------------------------------------------------------- /install.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import subprocess 3 | import logging 4 | 5 | def install_requirements(force=False) -> None: 6 | """ 7 | Invoke pip to install the requirements for the extension. 8 | """ 9 | try: 10 | from launch import args 11 | 12 | if getattr(args, "skip_install", False): 13 | logging.info( 14 | "webui launch.args.skip_install is true, skipping dynamic javascript installation", 15 | ) 16 | return 17 | except ImportError: 18 | pass 19 | 20 | requirements_to_install = [ 21 | "selenium" 22 | ] 23 | 24 | if not requirements_to_install: 25 | return 26 | 27 | command = [ 28 | sys.executable, 29 | "-m", 30 | "pip", 31 | "install", 32 | *requirements_to_install, 33 | ] 34 | print(f"sd-dynamic-javascript installer: running {' '.join(command)}") 35 | subprocess.check_call(command) 36 | 37 | if __name__ == "__main__": 38 | 39 | install_requirements(force=("-f" in sys.argv)) -------------------------------------------------------------------------------- /scripts/dynamic_javascript.py: -------------------------------------------------------------------------------- 1 | from asyncio.windows_events import NULL 2 | import contextlib 3 | import gradio as gr 4 | import logging 5 | from modules import scripts 6 | from modules import script_callbacks 7 | 8 | from selenium import webdriver 9 | from selenium.webdriver.chrome.options import Options 10 | 11 | import re 12 | 13 | class JavascriptRunner: 14 | def __init__(self): 15 | options = Options() 16 | options.add_argument('--ignore-ssl-errors=yes') 17 | options.add_argument('--ignore-certificate-errors') 18 | options.add_argument('--headless') 19 | self.driver = webdriver.Chrome(options=options) 20 | 21 | def __del__(self): 22 | self.driver.quit() 23 | 24 | def resetContext(self): 25 | # Navigate to a blank page after executing JS 26 | # This 'resets' things like variables so they don't hang around forever between executions 27 | self.driver.get('about:blank') 28 | 29 | def execute_javascript_in_prompt(self, prompt): 30 | sections = re.split('(%%.*?%%)', prompt, flags=re.DOTALL) 31 | for i, section in enumerate(sections): 32 | if section.startswith('%%') and section.endswith('%%'): 33 | # This is a JavaScript code section. Execute it. 34 | js_code = section[2:-2].strip() # Remove the delimiters 35 | result = self.driver.execute_script(js_code) 36 | # Replace the JavaScript code with its output in the sections list 37 | replacement = str(result) 38 | if replacement != 'None' and replacement.__len__() > 0: 39 | sections[i] = replacement 40 | else: 41 | sections[i] = "" 42 | 43 | # Join the sections back together into the final prompt 44 | return ''.join(sections) 45 | 46 | 47 | def _get_effective_prompt(prompts: list[str], prompt: str) -> str: 48 | return prompts[0] if prompts else prompt 49 | 50 | 51 | class JSPromptScript(scripts.Script): 52 | def __init__(self) -> None: 53 | self.jr = NULL 54 | super().__init__() 55 | 56 | def title(self): 57 | return "Dynamic Javascript Prompt" 58 | 59 | def show(self, is_img2img): 60 | return scripts.AlwaysVisible 61 | 62 | def ui(self, is_img2img): 63 | is_enabled = gr.Checkbox(label="Enable Dynamic Javascript", value=True, elem_id=self.elem_id("enable")) 64 | return [is_enabled] 65 | 66 | def process(self, p, is_enabled): 67 | if not is_enabled: 68 | logging.debug("Dynamic javascript prompts disabled - exiting process function.") 69 | return p 70 | 71 | original_prompt = _get_effective_prompt(p.all_prompts, p.prompt) 72 | 73 | for i in range(len(p.all_prompts)): 74 | prompt = p.all_prompts[i] 75 | negative_prompt = p.all_negative_prompts[i] 76 | try: 77 | if "%%" in prompt: 78 | if self.jr is NULL: 79 | self.jr = JavascriptRunner(); 80 | prompt = self.jr.execute_javascript_in_prompt(prompt) 81 | if "%%" in negative_prompt: 82 | if self.jr is NULL: 83 | self.jr = JavascriptRunner(); 84 | negative_prompt = self.jr.execute_javascript_in_prompt(negative_prompt) 85 | if self.jr is not NULL: 86 | self.jr.resetContext() # End shared context 87 | except Exception as e: 88 | logging.exception(e) 89 | prompt = str(e) 90 | negative_prompt = str(e) 91 | 92 | p.all_prompts[i] = prompt 93 | p.all_negative_prompts[i] = negative_prompt 94 | p.prompt_for_display = original_prompt 95 | --------------------------------------------------------------------------------