- setActiveTab(index as TabID)}>
-
-
-
-
-
-
-
- p.id === selected) ?? null}
- onChange={item => setSelected(item?.id ?? 0)}
- idFunc={item => String(item.id)}
- labelValueFunc={item => item.label}
- labelOptionFunc={item => item.label}
- placeholder='Выберите шаблон'
- className='w-full'
- />
- }>
- {selected ? : null}
- {selected ? : null}
- {selected ? : null}
-
-
-
+
+ p.id === selected) ?? null}
+ onChange={item => setSelected(item?.id ?? 0)}
+ idFunc={item => String(item.id)}
+ labelValueFunc={item => item.label}
+ labelOptionFunc={item => item.label}
+ placeholder='Выберите шаблон'
+ className='w-full'
+ />
+ }>
+ {selected ? : null}
+
);
}
diff --git a/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/menu-ai-prompt.tsx b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/menu-ai-prompt.tsx
new file mode 100644
index 00000000..fff32b09
--- /dev/null
+++ b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/menu-ai-prompt.tsx
@@ -0,0 +1,54 @@
+'use client';
+
+import { toast } from 'react-toastify';
+
+import { urls, useConceptNavigation } from '@/app';
+
+import { MiniButton } from '@/components/control';
+import { IconClone, IconEdit2 } from '@/components/icons';
+import { useDialogsStore } from '@/stores/dialogs';
+import { infoMsg } from '@/utils/labels';
+
+import { PromptTabID } from '../../pages/prompt-templates-page/templates-tabs';
+
+interface MenuAIPromptProps {
+ promptID: number;
+ generatedPrompt: string;
+}
+
+export function MenuAIPrompt({ promptID, generatedPrompt }: MenuAIPromptProps) {
+ const router = useConceptNavigation();
+ const hideDialog = useDialogsStore(state => state.hideDialog);
+
+ function navigatePrompt() {
+ hideDialog();
+ router.push({ path: urls.prompt_template(promptID, PromptTabID.EDIT) });
+ }
+
+ function handleCopyPrompt() {
+ void navigator.clipboard.writeText(generatedPrompt);
+ toast.success(infoMsg.promptReady);
+ }
+
+ return (
+
+ }
+ className='h-full pl-2 text-muted-foreground hover:text-primary cc-animate-color bg-transparent'
+ onClick={navigatePrompt}
+ />
+ }
+ className='h-full pl-2 text-muted-foreground hover:text-constructive cc-animate-color bg-transparent'
+ onClick={handleCopyPrompt}
+ disabled={!generatedPrompt}
+ />
+
+ );
+}
diff --git a/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/tab-prompt-edit.tsx b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/tab-prompt-edit.tsx
new file mode 100644
index 00000000..0a35906c
--- /dev/null
+++ b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/tab-prompt-edit.tsx
@@ -0,0 +1,34 @@
+import { TextArea } from '@/components/input';
+
+interface TabPromptEditProps {
+ label: string;
+ description: string;
+ text: string;
+ setText: (value: string) => void;
+}
+
+export function TabPromptEdit({ label, description, text, setText }: TabPromptEditProps) {
+ return (
+
+ );
+}
diff --git a/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/tab-prompt-result.tsx b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/tab-prompt-result.tsx
index b779b55c..fff08f75 100644
--- a/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/tab-prompt-result.tsx
+++ b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/tab-prompt-result.tsx
@@ -1,65 +1,17 @@
-import { useMemo } from 'react';
-import { toast } from 'react-toastify';
-
-import { MiniButton } from '@/components/control';
-import { IconClone } from '@/components/icons';
import { TextArea } from '@/components/input';
-import { infoMsg } from '@/utils/labels';
-
-import { usePromptTemplateSuspense } from '../../backend/use-prompt-template';
-import { PromptVariableType } from '../../models/prompting';
-import { extractPromptVariables } from '../../models/prompting-api';
-import { evaluatePromptVariable, useAIStore } from '../../stores/ai-context';
interface TabPromptResultProps {
- promptID: number;
+ prompt: string;
}
-export function TabPromptResult({ promptID }: TabPromptResultProps) {
- const { promptTemplate } = usePromptTemplateSuspense(promptID);
- const context = useAIStore();
- const variables = useMemo(() => {
- return promptTemplate ? extractPromptVariables(promptTemplate.text) : [];
- }, [promptTemplate]);
-
- const generatedMessage = (() => {
- if (!promptTemplate) {
- return '';
- }
- let result = promptTemplate.text;
- for (const variable of variables) {
- const type = Object.values(PromptVariableType).find(t => t === variable);
- let value = '';
- if (type) {
- value = evaluatePromptVariable(type, context) ?? '';
- }
- result = result.replace(new RegExp(`\{\{${variable}\}\}`, 'g'), value || `${variable}`);
- }
- return result;
- })();
-
- function handleCopyPrompt() {
- void navigator.clipboard.writeText(generatedMessage);
- toast.success(infoMsg.promptReady);
- }
-
+export function TabPromptResult({ prompt }: TabPromptResultProps) {
return (
-
- }
- onClick={handleCopyPrompt}
- disabled={!generatedMessage}
- />
-
-
+
);
}
diff --git a/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/tab-prompt-select.tsx b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/tab-prompt-select.tsx
deleted file mode 100644
index 0b214501..00000000
--- a/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/tab-prompt-select.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { TextArea } from '@/components/input';
-
-import { usePromptTemplateSuspense } from '../../backend/use-prompt-template';
-
-interface TabPromptSelectProps {
- promptID: number;
-}
-
-export function TabPromptSelect({ promptID }: TabPromptSelectProps) {
- const { promptTemplate } = usePromptTemplateSuspense(promptID);
-
- return (
-
- {promptTemplate && (
-
-
-
-
-
- )}
-
- );
-}
diff --git a/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/tab-prompt-variables.tsx b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/tab-prompt-variables.tsx
index 45b22a11..87a907e5 100644
--- a/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/tab-prompt-variables.tsx
+++ b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/tab-prompt-variables.tsx
@@ -1,18 +1,16 @@
'use client';
-import { usePromptTemplateSuspense } from '../../backend/use-prompt-template';
import { describePromptVariable } from '../../labels';
import { PromptVariableType } from '../../models/prompting';
import { extractPromptVariables } from '../../models/prompting-api';
import { useAvailableVariables } from '../../stores/use-available-variables';
interface TabPromptVariablesProps {
- promptID: number;
+ template: string;
}
-export function TabPromptVariables({ promptID }: TabPromptVariablesProps) {
- const { promptTemplate } = usePromptTemplateSuspense(promptID);
- const variables = extractPromptVariables(promptTemplate.text);
+export function TabPromptVariables({ template }: TabPromptVariablesProps) {
+ const variables = extractPromptVariables(template);
const availableTypes = useAvailableVariables();
return (
diff --git a/rsconcept/frontend/src/features/ai/labels.ts b/rsconcept/frontend/src/features/ai/labels.ts
index 88eb21cc..750d0de9 100644
--- a/rsconcept/frontend/src/features/ai/labels.ts
+++ b/rsconcept/frontend/src/features/ai/labels.ts
@@ -4,14 +4,18 @@ const describePromptVariableRecord: Record = {
[PromptVariableType.BLOCK]: 'Текущий блок операционной схемы',
[PromptVariableType.OSS]: 'Текущая операционная схема',
[PromptVariableType.SCHEMA]: 'Текущая концептуальная схема',
- [PromptVariableType.CONSTITUENTA]: 'Текущая конституента'
+ [PromptVariableType.SCHEMA_THESAURUS]: 'Термины и определения текущей концептуальной схемы',
+ [PromptVariableType.CONSTITUENTA]: 'Текущая конституента',
+ [PromptVariableType.CONSTITUENTA_SYNTAX_TREE]: 'Синтаксическое дерево конституенты'
};
const mockPromptVariableRecord: Record = {
[PromptVariableType.BLOCK]: 'Пример: Текущий блок операционной схемы',
[PromptVariableType.OSS]: 'Пример: Текущая операционная схема',
[PromptVariableType.SCHEMA]: 'Пример: Текущая концептуальная схема',
- [PromptVariableType.CONSTITUENTA]: 'Пример: Текущая конституента'
+ [PromptVariableType.SCHEMA_THESAURUS]: 'Пример\nТермин1 - Определение1\nТермин2 - Определение2',
+ [PromptVariableType.CONSTITUENTA]: 'Пример: Текущая конституента',
+ [PromptVariableType.CONSTITUENTA_SYNTAX_TREE]: 'Пример синтаксического дерева конституенты'
};
/** Retrieves description for {@link PromptVariableType}. */
diff --git a/rsconcept/frontend/src/features/ai/models/prompting-api.ts b/rsconcept/frontend/src/features/ai/models/prompting-api.ts
index 0f9c5c4b..fa9f2def 100644
--- a/rsconcept/frontend/src/features/ai/models/prompting-api.ts
+++ b/rsconcept/frontend/src/features/ai/models/prompting-api.ts
@@ -1,3 +1,10 @@
+import { type IBlock, type IOperationSchema, NodeType } from '@/features/oss/models/oss';
+import { CstType, type IConstituenta, type IRSForm } from '@/features/rsform';
+import { labelCstTypification } from '@/features/rsform/labels';
+import { isBasicConcept } from '@/features/rsform/models/rsform-api';
+
+import { PARAMETER } from '@/utils/constants';
+
import { mockPromptVariable } from '../labels';
/** Extracts a list of variables (as string[]) from a target string.
@@ -27,3 +34,94 @@ export function generateSample(target: string): string {
}
return result;
}
+
+/** Generates a prompt for a schema variable. */
+export function varSchema(schema: IRSForm): string {
+ let result = `Название концептуальной схемы: ${schema.title}\n`;
+ result += `[${schema.alias}] Описание: "${schema.description}"\n\n`;
+ result += 'Понятия:\n';
+ schema.items.forEach(item => {
+ result += `${item.alias} - "${labelCstTypification(item)}" - "${item.term_resolved}" - "${
+ item.definition_formal
+ }" - "${item.definition_resolved}" - "${item.convention}"\n`;
+ });
+ return result;
+}
+
+/** Generates a prompt for a schema thesaurus variable. */
+export function varSchemaThesaurus(schema: IRSForm): string {
+ let result = `Название концептуальной схемы: ${schema.title}\n`;
+ result += `[${schema.alias}] Описание: "${schema.description}"\n\n`;
+ result += 'Термины:\n';
+ schema.items.forEach(item => {
+ if (item.cst_type === CstType.AXIOM || item.cst_type === CstType.THEOREM) {
+ return;
+ }
+ if (isBasicConcept(item.cst_type)) {
+ result += `${item.term_resolved} - "${item.convention}"\n`;
+ } else {
+ result += `${item.term_resolved} - "${item.definition_resolved}"\n`;
+ }
+ });
+ return result;
+}
+
+/** Generates a prompt for a OSS variable. */
+export function varOSS(oss: IOperationSchema): string {
+ let result = `Название операционной схемы: ${oss.title}\n`;
+ result += `Сокращение: ${oss.alias}\n`;
+ result += `Описание: ${oss.description}\n`;
+ result += `Блоки: ${oss.blocks.length}\n`;
+ oss.hierarchy.topologicalOrder().forEach(blockID => {
+ const block = oss.itemByNodeID.get(blockID);
+ if (block?.nodeType !== NodeType.BLOCK) {
+ return;
+ }
+ result += `\nБлок ${block.id}: ${block.title}\n`;
+ result += `Описание: ${block.description}\n`;
+ result += `Предок: "${block.parent}"\n`;
+ });
+ result += `Операции: ${oss.operations.length}\n`;
+ oss.operations.forEach(operation => {
+ result += `\nОперация ${operation.id}: ${operation.alias}\n`;
+ result += `Название: ${operation.title}\n`;
+ result += `Описание: ${operation.description}\n`;
+ result += `Блок: ${operation.parent}\n`;
+ });
+ return result;
+}
+
+/** Generates a prompt for a block variable. */
+export function varBlock(target: IBlock, oss: IOperationSchema): string {
+ const blocks = oss.blocks.filter(block => block.parent === target.id);
+ const operations = oss.operations.filter(operation => operation.parent === target.id);
+ let result = `Название блока: ${target.title}\n`;
+ result += `Описание: "${target.description}"\n`;
+ result += '\nСодержание\n';
+ result += `Блоки: ${blocks.length}\n`;
+ blocks.forEach(block => {
+ result += `\nБлок ${block.id}: ${block.title}\n`;
+ result += `Описание: "${block.description}"\n`;
+ });
+ result += `Операции: ${operations.length}\n`;
+ operations.forEach(operation => {
+ result += `\nОперация ${operation.id}: ${operation.alias}\n`;
+ result += `Название: "${operation.title}"\n`;
+ result += `Описание: "${operation.description}"\n`;
+ });
+ return result;
+}
+
+/** Generates a prompt for a constituenta variable. */
+export function varConstituenta(cst: IConstituenta): string {
+ return JSON.stringify(cst, null, PARAMETER.indentJSON);
+}
+
+/** Generates a prompt for a constituenta syntax tree variable. */
+export function varSyntaxTree(cst: IConstituenta): string {
+ let result = `Конституента: ${cst.alias}\n`;
+ result += `Формальное выражение: ${cst.definition_formal}\n`;
+ result += `Дерево синтаксического разбора:\n`;
+ result += JSON.stringify(cst.parse.syntaxTree, null, PARAMETER.indentJSON);
+ return result;
+}
diff --git a/rsconcept/frontend/src/features/ai/models/prompting.ts b/rsconcept/frontend/src/features/ai/models/prompting.ts
index df7adef4..b24c6ac9 100644
--- a/rsconcept/frontend/src/features/ai/models/prompting.ts
+++ b/rsconcept/frontend/src/features/ai/models/prompting.ts
@@ -1,29 +1,15 @@
/** Represents prompt variable type. */
export const PromptVariableType = {
BLOCK: 'block',
- // BLOCK_TITLE: 'block.title',
- // BLOCK_DESCRIPTION: 'block.description',
- // BLOCK_CONTENTS: 'block.contents',
OSS: 'oss',
- // OSS_CONTENTS: 'oss.contents',
- // OSS_ALIAS: 'oss.alias',
- // OSS_TITLE: 'oss.title',
- // OSS_DESCRIPTION: 'oss.description',
SCHEMA: 'schema',
- // SCHEMA_ALIAS: 'schema.alias',
- // SCHEMA_TITLE: 'schema.title',
- // SCHEMA_DESCRIPTION: 'schema.description',
- // SCHEMA_THESAURUS: 'schema.thesaurus',
+ SCHEMA_THESAURUS: 'schema.thesaurus',
// SCHEMA_GRAPH: 'schema.graph',
// SCHEMA_TYPE_GRAPH: 'schema.type-graph',
- CONSTITUENTA: 'constituenta'
- // CONSTITUENTA_ALIAS: 'constituent.alias',
- // CONSTITUENTA_CONVENTION: 'constituent.convention',
- // CONSTITUENTA_DEFINITION: 'constituent.definition',
- // CONSTITUENTA_DEFINITION_FORMAL: 'constituent.definition-formal',
- // CONSTITUENTA_EXPRESSION_TREE: 'constituent.expression-tree'
+ CONSTITUENTA: 'constituenta',
+ CONSTITUENTA_SYNTAX_TREE: 'constituent.ast'
} as const;
export type PromptVariableType = (typeof PromptVariableType)[keyof typeof PromptVariableType];
diff --git a/rsconcept/frontend/src/features/ai/stores/ai-context.ts b/rsconcept/frontend/src/features/ai/stores/ai-context.ts
index e9bdf18e..d9538cff 100644
--- a/rsconcept/frontend/src/features/ai/stores/ai-context.ts
+++ b/rsconcept/frontend/src/features/ai/stores/ai-context.ts
@@ -2,9 +2,16 @@ import { create } from 'zustand';
import { type IBlock, type IOperationSchema } from '@/features/oss/models/oss';
import { type IConstituenta, type IRSForm } from '@/features/rsform';
-import { labelCstTypification } from '@/features/rsform/labels';
import { PromptVariableType } from '../models/prompting';
+import {
+ varBlock,
+ varConstituenta,
+ varOSS,
+ varSchema,
+ varSchemaThesaurus,
+ varSyntaxTree
+} from '../models/prompting-api';
interface AIContextStore {
currentOSS: IOperationSchema | null;
@@ -40,10 +47,12 @@ export function makeVariableSelector(variableType: PromptVariableType) {
case PromptVariableType.OSS:
return (state: AIContextStore) => ({ currentOSS: state.currentOSS });
case PromptVariableType.SCHEMA:
+ case PromptVariableType.SCHEMA_THESAURUS:
return (state: AIContextStore) => ({ currentSchema: state.currentSchema });
case PromptVariableType.BLOCK:
- return (state: AIContextStore) => ({ currentBlock: state.currentBlock });
+ return (state: AIContextStore) => ({ currentBlock: state.currentBlock, currentOSS: state.currentOSS });
case PromptVariableType.CONSTITUENTA:
+ case PromptVariableType.CONSTITUENTA_SYNTAX_TREE:
return (state: AIContextStore) => ({ currentConstituenta: state.currentConstituenta });
default:
return () => ({});
@@ -54,25 +63,18 @@ export function makeVariableSelector(variableType: PromptVariableType) {
export function evaluatePromptVariable(variableType: PromptVariableType, context: Partial): string {
switch (variableType) {
case PromptVariableType.OSS:
- return context.currentOSS?.title ?? '';
+ return context.currentOSS ? varOSS(context.currentOSS) : `!${variableType}!`;
case PromptVariableType.SCHEMA:
- return context.currentSchema ? generateSchemaPrompt(context.currentSchema) : '';
+ return context.currentSchema ? varSchema(context.currentSchema) : `!${variableType}!`;
+ case PromptVariableType.SCHEMA_THESAURUS:
+ return context.currentSchema ? varSchemaThesaurus(context.currentSchema) : `!${variableType}!`;
case PromptVariableType.BLOCK:
- return context.currentBlock?.title ?? '';
+ return context.currentBlock && context.currentOSS
+ ? varBlock(context.currentBlock, context.currentOSS)
+ : `!${variableType}!`;
case PromptVariableType.CONSTITUENTA:
- return context.currentConstituenta?.alias ?? '';
+ return context.currentConstituenta ? varConstituenta(context.currentConstituenta) : `!${variableType}!`;
+ case PromptVariableType.CONSTITUENTA_SYNTAX_TREE:
+ return context.currentConstituenta ? varSyntaxTree(context.currentConstituenta) : `!${variableType}!`;
}
}
-
-// ====== Internals =========
-function generateSchemaPrompt(schema: IRSForm): string {
- let body = `Название концептуальной схемы: ${schema.title}\n`;
- body += `[${schema.alias}] Описание: "${schema.description}"\n\n`;
- body += 'Понятия:\n';
- schema.items.forEach(item => {
- body += `${item.alias} - "${labelCstTypification(item)}" - "${item.term_resolved}" - "${
- item.definition_formal
- }" - "${item.definition_resolved}" - "${item.convention}"\n`;
- });
- return body;
-}
diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/oss-edit-state.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/oss-edit-state.tsx
index 7d501117..c2244258 100644
--- a/rsconcept/frontend/src/features/oss/pages/oss-page/oss-edit-state.tsx
+++ b/rsconcept/frontend/src/features/oss/pages/oss-page/oss-edit-state.tsx
@@ -16,7 +16,7 @@ import { promptText } from '@/utils/labels';
import { OperationType } from '../../backend/types';
import { useOssSuspense } from '../../backend/use-oss';
-import { type IOperation } from '../../models/oss';
+import { type IOperation, NodeType } from '../../models/oss';
import { OssEditContext, type OssTabID } from './oss-edit-context';
@@ -32,6 +32,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren state.setLocation);
const searchLocation = useLibrarySearchStore(state => state.location);
const setCurrentOSS = useAIStore(state => state.setCurrentOSS);
+ const setCurrentBlock = useAIStore(state => state.setCurrentBlock);
const { user } = useAuthSuspense();
const { schema } = useOssSuspense({ itemID: itemID });
@@ -57,6 +58,15 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren setCurrentOSS(null);
}, [schema, setCurrentOSS]);
+ useEffect(() => {
+ const selectedBlock = selectedItems.find(item => item.nodeType === NodeType.BLOCK);
+ if (selectedBlock) {
+ setCurrentBlock(selectedBlock);
+ return () => setCurrentBlock(null);
+ }
+ setCurrentBlock(null);
+ }, [selectedItems, setCurrentBlock]);
+
function navigateTab(tab: OssTabID) {
const url = urls.oss_props({
id: schema.id,
diff --git a/rsconcept/frontend/src/utils/constants.ts b/rsconcept/frontend/src/utils/constants.ts
index 4d5adb87..29497219 100644
--- a/rsconcept/frontend/src/utils/constants.ts
+++ b/rsconcept/frontend/src/utils/constants.ts
@@ -21,6 +21,8 @@ export const PARAMETER = {
graphNodePadding: 5, // Padding inside graph nodes (in pixels)
graphNodeRadius: 20, // Radius of graph nodes (in pixels)
+ indentJSON: 2, // Number of spaces for JSON indentation
+
logicLabel: 'LOGIC',
errorNodeLabel: '[ERROR]',
exteorVersion: '4.9.7'
diff --git a/rsconcept/frontend/src/utils/utils.ts b/rsconcept/frontend/src/utils/utils.ts
index 7d7cd449..25b956c9 100644
--- a/rsconcept/frontend/src/utils/utils.ts
+++ b/rsconcept/frontend/src/utils/utils.ts
@@ -5,6 +5,7 @@
import { toast } from 'react-toastify';
import { type AxiosError, type AxiosHeaderValue, type AxiosResponse, isAxiosError } from 'axios';
+import { PARAMETER } from './constants';
import { infoMsg, promptText } from './labels';
/**
@@ -160,7 +161,7 @@ export function convertToCSV(targetObj: readonly object[]): Blob {
* Convert object or array to JSON Blob.
*/
export function convertToJSON(targetObj: unknown): Blob {
- const jsonString = JSON.stringify(targetObj, null, 2);
+ const jsonString = JSON.stringify(targetObj, null, PARAMETER.indentJSON);
return new Blob([jsonString], { type: 'application/json;charset=utf-8;' });
}