mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): experimental dynamic prompts in js
This commit is contained in:
parent
0f1b975d0e
commit
4878badb24
@ -85,6 +85,7 @@
|
||||
"konva": "^9.2.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nanostores": "^0.9.2",
|
||||
"ohm-js": "^17.1.0",
|
||||
"openapi-fetch": "^0.6.1",
|
||||
"overlayscrollbars": "^2.2.0",
|
||||
"overlayscrollbars-react": "^0.5.0",
|
||||
|
152
invokeai/frontend/web/src/common/util/dynamicPrompts/grammar.ts
Normal file
152
invokeai/frontend/web/src/common/util/dynamicPrompts/grammar.ts
Normal file
@ -0,0 +1,152 @@
|
||||
import * as ohm from 'ohm-js';
|
||||
|
||||
const grammarSource = `
|
||||
Prompt {
|
||||
exp
|
||||
= exp choicesStart multi_number multi_trigger choices choicesEnd exp --multi_choices
|
||||
| exp choicesStart multi_number multi_trigger multi_joinString multi_trigger choices choicesEnd exp --multi_choicesWithJoinString
|
||||
| exp choicesStart choices choicesEnd exp --choices
|
||||
| text --text
|
||||
|
||||
choices
|
||||
= listOf<choice, choicesDelimiter>
|
||||
|
||||
text
|
||||
= (~choicesStart any)*
|
||||
|
||||
choice
|
||||
= (~reservedChar any)*
|
||||
|
||||
reservedChar
|
||||
= (choicesEnd | choicesStart | choicesDelimiter | multi_trigger)
|
||||
|
||||
choicesStart
|
||||
= "{"
|
||||
|
||||
choicesEnd
|
||||
= "}"
|
||||
|
||||
choicesDelimiter
|
||||
= "|"
|
||||
|
||||
multi_trigger
|
||||
= "$$"
|
||||
|
||||
multi_number
|
||||
= digit+
|
||||
|
||||
multi_joinString
|
||||
= (~multi_trigger choice)
|
||||
|
||||
}`;
|
||||
|
||||
const getPermutationsOfSize = (array: string[], size: number): string[][] => {
|
||||
const result: string[][] = [];
|
||||
|
||||
function permute(arr: string[], m: string[] = []) {
|
||||
if (m.length === size) {
|
||||
result.push(m);
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const curr = arr.slice();
|
||||
const next = curr.splice(i, 1);
|
||||
permute(curr, m.concat(next));
|
||||
}
|
||||
}
|
||||
|
||||
permute(array);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const dynamicPromptsGrammar = ohm.grammar(grammarSource);
|
||||
|
||||
export const dynamicPromptsSemantics = dynamicPromptsGrammar
|
||||
.createSemantics()
|
||||
.addOperation('expand', {
|
||||
exp_multi_choices: function (
|
||||
before,
|
||||
_choicesStart,
|
||||
number,
|
||||
_multiTrigger,
|
||||
choices,
|
||||
_choicesEnd,
|
||||
after
|
||||
) {
|
||||
const beforePermutations = before.expand();
|
||||
const choicePermutations = getPermutationsOfSize(
|
||||
choices.expand(),
|
||||
Number(number.sourceString)
|
||||
);
|
||||
const afterPermutations = after.expand();
|
||||
const sep = ',';
|
||||
|
||||
const combined = [];
|
||||
for (const b of beforePermutations) {
|
||||
for (const c of choicePermutations) {
|
||||
for (const a of afterPermutations) {
|
||||
combined.push(b + c.join(sep) + a);
|
||||
}
|
||||
}
|
||||
}
|
||||
return combined;
|
||||
},
|
||||
exp_multi_choicesWithJoinString: function (
|
||||
before,
|
||||
_choicesStart,
|
||||
number,
|
||||
_multiTrigger1,
|
||||
_multiJoinString,
|
||||
_multiTrigger2,
|
||||
choices,
|
||||
_choicesEnd,
|
||||
after
|
||||
) {
|
||||
const beforePermutations = before.expand();
|
||||
const choicePermutations = getPermutationsOfSize(
|
||||
choices.expand(),
|
||||
Number(number.sourceString)
|
||||
);
|
||||
const afterPermutations = after.expand();
|
||||
const sep = _multiJoinString.sourceString;
|
||||
|
||||
const combined = [];
|
||||
for (const b of beforePermutations) {
|
||||
for (const c of choicePermutations) {
|
||||
for (const a of afterPermutations) {
|
||||
combined.push(b + c.join(sep) + a);
|
||||
}
|
||||
}
|
||||
}
|
||||
return combined;
|
||||
},
|
||||
exp_choices: function (before, _choicesStart, choices, _choicesEnd, after) {
|
||||
const beforePermutations = before.expand();
|
||||
const choicePermutations = choices.expand();
|
||||
const afterPermutations = after.expand();
|
||||
|
||||
const combined: string[] = [];
|
||||
for (const b of beforePermutations) {
|
||||
for (const c of choicePermutations) {
|
||||
for (const a of afterPermutations) {
|
||||
combined.push(b + c + a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return combined;
|
||||
},
|
||||
exp_text: function (text) {
|
||||
return [text.sourceString];
|
||||
},
|
||||
choices: function (choices) {
|
||||
return choices.asIteration().children.map((c) => c.sourceString);
|
||||
},
|
||||
_iter: function (...children) {
|
||||
children.map((c) => c.expand());
|
||||
},
|
||||
_terminal: function () {
|
||||
return this.sourceString;
|
||||
},
|
||||
});
|
@ -0,0 +1,17 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useDebounce } from 'use-debounce';
|
||||
import { dynamicPromptsGrammar, dynamicPromptsSemantics } from './grammar';
|
||||
|
||||
const parsePrompt = (prompt: string): { prompts: string[]; error: boolean } => {
|
||||
const match = dynamicPromptsGrammar.match(prompt);
|
||||
if (match.failed()) {
|
||||
return { prompts: [prompt], error: true };
|
||||
}
|
||||
return { prompts: dynamicPromptsSemantics(match).expand(), error: false };
|
||||
};
|
||||
|
||||
export const useDynamicPrompts = (prompt: string) => {
|
||||
const [debouncedPrompt] = useDebounce(prompt, 500, { leading: false });
|
||||
const result = useMemo(() => parsePrompt(debouncedPrompt), [debouncedPrompt]);
|
||||
return result;
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import { Box, FormControl, useDisclosure } from '@chakra-ui/react';
|
||||
import { Box, FormControl, Text, useDisclosure } from '@chakra-ui/react';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { ChangeEvent, KeyboardEvent, useCallback, useRef } from 'react';
|
||||
@ -20,6 +20,7 @@ import { flushSync } from 'react-dom';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus';
|
||||
import { useDynamicPrompts } from 'common/util/dynamicPrompts/useDynamicPrompts';
|
||||
|
||||
const promptInputSelector = createSelector(
|
||||
[stateSelector, activeTabNameSelector],
|
||||
@ -48,6 +49,7 @@ const ParamPositiveConditioning = () => {
|
||||
const promptRef = useRef<HTMLTextAreaElement>(null);
|
||||
const { isOpen, onClose, onOpen } = useDisclosure();
|
||||
const { t } = useTranslation();
|
||||
const parseResult = useDynamicPrompts(prompt);
|
||||
const handleChangePrompt = useCallback(
|
||||
(e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
dispatch(setPositivePrompt(e.target.value));
|
||||
@ -155,6 +157,13 @@ const ParamPositiveConditioning = () => {
|
||||
<AddEmbeddingButton onClick={onOpen} />
|
||||
</Box>
|
||||
)}
|
||||
<Box>
|
||||
generated {parseResult.prompts.length} prompts:
|
||||
{parseResult.error && <Text color="error.500">Parsing failed</Text>}
|
||||
{parseResult.prompts.map((p, i) => (
|
||||
<Text key={`${p}.${i}`}>{p}</Text>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -5074,6 +5074,11 @@ object.values@^1.1.6:
|
||||
define-properties "^1.1.4"
|
||||
es-abstract "^1.20.4"
|
||||
|
||||
ohm-js@^17.1.0:
|
||||
version "17.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ohm-js/-/ohm-js-17.1.0.tgz#50d8e08f69d7909931998d75202d35e2a90c8885"
|
||||
integrity sha512-xc3B5dgAjTBQGHaH7B58M2Pmv6WvzrJ/3/7LeUzXNg0/sY3jQPdSd/S2SstppaleO77rifR1tyhdfFGNIwxf2Q==
|
||||
|
||||
once@^1.3.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
|
Loading…
Reference in New Issue
Block a user