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",
|
"konva": "^9.2.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"nanostores": "^0.9.2",
|
"nanostores": "^0.9.2",
|
||||||
|
"ohm-js": "^17.1.0",
|
||||||
"openapi-fetch": "^0.6.1",
|
"openapi-fetch": "^0.6.1",
|
||||||
"overlayscrollbars": "^2.2.0",
|
"overlayscrollbars": "^2.2.0",
|
||||||
"overlayscrollbars-react": "^0.5.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 { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { ChangeEvent, KeyboardEvent, useCallback, useRef } from 'react';
|
import { ChangeEvent, KeyboardEvent, useCallback, useRef } from 'react';
|
||||||
@ -20,6 +20,7 @@ import { flushSync } from 'react-dom';
|
|||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus';
|
||||||
|
import { useDynamicPrompts } from 'common/util/dynamicPrompts/useDynamicPrompts';
|
||||||
|
|
||||||
const promptInputSelector = createSelector(
|
const promptInputSelector = createSelector(
|
||||||
[stateSelector, activeTabNameSelector],
|
[stateSelector, activeTabNameSelector],
|
||||||
@ -48,6 +49,7 @@ const ParamPositiveConditioning = () => {
|
|||||||
const promptRef = useRef<HTMLTextAreaElement>(null);
|
const promptRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const { isOpen, onClose, onOpen } = useDisclosure();
|
const { isOpen, onClose, onOpen } = useDisclosure();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const parseResult = useDynamicPrompts(prompt);
|
||||||
const handleChangePrompt = useCallback(
|
const handleChangePrompt = useCallback(
|
||||||
(e: ChangeEvent<HTMLTextAreaElement>) => {
|
(e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
dispatch(setPositivePrompt(e.target.value));
|
dispatch(setPositivePrompt(e.target.value));
|
||||||
@ -155,6 +157,13 @@ const ParamPositiveConditioning = () => {
|
|||||||
<AddEmbeddingButton onClick={onOpen} />
|
<AddEmbeddingButton onClick={onOpen} />
|
||||||
</Box>
|
</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>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -5074,6 +5074,11 @@ object.values@^1.1.6:
|
|||||||
define-properties "^1.1.4"
|
define-properties "^1.1.4"
|
||||||
es-abstract "^1.20.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:
|
once@^1.3.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||||
|
Loading…
Reference in New Issue
Block a user