F: Implement prompt editor
This commit is contained in:
parent
78964b23cc
commit
ea892bc9cb
|
@ -0,0 +1,24 @@
|
|||
import { type CompletionContext } from '@codemirror/autocomplete';
|
||||
|
||||
import { describePromptVariable } from '../../labels';
|
||||
import { type PromptVariableType } from '../../models/prompting';
|
||||
|
||||
export function variableCompletions(variables: string[]) {
|
||||
return (context: CompletionContext) => {
|
||||
let word = context.matchBefore(/\{\{[a-zA-Z.-]*/);
|
||||
if (!word && context.explicit) {
|
||||
word = { from: context.pos, to: context.pos, text: '' };
|
||||
}
|
||||
if (!word || (word.from == word.to && !context.explicit)) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
from: word.from,
|
||||
to: word.to,
|
||||
options: variables.map(name => ({
|
||||
label: `{{${name}}}`,
|
||||
info: describePromptVariable(name as PromptVariableType)
|
||||
}))
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
import { syntaxTree } from '@codemirror/language';
|
||||
import { RangeSetBuilder } from '@codemirror/state';
|
||||
import { Decoration, type DecorationSet, type EditorView, ViewPlugin, type ViewUpdate } from '@codemirror/view';
|
||||
|
||||
const invalidVarMark = Decoration.mark({
|
||||
class: 'text-destructive'
|
||||
});
|
||||
|
||||
const validMark = Decoration.mark({
|
||||
class: 'text-(--acc-fg-purple)'
|
||||
});
|
||||
|
||||
class MarkVariablesPlugin {
|
||||
decorations: DecorationSet;
|
||||
allowed: string[];
|
||||
|
||||
constructor(view: EditorView, allowed: string[]) {
|
||||
this.allowed = allowed;
|
||||
this.decorations = this.buildDecorations(view);
|
||||
}
|
||||
|
||||
update(update: ViewUpdate) {
|
||||
if (update.docChanged || update.viewportChanged) {
|
||||
this.decorations = this.buildDecorations(update.view);
|
||||
}
|
||||
}
|
||||
|
||||
buildDecorations(view: EditorView): DecorationSet {
|
||||
const builder = new RangeSetBuilder<Decoration>();
|
||||
const tree = syntaxTree(view.state);
|
||||
const doc = view.state.doc;
|
||||
|
||||
tree.iterate({
|
||||
enter: node => {
|
||||
if (node.name === 'Variable') {
|
||||
// Extract inner text from the Variable node ({{my_var}})
|
||||
const text = doc.sliceString(node.from, node.to);
|
||||
const match = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/.exec(text);
|
||||
const varName = match?.[1];
|
||||
|
||||
if (!varName || !this.allowed.includes(varName)) {
|
||||
builder.add(node.from, node.to, invalidVarMark);
|
||||
} else {
|
||||
builder.add(node.from, node.to, validMark);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return builder.finish();
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a ViewPlugin that marks invalid variables in the editor. */
|
||||
export function markVariables(allowed: string[]) {
|
||||
return ViewPlugin.fromClass(
|
||||
class extends MarkVariablesPlugin {
|
||||
constructor(view: EditorView) {
|
||||
super(view, allowed);
|
||||
}
|
||||
},
|
||||
{
|
||||
decorations: plugin => plugin.decorations
|
||||
}
|
||||
);
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import { syntaxTree } from '@codemirror/language';
|
||||
import { RangeSetBuilder } from '@codemirror/state';
|
||||
import { Decoration, type DecorationSet, type EditorView, ViewPlugin, type ViewUpdate } from '@codemirror/view';
|
||||
|
||||
const noSpellcheckMark = Decoration.mark({
|
||||
attributes: { spellcheck: 'false' }
|
||||
});
|
||||
|
||||
class NoSpellcheckPlugin {
|
||||
decorations: DecorationSet;
|
||||
|
||||
constructor(view: EditorView) {
|
||||
this.decorations = this.buildDecorations(view);
|
||||
}
|
||||
|
||||
update(update: ViewUpdate) {
|
||||
if (update.docChanged || update.viewportChanged) {
|
||||
this.decorations = this.buildDecorations(update.view);
|
||||
}
|
||||
}
|
||||
|
||||
buildDecorations(view: EditorView): DecorationSet {
|
||||
const builder = new RangeSetBuilder<Decoration>();
|
||||
|
||||
const tree = syntaxTree(view.state);
|
||||
for (const { from, to } of view.visibleRanges) {
|
||||
tree.iterate({
|
||||
from,
|
||||
to,
|
||||
enter: node => {
|
||||
if (node.name === 'Variable') {
|
||||
builder.add(node.from, node.to, noSpellcheckMark);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return builder.finish();
|
||||
}
|
||||
}
|
||||
|
||||
/** Plugin that adds a no-spellcheck attribute to all variables in the editor. */
|
||||
export const noSpellcheckForVariables = ViewPlugin.fromClass(NoSpellcheckPlugin, {
|
||||
decorations: (plugin: NoSpellcheckPlugin) => plugin.decorations
|
||||
});
|
|
@ -1,6 +1,5 @@
|
|||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||
export const
|
||||
Text = 1,
|
||||
export const Text = 1,
|
||||
Variable = 2,
|
||||
Error = 3,
|
||||
Filler = 4
|
||||
Filler = 4;
|
||||
|
|
|
@ -12,13 +12,13 @@ const testData = [
|
|||
['{{var_1}}', '[Text[Variable]]'],
|
||||
['{{user.name}}', '[Text[Variable]]'],
|
||||
['!error!', '[Text[Error]]'],
|
||||
['word !error! word', '[Text[Filler Error Filler]]'],
|
||||
['{{variable}} !error! word', '[Text[Variable Error Filler]]'],
|
||||
['word {{variable}}', '[Text[Filler Variable]]'],
|
||||
['word {{variable}} !error!', '[Text[Filler Variable Error]]'],
|
||||
['{{variable}} word', '[Text[Variable Filler]]'],
|
||||
['!err! {{variable}}', '[Text[Error Variable]]'],
|
||||
['!err! {{variable}} word', '[Text[Error Variable Filler]]']
|
||||
['word !error! word', '[Text[Filler][Error][Filler]]'],
|
||||
['{{variable}} !error! word', '[Text[Variable][Error][Filler]]'],
|
||||
['word {{variable}}', '[Text[Filler][Variable]]'],
|
||||
['word {{variable}} !error!', '[Text[Filler][Variable][Error]]'],
|
||||
['{{variable}} word', '[Text[Variable][Filler]]'],
|
||||
['!err! {{variable}}', '[Text[Error][Variable]]'],
|
||||
['!err! {{variable}} word', '[Text[Error][Variable][Filler]]']
|
||||
] as const;
|
||||
|
||||
/** Test prompt grammar parser with various prompt inputs */
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||
import {LRParser} from "@lezer/lr"
|
||||
import {highlighting} from "./highlight"
|
||||
import { LRParser } from '@lezer/lr';
|
||||
import { highlighting } from './highlight';
|
||||
export const parser = LRParser.deserialize({
|
||||
version: 14,
|
||||
states: "!vQVQPOOObQPO'#C^OgQPO'#CjO]QPO'#C_OOQO'#C`'#C`OOQO'#Ce'#CeOOQO'#Ca'#CaQVQPOOOuQPO,58xOOQO,59U,59UOzQPO,58yOOQO-E6_-E6_OOQO1G.d1G.dOOQO1G.e1G.e",
|
||||
stateData: "!U~OWOS~OZPO]RO_QO~O[WO~O_QOU^XZ^X]^X~OY[O~O]]O~O[_W_~",
|
||||
goto: "x_PP```dPPPjPPPPnTTOVQVORZVTUOVSSOVQXQRYR",
|
||||
nodeNames: "⚠ Text Variable Error Filler",
|
||||
states:
|
||||
"!vQVQPOOObQQO'#C^OgQPO'#CjO]QPO'#C_OOQO'#C`'#C`OOQO'#Ce'#CeOOQO'#Ca'#CaQVQPOOOuQPO,58xOOQO,59U,59UOzQPO,58yOOQO-E6_-E6_OOQO1G.d1G.dOOQO1G.e1G.e",
|
||||
stateData: '!S~OWOS~OZPO]RO_QO~O[WO~O_QOU^XZ^X]^X~OY[O~O]]O~O_W~',
|
||||
goto: 'x_PP```dPPPjPPPPnTTOVQVORZVTUOVSSOVQXQRYR',
|
||||
nodeNames: '⚠ Text Variable Error Filler',
|
||||
maxTerm: 15,
|
||||
propSources: [highlighting],
|
||||
skippedNodes: [0],
|
||||
repeatNodeCount: 1,
|
||||
tokenData: ")O~RrOX#]XZ$QZ^$u^p#]pq$Qqr&qr!O#]!P!b#]!c!}&v!}#R#]#R#S&v#S#T#]#T#o&v#o#p(h#q#r(s#r#y#]#y#z$u#z$f#]$f$g$u$g#BY#]#BY#BZ$u#BZ$IS#]$IS$I_$u$I_$I|#]$I|$JO$u$JO$JT#]$JT$JU$u$JU$KV#]$KV$KW$u$KW&FU#]&FU&FV$u&FV;'S#];'S;=`#z<%lO#]~#bW_~OX#]Zp#]r!O#]!P!b#]!c#o#]#r;'S#];'S;=`#z<%lO#]~#}P;=`<%l#]~$VYW~X^$Qpq$Q#y#z$Q$f$g$Q#BY#BZ$Q$IS$I_$Q$I|$JO$Q$JT$JU$Q$KV$KW$Q&FU&FV$Q~$|k_~W~OX#]XZ$QZ^$u^p#]pq$Qr!O#]!P!b#]!c#o#]#r#y#]#y#z$u#z$f#]$f$g$u$g#BY#]#BY#BZ$u#BZ$IS#]$IS$I_$u$I_$I|#]$I|$JO$u$JO$JT#]$JT$JU$u$JU$KV#]$KV$KW$u$KW&FU#]&FU&FV$u&FV;'S#];'S;=`#z<%lO#]~&vO]~~&}`[~_~OX#]Zp#]r}#]}!O&v!O!P(P!P!Q#]!Q![&v![!b#]!c!}&v!}#R#]#R#S&v#S#T#]#T#o&v#r;'S#];'S;=`#z<%lO#]~(UU[~}!O(P!O!P(P!Q![(P!c!}(P#R#S(P#T#o(P~(kP#o#p(n~(sOZ~~(vP#q#r(y~)OOY~",
|
||||
tokenizers: [0],
|
||||
topRules: {"Text":[0,1]},
|
||||
tokenData:
|
||||
"(^~RqOX#YXZ#zZ^$o^p#Ypq#zqr&hr!b#Y!c!}&m!}#R#Y#R#S&m#S#T#Y#T#o&m#o#p'v#q#r(R#r#y#Y#y#z$o#z$f#Y$f$g$o$g#BY#Y#BY#BZ$o#BZ$IS#Y$IS$I_$o$I_$I|#Y$I|$JO$o$JO$JT#Y$JT$JU$o$JU$KV#Y$KV$KW$o$KW&FU#Y&FU&FV$o&FV;'S#Y;'S;=`#t<%lO#YP#_V_POX#YZp#Yr!b#Y!c#o#Y#r;'S#Y;'S;=`#t<%lO#YP#wP;=`<%l#Y~$PYW~X^#zpq#z#y#z#z$f$g#z#BY#BZ#z$IS$I_#z$I|$JO#z$JT$JU#z$KV$KW#z&FU&FV#z~$vj_PW~OX#YXZ#zZ^$o^p#Ypq#zr!b#Y!c#o#Y#r#y#Y#y#z$o#z$f#Y$f$g$o$g#BY#Y#BY#BZ$o#BZ$IS#Y$IS$I_$o$I_$I|#Y$I|$JO$o$JO$JT#Y$JT$JU$o$JU$KV#Y$KV$KW$o$KW&FU#Y&FU&FV$o&FV;'S#Y;'S;=`#t<%lO#Y~&mO]~R&t`[Q_POX#YZp#Yr}#Y}!O&m!O!P&m!P!Q#Y!Q![&m![!b#Y!c!}&m!}#R#Y#R#S&m#S#T#Y#T#o&m#r;'S#Y;'S;=`#t<%lO#Y~'yP#o#p'|~(ROZ~~(UP#q#r(X~(^OY~",
|
||||
tokenizers: [0, 1],
|
||||
topRules: { Text: [0, 1] },
|
||||
tokenPrec: 47
|
||||
})
|
||||
});
|
||||
|
|
|
@ -12,9 +12,9 @@
|
|||
@tokens {
|
||||
space { @whitespace+ }
|
||||
variable { $[a-zA-Z_]$[a-zA-Z0-9_.-]* }
|
||||
word { ![@{|}!.\t\n ]+ }
|
||||
word { ![@{|}! \t\n]+ }
|
||||
|
||||
@precedence { variable, word, space }
|
||||
@precedence { word, space }
|
||||
}
|
||||
|
||||
textItem {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { forwardRef, useRef } from 'react';
|
||||
import { autocompletion } from '@codemirror/autocomplete';
|
||||
import { type Extension } from '@codemirror/state';
|
||||
import { tags } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
|
@ -15,11 +16,14 @@ import { EditorView } from 'codemirror';
|
|||
import { Label } from '@/components/input';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
import { APP_COLORS } from '@/styling/colors';
|
||||
import { notImplemented } from '@/utils/utils';
|
||||
|
||||
import { useAvailableVariables } from '../../stores/use-available-variables';
|
||||
import { PromptVariableType } from '../../models/prompting';
|
||||
|
||||
import { variableCompletions } from './completion';
|
||||
import { markVariables } from './mark-variables';
|
||||
import { noSpellcheckForVariables } from './no-spellcheck';
|
||||
import { PromptLanguage } from './parse';
|
||||
import { variableHoverTooltip } from './tooltip';
|
||||
|
||||
const EDITOR_OPTIONS: BasicSetupOptions = {
|
||||
highlightSpecialChars: false,
|
||||
|
@ -65,6 +69,7 @@ interface PromptInputProps
|
|||
> {
|
||||
value: string;
|
||||
onChange: (newValue: string) => void;
|
||||
availableVariables?: string[];
|
||||
|
||||
label?: string;
|
||||
disabled?: boolean;
|
||||
|
@ -83,8 +88,6 @@ export const PromptInput = forwardRef<ReactCodeMirrorRef, PromptInputProps>(
|
|||
ref
|
||||
) => {
|
||||
const darkMode = usePreferencesStore(state => state.darkMode);
|
||||
const availableVariables = useAvailableVariables();
|
||||
console.log(availableVariables);
|
||||
|
||||
const internalRef = useRef<ReactCodeMirrorRef>(null);
|
||||
const thisRef = !ref || typeof ref === 'function' ? internalRef : ref;
|
||||
|
@ -99,29 +102,28 @@ export const PromptInput = forwardRef<ReactCodeMirrorRef, PromptInputProps>(
|
|||
caret: APP_COLORS.fgDefault
|
||||
},
|
||||
styles: [
|
||||
{ tag: tags.name, color: APP_COLORS.fgPurple, cursor: 'default' }, // Variable
|
||||
{ tag: tags.name, cursor: 'default' }, // Variable
|
||||
{ tag: tags.comment, color: APP_COLORS.fgRed } // Error
|
||||
]
|
||||
});
|
||||
|
||||
const variables = restProps.availableVariables ?? Object.values(PromptVariableType);
|
||||
const autoCompleter = autocompletion({
|
||||
override: [variableCompletions(variables)],
|
||||
activateOnTyping: true,
|
||||
icons: false
|
||||
});
|
||||
|
||||
const editorExtensions = [
|
||||
EditorView.lineWrapping,
|
||||
EditorView.contentAttributes.of({ spellcheck: 'true' }),
|
||||
PromptLanguage
|
||||
PromptLanguage,
|
||||
variableHoverTooltip(variables),
|
||||
autoCompleter,
|
||||
noSpellcheckForVariables,
|
||||
markVariables(variables)
|
||||
];
|
||||
|
||||
function handleInput(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||
if (!thisRef.current?.view) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
if ((event.ctrlKey || event.metaKey) && event.code === 'Space') {
|
||||
notImplemented();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx('flex flex-col gap-2', cursor)}>
|
||||
<Label text={label} />
|
||||
|
@ -134,7 +136,6 @@ export const PromptInput = forwardRef<ReactCodeMirrorRef, PromptInputProps>(
|
|||
indentWithTab={false}
|
||||
onChange={onChange}
|
||||
editable={!disabled}
|
||||
onKeyDown={handleInput}
|
||||
{...restProps}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
import { syntaxTree } from '@codemirror/language';
|
||||
import { type EditorState, type Extension } from '@codemirror/state';
|
||||
import { hoverTooltip, type TooltipView } from '@codemirror/view';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { findEnvelopingNodes } from '@/utils/codemirror';
|
||||
|
||||
import { describePromptVariable } from '../../labels';
|
||||
import { type PromptVariableType } from '../../models/prompting';
|
||||
|
||||
import { Variable } from './parse/parser.terms';
|
||||
|
||||
/**
|
||||
* Retrieves variable from position in Editor.
|
||||
*/
|
||||
function findVariableAt(pos: number, state: EditorState) {
|
||||
const nodes = findEnvelopingNodes(pos, pos, syntaxTree(state), [Variable]);
|
||||
if (nodes.length !== 1) {
|
||||
return undefined;
|
||||
}
|
||||
const start = nodes[0].from;
|
||||
const end = nodes[0].to;
|
||||
const text = state.doc.sliceString(start, end);
|
||||
const match = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/.exec(text);
|
||||
const varName = match?.[1];
|
||||
if (!varName) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
varName: varName,
|
||||
start: start,
|
||||
end: end
|
||||
};
|
||||
}
|
||||
|
||||
const tooltipProducer = (available: string[]) => {
|
||||
return hoverTooltip((view, pos) => {
|
||||
const parse = findVariableAt(pos, view.state);
|
||||
if (!parse) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isAvailable = available.includes(parse.varName);
|
||||
return {
|
||||
pos: parse.start,
|
||||
end: parse.end,
|
||||
above: false,
|
||||
create: () => domTooltipVariable(parse.varName, isAvailable)
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export function variableHoverTooltip(available: string[]): Extension {
|
||||
return [tooltipProducer(available)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create DOM tooltip for {@link PromptVariableType}.
|
||||
*/
|
||||
function domTooltipVariable(varName: string, isAvailable: boolean): TooltipView {
|
||||
const dom = document.createElement('div');
|
||||
dom.className = clsx(
|
||||
'max-h-100 max-w-100 min-w-40',
|
||||
'dense',
|
||||
'px-2 py-1 flex flex-col',
|
||||
'rounded-md shadow-md',
|
||||
'cc-scroll-y',
|
||||
'text-sm bg-card',
|
||||
'select-none cursor-auto'
|
||||
);
|
||||
|
||||
const header = document.createElement('p');
|
||||
header.innerHTML = `<b>Переменная ${varName}</b>`;
|
||||
dom.appendChild(header);
|
||||
|
||||
const status = document.createElement('p');
|
||||
status.className = isAvailable ? 'text-green-700' : 'text-red-700';
|
||||
status.innerText = isAvailable ? 'Доступна для использования' : 'Недоступна для использования';
|
||||
dom.appendChild(status);
|
||||
|
||||
const desc = document.createElement('p');
|
||||
desc.className = '';
|
||||
desc.innerText = `Описание: ${describePromptVariable(varName as PromptVariableType)}`;
|
||||
dom.appendChild(desc);
|
||||
|
||||
return { dom: dom };
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
import { TextArea } from '@/components/input';
|
||||
|
||||
import { PromptInput } from '../../components/prompt-input';
|
||||
import { useAvailableVariables } from '../../stores/use-available-variables';
|
||||
|
||||
interface TabPromptEditProps {
|
||||
label: string;
|
||||
description: string;
|
||||
|
@ -8,6 +11,7 @@ interface TabPromptEditProps {
|
|||
}
|
||||
|
||||
export function TabPromptEdit({ label, description, text, setText }: TabPromptEditProps) {
|
||||
const availableVariables = useAvailableVariables();
|
||||
return (
|
||||
<div className='cc-column'>
|
||||
<div className='flex flex-col gap-2'>
|
||||
|
@ -20,13 +24,13 @@ export function TabPromptEdit({ label, description, text, setText }: TabPromptEd
|
|||
rows={1}
|
||||
/>
|
||||
<TextArea id='prompt-description' label='Описание' value={description} disabled noResize rows={3} />
|
||||
<TextArea
|
||||
<PromptInput
|
||||
id='prompt-text' //
|
||||
label='Текст шаблона'
|
||||
value={text}
|
||||
onChange={event => setText(event.target.value)}
|
||||
noResize
|
||||
rows={8}
|
||||
onChange={setText}
|
||||
maxHeight='10rem'
|
||||
availableVariables={availableVariables}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
/** Represents prompt variable type. */
|
||||
export const PromptVariableType = {
|
||||
BLOCK: 'block',
|
||||
|
||||
OSS: 'oss',
|
||||
|
||||
SCHEMA: 'schema',
|
||||
SCHEMA_THESAURUS: 'schema.thesaurus',
|
||||
// SCHEMA_GRAPH: 'schema.graph',
|
||||
// SCHEMA_TYPE_GRAPH: 'schema.type-graph',
|
||||
|
||||
CONSTITUENTA: 'constituenta',
|
||||
CONSTITUENTA_SYNTAX_TREE: 'constituent.ast'
|
||||
CONSTITUENTA_SYNTAX_TREE: 'constituent.ast',
|
||||
|
||||
OSS: 'oss',
|
||||
|
||||
BLOCK: 'block'
|
||||
} as const;
|
||||
export type PromptVariableType = (typeof PromptVariableType)[keyof typeof PromptVariableType];
|
||||
|
|
|
@ -7,6 +7,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|||
import clsx from 'clsx';
|
||||
import { useDebounce } from 'use-debounce';
|
||||
|
||||
import { PromptInput } from '@/features/ai/components/prompt-input';
|
||||
import { useAuthSuspense } from '@/features/auth';
|
||||
|
||||
import { MiniButton } from '@/components/control';
|
||||
|
@ -88,6 +89,11 @@ export function FormPromptTemplate({ promptTemplate, className, isMutable, toggl
|
|||
});
|
||||
}
|
||||
|
||||
function handleChangeText(newValue: string, onChange: (newValue: string) => void) {
|
||||
setSampleResult(null);
|
||||
onChange(newValue);
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
id={globalIDs.prompt_editor}
|
||||
|
@ -108,15 +114,22 @@ export function FormPromptTemplate({ promptTemplate, className, isMutable, toggl
|
|||
error={errors.description}
|
||||
disabled={isProcessing || !isMutable}
|
||||
/>
|
||||
<TextArea
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name='text'
|
||||
render={({ field }) => (
|
||||
<PromptInput
|
||||
id='prompt_text'
|
||||
label='Содержание' //
|
||||
fitContent
|
||||
label='Содержание'
|
||||
placeholder='Пример: Предложи дополнение для КС {{schema}}'
|
||||
className='disabled:min-h-9 max-h-64'
|
||||
{...register('text')}
|
||||
error={errors.text}
|
||||
value={field.value}
|
||||
onChange={newValue => handleChangeText(newValue, field.onChange)}
|
||||
disabled={isProcessing || !isMutable}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className='flex justify-between'>
|
||||
<Controller
|
||||
name='is_shared'
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import { useFitHeight } from '@/stores/app-layout';
|
||||
|
||||
import { describePromptVariable } from '../../labels';
|
||||
import { PromptVariableType } from '../../models/prompting';
|
||||
|
||||
/** Displays all prompt variable types with their descriptions. */
|
||||
export function TabViewVariables() {
|
||||
const panelHeight = useFitHeight('3rem');
|
||||
|
||||
return (
|
||||
<div className='pt-8'>
|
||||
<div className='mt-10 cc-scroll-y min-h-40' style={{ maxHeight: panelHeight }}>
|
||||
<ul className='space-y-1'>
|
||||
{Object.values(PromptVariableType).map(variableType => (
|
||||
<li key={variableType} className='flex flex-col'>
|
||||
|
|
|
@ -64,6 +64,16 @@
|
|||
padding: 0.15rem 0.375rem;
|
||||
}
|
||||
|
||||
.cm-completionInfo {
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
background-color: var(--color-input) !important;
|
||||
}
|
||||
|
||||
.cm-tooltip-autocomplete ul li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.cm-editor.cm-focused {
|
||||
border-color: var(--color-primary);
|
||||
outline: 2px solid var(--color-primary);
|
||||
|
|
Loading…
Reference in New Issue
Block a user