From 9f8304d720911aa4f484d625851221043cd54296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B8=D1=81=D0=B0?= Date: Wed, 4 Dec 2024 17:51:02 +0300 Subject: [PATCH 01/10] added text-to-speech --- interface/public/index.html | 2 +- interface/server/server.js | 10 +- interface/src/App.tsx | 13 ++ interface/src/api/sc/agents/audioAgent.ts | 196 ++++++++++++++++++ interface/src/api/sc/agents/declarations.d.ts | 4 + .../src/assets/icon/TextToSpeech-icon.svg | 1 + interface/src/assets/icon/pause-icon.svg | 1 + .../src/assets/icon/textToSpeech-icon.svg | 1 + .../src/components/Chat/Message/Message.tsx | 41 +++- .../src/components/Chat/Message/styled.ts | 16 ++ interface/src/pages/Demo/Demo.tsx | 3 +- interface/tsconfig.json | 2 + 12 files changed, 283 insertions(+), 7 deletions(-) create mode 100644 interface/src/api/sc/agents/audioAgent.ts create mode 100644 interface/src/api/sc/agents/declarations.d.ts create mode 100644 interface/src/assets/icon/TextToSpeech-icon.svg create mode 100644 interface/src/assets/icon/pause-icon.svg create mode 100644 interface/src/assets/icon/textToSpeech-icon.svg diff --git a/interface/public/index.html b/interface/public/index.html index 034973aa3b..7e9b054d66 100644 --- a/interface/public/index.html +++ b/interface/public/index.html @@ -17,12 +17,12 @@ -
+ diff --git a/interface/server/server.js b/interface/server/server.js index 8ecbc01faf..37b45cd689 100644 --- a/interface/server/server.js +++ b/interface/server/server.js @@ -1,3 +1,9 @@ +const express = require('express'); + +const app = express(); + +app.use(express.json()); + require('dotenv').config(); const express = require('express'); @@ -79,9 +85,9 @@ app.use(express.static(path.join(__dirname, '../build'))); app.engine('html', require('ejs').renderFile); app.set('view engine', 'html'); app.set('views', path.resolve(__dirname, '../build')); - +app.use(express.json()); app.get('*', (req, res) => { res.render('index'); }); -server.listen(PORT, () => console.log(`Listen on port ${PORT}`)); +server.listen(PORT, () => console.log(`Listen on port ${PORT}`)); \ No newline at end of file diff --git a/interface/src/App.tsx b/interface/src/App.tsx index bdf294590f..af2d80f41b 100644 --- a/interface/src/App.tsx +++ b/interface/src/App.tsx @@ -13,6 +13,7 @@ const { Header, Content, Footer } = Layout; import { HeaderPanel } from "@components/Header"; import { FooterPanel } from "@components/Footer"; +import { audioAgent } from "@agents/audioAgent" const Demo = loadingComponent(lazy(() => import('@pages/Demo'))); const About = loadingComponent(lazy(() => import('@pages/About'))); @@ -88,9 +89,21 @@ export const App = () => { } } } + async function registerAgents() { + const actionInitiated = "action_initiated"; + const keynodes = [ + { id: actionInitiated, type: ScType.NodeConstNoRole }, + ]; + + const keynodesAddrs = await client.resolveKeynodes(keynodes); + + const eventParams = new ScEventParams(keynodesAddrs[actionInitiated], ScEventType.AddOutgoingEdge, audioAgent); + await client.eventsCreate([eventParams]); + } useEffect(() => { fetchColorValue(); + registerAgents(); }, []); const headerStyles = { diff --git a/interface/src/api/sc/agents/audioAgent.ts b/interface/src/api/sc/agents/audioAgent.ts new file mode 100644 index 0000000000..e3b2ea73bb --- /dev/null +++ b/interface/src/api/sc/agents/audioAgent.ts @@ -0,0 +1,196 @@ +import { client } from '@api/sc'; +import { ScAddr, ScConstruction, ScLinkContent, ScTemplate, ScType, ScLinkContentType } from 'ts-sc-client'; +import { makeAgent } from '@api/sc/agents/makeAgent'; +declare var responsiveVoice: any; +const nrelScTextTranslation = 'nrel_sc_text_translation'; +const langEn = 'lang_en'; +const langRu = 'lang_ru'; +const action = 'action'; +const actionInitiated = 'action_initiated'; +const actionText2Speech = 'action_text_to_speech'; +const rrel1 = 'rrel_1'; +const actionFinished = 'action_finished'; + +const baseKeynodes = [ + { id: action, type: ScType.NodeConstClass }, + { id: actionInitiated, type: ScType.NodeConstClass }, + { id: actionText2Speech, type: ScType.NodeConstClass }, + { id: rrel1, type: ScType.NodeConstRole }, + { id: actionFinished, type: ScType.NodeConstClass }, + { id: nrelScTextTranslation, type: ScType.NodeConstNoRole}, + { id: langEn, type: ScType.NodeConstClass }, + { id: langRu, type: ScType.NodeConstClass }, +]; + +const describeAgent = async ( + message: ScAddr, + keynodes: Record, +) => { + const actionNodeAlias = '_action_node'; + const template = new ScTemplate(); + template.triple(keynodes[action], ScType.EdgeAccessVarPosPerm, [ScType.NodeVar, actionNodeAlias]); + template.triple(keynodes[actionText2Speech], ScType.EdgeAccessVarPosPerm, actionNodeAlias); + template.tripleWithRelation( + actionNodeAlias, + ScType.EdgeAccessVarPosPerm, + message, + ScType.EdgeAccessVarPosPerm, + keynodes[rrel1], + ); + const generationResult = await client.templateGenerate(template, {}); + if (generationResult && generationResult.size > 0) { + let actionNode = generationResult.get(actionNodeAlias); + return actionNode; + } + return null; +}; + +export const callText2SpeechAgent = async (message: ScAddr) => { + const keynodes = await client.resolveKeynodes(baseKeynodes); + const actionNode = await describeAgent(message, keynodes); + if (actionNode == null) return; + const construction = new ScConstruction(); + construction.createEdge(ScType.EdgeAccessConstPosPerm, keynodes[actionInitiated], actionNode); + client.createElements(construction); +}; + +export const check = async(action: ScAddr,keynodes: Record) => { + const checkTemplate = new ScTemplate(); + checkTemplate.triple( + keynodes[actionText2Speech], + ScType.EdgeAccessVarPosPerm, + action, + ); + const checkResult = await client.templateSearch(checkTemplate); + if (checkResult.length==0) + { + console.log("No text to speech action"); + return false; + + } + console.log("Agent called"); + return true; +} + +export const getLinkNode= async(action: ScAddr,keynodes: Record) => { + const arg = '_arg'; + const translation='_translation' + const link="_link"; + const template = new ScTemplate(); + template.triple( + keynodes[actionText2Speech], + ScType.EdgeAccessVarPosPerm, + action, + ) + template.tripleWithRelation( + action, + ScType.EdgeAccessVarPosPerm, + [ScType.NodeVar, arg], + ScType.EdgeAccessVarPosPerm, + keynodes[rrel1], + ); + template.tripleWithRelation( + [ScType.NodeVar, translation], + ScType.EdgeDCommonVar, + arg, + ScType.EdgeAccessVarPosPerm, + keynodes[nrelScTextTranslation], + ) + template.triple(translation,ScType.EdgeAccessVarPosPerm,[ScType.LinkVar, link]); + const result=await client.templateSearch(template); + if (!result.length) { + console.log("Construction is undefined") + return false; + } + const linkNode = result[0].get(link); + return linkNode; +} + +export const getText= async(linkNode: ScAddr,keynodes: Record) => { + const resultText = await client.getLinkContents([linkNode]); + const text = resultText[0].data as string; + return text.replace(/<[^>]*>/g, '').replace(/\.{3}|[.;"]/g, '');} + + +export const getLang= async(linkNode: ScAddr,keynodes: Record) => { + const enTemplate=new ScTemplate(); + enTemplate.triple(keynodes[langEn],ScType.EdgeAccessVarPosPerm,linkNode); + const enResult=await client.templateSearch(enTemplate); + const ruTemplate=new ScTemplate(); + ruTemplate.triple(keynodes[langRu],ScType.EdgeAccessVarPosPerm,linkNode); + const ruResult=await client.templateSearch(ruTemplate); + if (ruResult.length!=0) + { + return "Russian Female" as string; + } + else if (enResult.length!=0) + { + return "UK English Female" as string; + } + else { + console.log("Language is undefined") + return false; + } +} + +export const speak= async(text: string,lang: string) => { + if(responsiveVoice.voiceSupport()) { + responsiveVoice.speak(text,lang); + } else { + console.log("Your browser doesn't support TTS"); + } +} + + +export const audioAgent = async(subscribedAddr: ScAddr, foundEgde: ScAddr, action: ScAddr) => { + const actionText2Speech = "action_text_to_speech"; + const nrelScTextTranslation = 'nrel_sc_text_translation'; + const langEn = 'lang_en'; + const langRu = 'lang_ru'; + const rrel1 = 'rrel_1'; + const actionFinished = 'action_finished'; + const baseKeynodes = [ + { id: actionText2Speech, type: ScType.NodeConstNoRole }, + { id: rrel1, type: ScType.NodeConstRole }, + { id: nrelScTextTranslation, type: ScType.NodeConstNoRole}, + { id: langEn, type: ScType.NodeConstClass }, + { id: langRu, type: ScType.NodeConstClass }, + { id: actionFinished, type: ScType.NodeConstClass }, + ]; + const keynodes = await client.resolveKeynodes(baseKeynodes); + if(await check(action,keynodes)===false) + { + return; + } + const linkNode=await getLinkNode(action,keynodes); + if (linkNode===false) { + const construction = new ScConstruction(); + construction.createEdge(ScType.EdgeAccessConstPosPerm,keynodes[actionFinished], action); + client.createElements(construction); + return; + } + const lang=await getLang(linkNode,keynodes); + const text=await getText(linkNode,keynodes); + if (lang===false) { + const construction = new ScConstruction(); + construction.createEdge(ScType.EdgeAccessConstPosPerm,keynodes[actionFinished], action); + client.createElements(construction); + return; + } + console.log(text); + speak(text,lang); + const construction = new ScConstruction(); + construction.createEdge(ScType.EdgeAccessConstPosPerm,keynodes[actionFinished], action); + client.createElements(construction); + return; +}; + + + + + + + + + + diff --git a/interface/src/api/sc/agents/declarations.d.ts b/interface/src/api/sc/agents/declarations.d.ts new file mode 100644 index 0000000000..4b3471f8b4 --- /dev/null +++ b/interface/src/api/sc/agents/declarations.d.ts @@ -0,0 +1,4 @@ +declare module "*.json" { + const value: any; + export default value; +} diff --git a/interface/src/assets/icon/TextToSpeech-icon.svg b/interface/src/assets/icon/TextToSpeech-icon.svg new file mode 100644 index 0000000000..c734ac7fb8 --- /dev/null +++ b/interface/src/assets/icon/TextToSpeech-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/interface/src/assets/icon/pause-icon.svg b/interface/src/assets/icon/pause-icon.svg new file mode 100644 index 0000000000..c7821f9454 --- /dev/null +++ b/interface/src/assets/icon/pause-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/interface/src/assets/icon/textToSpeech-icon.svg b/interface/src/assets/icon/textToSpeech-icon.svg new file mode 100644 index 0000000000..93b094d67f --- /dev/null +++ b/interface/src/assets/icon/textToSpeech-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/interface/src/components/Chat/Message/Message.tsx b/interface/src/components/Chat/Message/Message.tsx index adcb3a0a8b..d84f54d4de 100644 --- a/interface/src/components/Chat/Message/Message.tsx +++ b/interface/src/components/Chat/Message/Message.tsx @@ -1,4 +1,4 @@ -import { PropsWithChildren } from 'react'; +import { PropsWithChildren, useState } from 'react'; import { WrapperMessage, Time, @@ -10,12 +10,19 @@ import { Info, TextWrapper, WrapperLoadingIcon, + SpeakButton, } from './styled'; import { ReactComponent as LoadingIcon } from '@assets/icon/messageLoading.svg'; import { ReactComponent as ErrorIcon } from '@assets/icon/errorMessage-icon.svg'; import { ReactComponent as RebootIcon } from '@assets/icon/rebootErrorMessage-icon.svg'; +import { ReactComponent as TextToSpeechIcon } from '@assets/icon/TextToSpeech-icon.svg'; +import { ReactComponent as PauseIcon } from '@assets/icon/pause-icon.svg'; import { useLanguage } from '@hooks/useLanguage'; +import { ScAddr, ScEventParams, ScEventType, ScType ,ScConstruction} from 'ts-sc-client'; +import { callText2SpeechAgent } from '@api/sc/agents/audioAgent'; +declare var responsiveVoice: any; interface IProps { + addr:ScAddr; isLoading?: boolean; isLeft?: boolean; isError?: boolean; @@ -30,6 +37,7 @@ const textSend = { }; export const Message = ({ + addr, isLeft = false, isError = false, isLoading = false, @@ -37,8 +45,27 @@ export const Message = ({ children, onClick, }: PropsWithChildren) => { - const hookLanguage = useLanguage(); - + const hookLanguage = useLanguage(); + const [isTalking, setIsTalking] = useState(false); + const talking = async () => { + if (isTalking) { + setIsTalking(false); + responsiveVoice.cancel(); + }else{ + setIsTalking(true); + await callText2SpeechAgent(addr); + const checkIfSpeaking = setInterval(() => { + if(!responsiveVoice.isPlaying()) + { + setIsTalking(false); + clearInterval(checkIfSpeaking); + } + },2200); + } + + }; + + return ( <> <> @@ -53,6 +80,14 @@ export const Message = ({ {isLoading && !isLeft && } {isError && !isLeft && } + {isLeft && ( + + {isTalking ? ( + + ) : ( + + )} + )} diff --git a/interface/src/components/Chat/Message/styled.ts b/interface/src/components/Chat/Message/styled.ts index d9465e3a36..efcf902e95 100644 --- a/interface/src/components/Chat/Message/styled.ts +++ b/interface/src/components/Chat/Message/styled.ts @@ -50,6 +50,22 @@ export const WrapperLoadingIcon = styled.div` bottom: 4px; `; +//компонент для кнопки озвучивания +export const SpeakButton = styled.button` + position: absolute; + right: 10px; /* сдвигаем кнопку левее, чтобы она была рядом с иконкой загрузки */ + bottom: 4px; + background: transparent; + border: none; + cursor: pointer; + color: #4a5875; + font-size: 16px; + + &:hover { + color: #2c3e50; /* цвет при наведении */ + } +`; + export const WrapperRebootError = styled.div` margin: -4px 16px 6px 0px; padding: 3px; diff --git a/interface/src/pages/Demo/Demo.tsx b/interface/src/pages/Demo/Demo.tsx index f5d1f829c5..a1b8c7a992 100644 --- a/interface/src/pages/Demo/Demo.tsx +++ b/interface/src/pages/Demo/Demo.tsx @@ -52,11 +52,12 @@ export const Demo = () => { {showDate && } - {typeof item.text === 'string' ? ( + {typeof item.text === 'string' ? (
) : ( diff --git a/interface/tsconfig.json b/interface/tsconfig.json index 42ca1b3b1c..be1abbbddc 100644 --- a/interface/tsconfig.json +++ b/interface/tsconfig.json @@ -4,6 +4,8 @@ "noImplicitAny": false, "module": "commonjs", "target": "es6", + "types": ["node"], + "resolveJsonModule": true, "strict": true, "jsx": "react-jsx", "baseUrl": "./src", From 131142f3658d817d54efb8233b6d6ee84c23e6be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B8=D1=81=D0=B0?= Date: Wed, 4 Dec 2024 17:53:15 +0300 Subject: [PATCH 02/10] correction --- interface/src/api/sc/agents/declarations.d.ts | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 interface/src/api/sc/agents/declarations.d.ts diff --git a/interface/src/api/sc/agents/declarations.d.ts b/interface/src/api/sc/agents/declarations.d.ts deleted file mode 100644 index 4b3471f8b4..0000000000 --- a/interface/src/api/sc/agents/declarations.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module "*.json" { - const value: any; - export default value; -} From 71a27f58200c9c18e203f2cde190d4f2451ffd9f Mon Sep 17 00:00:00 2001 From: Alice Gavrilovich Date: Sun, 16 Feb 2025 16:50:59 +0300 Subject: [PATCH 03/10] docs: added AudioAgent documentation --- docs/agents/audioAgent.ru.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 docs/agents/audioAgent.ru.md diff --git a/docs/agents/audioAgent.ru.md b/docs/agents/audioAgent.ru.md new file mode 100644 index 0000000000..08f698f899 --- /dev/null +++ b/docs/agents/audioAgent.ru.md @@ -0,0 +1,27 @@ +# Агент преобразования текста в речь + +Данный агент выполняет озвучивание ответных сообщений на русском и английском языках. + +**Класс действий:** + +`action_text_to_speech` + +**Параметры:** + +1. `messageAddr` -- элемент класса `concept_message`. + +**Используемые библиотеки:** + +* [responsiveVoice](https://responsivevoice.org/) - для озвучивания сообщений. + +**Ход работы агента:** + +* Агент ищет в базе знаний sc-ссылку с текстом сообщения через отношение nrel_sc_text_translation; +* Далее он ищет класс языка,к которому принадлежит данная sc-ссылка; +* Затем агент озвучивает текст сообщения в соответствии с языком. + +**Пример входной конструкции:** + +**Язык реализации агента:** +TypeScript + From 75558e4f9906e4ca21d14ad23bfcfd43cdddd404 Mon Sep 17 00:00:00 2001 From: Alice Gavrilovich Date: Sun, 16 Feb 2025 17:02:21 +0300 Subject: [PATCH 04/10] feat(docs): AudioAgent input construction --- docs/agents/images/audioAgentInput.png | Bin 0 -> 45551 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/agents/images/audioAgentInput.png diff --git a/docs/agents/images/audioAgentInput.png b/docs/agents/images/audioAgentInput.png new file mode 100644 index 0000000000000000000000000000000000000000..df926c95d2292392625fc420b607c22130f63431 GIT binary patch literal 45551 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

IKY;;@`NSr6Z1QD?S%Q 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 literal 0 HcmV?d00001 From 320a60491d6d42f4a26e40cb24d48f996ce8f23e Mon Sep 17 00:00:00 2001 From: Alice Gavrilovich Date: Sun, 16 Feb 2025 17:07:05 +0300 Subject: [PATCH 05/10] docs: docs updated --- docs/agents/audioAgent.ru.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/agents/audioAgent.ru.md b/docs/agents/audioAgent.ru.md index 08f698f899..a1ceeb4568 100644 --- a/docs/agents/audioAgent.ru.md +++ b/docs/agents/audioAgent.ru.md @@ -21,6 +21,7 @@ * Затем агент озвучивает текст сообщения в соответствии с языком. **Пример входной конструкции:** + **Язык реализации агента:** TypeScript From c295063db7bb434ab0750c975646136b6f24e543 Mon Sep 17 00:00:00 2001 From: Alice Gavrilovich Date: Sun, 16 Feb 2025 17:17:54 +0300 Subject: [PATCH 06/10] docs: added documentation in English --- docs/agents/audioAgent.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 docs/agents/audioAgent.md diff --git a/docs/agents/audioAgent.md b/docs/agents/audioAgent.md new file mode 100644 index 0000000000..c09638d2ad --- /dev/null +++ b/docs/agents/audioAgent.md @@ -0,0 +1,30 @@ + +# Text to Speech Agent + +This agent voices response messages in Russian and English. + +**Action class:** + +`action_text_to_speech` + +**Parameters:** + +1. `messageAddr` -- class element `concept_message`. + +**Libraries used:** + +* [responsiveVoice](https://responsivevoice.org/) - для озвучивания сообщений. + +**Workflow:** + +* The agent searches the knowledge base for an sc-link with the message text through the nrel_sc_text_translation relationship; +* Next, it looks for the language class to which this sc-link belongs; +* The agent then voices the message text according to the language. + +**Example of an input structure:** + + + +**Agent implementation language:** + +TypeScript From c4d7fffcb9f3fbe203d45d84ad49002059a4a79e Mon Sep 17 00:00:00 2001 From: Alice Gavrilovich Date: Sun, 16 Feb 2025 17:18:56 +0300 Subject: [PATCH 07/10] docs: fix documentation --- docs/agents/audioAgent.ru.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/agents/audioAgent.ru.md b/docs/agents/audioAgent.ru.md index a1ceeb4568..d7f27a8b5b 100644 --- a/docs/agents/audioAgent.ru.md +++ b/docs/agents/audioAgent.ru.md @@ -21,8 +21,10 @@ * Затем агент озвучивает текст сообщения в соответствии с языком. **Пример входной конструкции:** + **Язык реализации агента:** + TypeScript From 53a19223a046a299f06f6af8f6bd96452a3c2a5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B8=D1=81=D0=B0?= Date: Sun, 4 May 2025 18:50:06 +0300 Subject: [PATCH 08/10] audio mode --- interface/src/App.tsx | 25 +- interface/src/api/sc/agents/audioAgent.ts | 10 +- .../api/sc/agents/switchToAudioModeAgent.ts | 291 ++++++++++++++++++ interface/src/components/Chat/Chat.tsx | 26 ++ .../concept_audio_mode_off message.scs | 24 ++ .../concept_audio_mode_on message.scs | 24 ++ kb/audio_mode/lr_audio_mode_off_message.gwf | 159 ++++++++++ kb/audio_mode/lr_audio_mode_on_message.gwf | 159 ++++++++++ kb/audio_mode/messages.gwf | 83 +++++ kb/audio_mode/phrases.gwf | 59 ++++ .../lr_classify_audio_mode_off_message.gwf | 220 +++++++++++++ .../lr_classify_audio_mode_on_message.gwf | 220 +++++++++++++ nika.ini | 2 +- 13 files changed, 1295 insertions(+), 7 deletions(-) create mode 100644 interface/src/api/sc/agents/switchToAudioModeAgent.ts create mode 100644 kb/audio_mode/concept_audio_mode_off message.scs create mode 100644 kb/audio_mode/concept_audio_mode_on message.scs create mode 100644 kb/audio_mode/lr_audio_mode_off_message.gwf create mode 100644 kb/audio_mode/lr_audio_mode_on_message.gwf create mode 100644 kb/audio_mode/messages.gwf create mode 100644 kb/audio_mode/phrases.gwf create mode 100644 kb/section_subject_domain_of_dialogues/section_subject_domain_of_dialog_control/section_subject_domain_of_dialogue_control_using_template_responses/logic_rules/lr_alternative_classification/lr_classify_audio_mode_off_message.gwf create mode 100644 kb/section_subject_domain_of_dialogues/section_subject_domain_of_dialog_control/section_subject_domain_of_dialogue_control_using_template_responses/logic_rules/lr_alternative_classification/lr_classify_audio_mode_on_message.gwf diff --git a/interface/src/App.tsx b/interface/src/App.tsx index af2d80f41b..63b8086082 100644 --- a/interface/src/App.tsx +++ b/interface/src/App.tsx @@ -13,7 +13,8 @@ const { Header, Content, Footer } = Layout; import { HeaderPanel } from "@components/Header"; import { FooterPanel } from "@components/Footer"; -import { audioAgent } from "@agents/audioAgent" +import { audioAgent } from "@agents/audioAgent"; +import { switchToAudioModeAgent } from "@agents/switchToAudioModeAgent" const Demo = loadingComponent(lazy(() => import('@pages/Demo'))); const About = loadingComponent(lazy(() => import('@pages/About'))); @@ -89,7 +90,7 @@ export const App = () => { } } } - async function registerAgents() { + async function registerAudioAgent() { const actionInitiated = "action_initiated"; const keynodes = [ { id: actionInitiated, type: ScType.NodeConstNoRole }, @@ -100,10 +101,26 @@ export const App = () => { const eventParams = new ScEventParams(keynodesAddrs[actionInitiated], ScEventType.AddOutgoingEdge, audioAgent); await client.eventsCreate([eventParams]); } - + + async function registerSwitchToAudioModeAgent() { + const actionInitiated = "action_initiated"; + const keynodes = [ + { id: actionInitiated, type: ScType.NodeConstNoRole }, + ]; + + const keynodesAddrs = await client.resolveKeynodes(keynodes); + + const eventParams = new ScEventParams( + keynodesAddrs[actionInitiated], + ScEventType.AddOutgoingEdge, + switchToAudioModeAgent); + await client.eventsCreate([eventParams]); + } + useEffect(() => { fetchColorValue(); - registerAgents(); + registerAudioAgent(); + registerSwitchToAudioModeAgent(); }, []); const headerStyles = { diff --git a/interface/src/api/sc/agents/audioAgent.ts b/interface/src/api/sc/agents/audioAgent.ts index e3b2ea73bb..f901b73812 100644 --- a/interface/src/api/sc/agents/audioAgent.ts +++ b/interface/src/api/sc/agents/audioAgent.ts @@ -68,12 +68,13 @@ export const check = async(action: ScAddr,keynodes: Record) => { return false; } - console.log("Agent called"); + console.log("text to speech action"); return true; } export const getLinkNode= async(action: ScAddr,keynodes: Record) => { const arg = '_arg'; + const answer_construction = '_answer_construction'; const translation='_translation' const link="_link"; const template = new ScTemplate(); @@ -89,10 +90,15 @@ export const getLinkNode= async(action: ScAddr,keynodes: Record) ScType.EdgeAccessVarPosPerm, keynodes[rrel1], ); + template.triple( + arg, + ScType.EdgeAccessVarPosTemp, + [ScType.NodeVar, answer_construction], + ) template.tripleWithRelation( [ScType.NodeVar, translation], ScType.EdgeDCommonVar, - arg, + answer_construction, ScType.EdgeAccessVarPosPerm, keynodes[nrelScTextTranslation], ) diff --git a/interface/src/api/sc/agents/switchToAudioModeAgent.ts b/interface/src/api/sc/agents/switchToAudioModeAgent.ts new file mode 100644 index 0000000000..46bbdfd984 --- /dev/null +++ b/interface/src/api/sc/agents/switchToAudioModeAgent.ts @@ -0,0 +1,291 @@ +import { client } from '@api/sc'; +import { ScAddr, ScConstruction, ScLinkContent, ScTemplate, ScType, ScLinkContentType } from 'ts-sc-client'; +import { makeAgent } from '@api/sc/agents/makeAgent'; +const actionInitiated = 'action_initiated'; +const actionSwitchToAudioMode= 'action_switch_to_audio_mode'; +const actionText2Speech='action_text_to_speech' +const rrel1 = 'rrel_1'; +const actionFinished = 'action_finished'; +const nrelMode= 'nrel_mode'; +const conceptTalkingMode='concept_talking_mode' +const conceptPauseMode='concept_pause_mode' +const messageProcessingProgram='message_processing_program' +const actionStandartMessageReply='action_standard_message_reply' +const rrel2 = 'rrel_2'; +const nrelGoTo = 'nrel_goto'; +const question = 'question'; +const actionClass = 'action'; +const actionFinishedSuccesfully = 'action_finished_successfully'; +const actionFinishedUnsuccesfully = 'action_finished_unsuccessfully'; + + +const baseKeynodes = [ + { id: actionClass, type: ScType.NodeConstClass }, + { id: actionInitiated, type: ScType.NodeConstClass }, + { id: rrel1, type: ScType.NodeConstRole }, + { id: actionFinished, type: ScType.NodeConstClass }, + { id: actionSwitchToAudioMode, type: ScType.NodeConstClass }, + { id: nrelMode, type: ScType.NodeConstNoRole}, + { id: actionSwitchToAudioMode, type: ScType.NodeConstClass }, + { id: conceptTalkingMode, type: ScType.NodeConstClass }, + { id: conceptPauseMode, type: ScType.NodeConstClass }, + { id: messageProcessingProgram, type: ScType.NodeConstStruct}, + { id: actionStandartMessageReply, type: ScType.NodeConstClass}, + { id: rrel2, type: ScType.NodeConstRole}, + { id: nrelGoTo, type: ScType.NodeConstNoRole}, + { id: question, type: ScType.NodeConstClass}, + { id: actionText2Speech, type: ScType.NodeConstClass}, + { id: actionFinishedSuccesfully, type: ScType.NodeConstClass}, + { id: actionFinishedUnsuccesfully, type: ScType.NodeConstClass}, + +]; + +const describeAgent = async (mode, + keynodes: Record, +) => { + const actionNodeAlias = '_action_node'; + const template = new ScTemplate(); + template.triple(keynodes[actionClass], ScType.EdgeAccessVarPosPerm, [ScType.NodeVar, actionNodeAlias]); + template.triple(keynodes[actionSwitchToAudioMode], ScType.EdgeAccessVarPosPerm, actionNodeAlias); + if (mode=="talking") + template.tripleWithRelation( + actionNodeAlias, + ScType.EdgeDCommonVar, + keynodes[conceptTalkingMode], + ScType.EdgeAccessVarPosPerm, + keynodes[nrelMode]); + else + template.tripleWithRelation( + actionNodeAlias, + ScType.EdgeDCommonVar, + keynodes[conceptPauseMode], + ScType.EdgeAccessVarPosPerm, + keynodes[nrelMode]); + + const generationResult = await client.templateGenerate(template, {}); + if (generationResult && generationResult.size > 0) { + let actionNode = generationResult.get(actionNodeAlias); + return actionNode; + } + return null; +}; + +export const callSwitchToAudioModeAgent = async (mode) => { + const keynodes = await client.resolveKeynodes(baseKeynodes); + const actionNode = await describeAgent(mode,keynodes); + if (actionNode == null) return; + const construction = new ScConstruction(); + construction.createEdge(ScType.EdgeAccessConstPosPerm, keynodes[actionInitiated], actionNode); + client.createElements(construction); +}; + +export const check = async(action: ScAddr,keynodes: Record) => { + const checkTemplate = new ScTemplate(); + checkTemplate.triple( + keynodes[actionSwitchToAudioMode], + ScType.EdgeAccessVarPosPerm, + action, + ); + const checkResult = await client.templateSearch(checkTemplate); + if (checkResult.length==0) + { + console.log("No switch mode action"); + return false; + + } + console.log("Switch mode action"); + return true; +} +export const check_mode = async(action:ScAddr,keynodes: Record)=>{ + const checkTemplate = new ScTemplate(); + checkTemplate.tripleWithRelation( + action, + ScType.EdgeDCommonVar, + keynodes[conceptTalkingMode], + ScType.EdgeAccessVarPosPerm, + keynodes[nrelMode] + ); + const checkResult = await client.templateSearch(checkTemplate); + if (checkResult.length==0) + { + const secondCheckTemplate = new ScTemplate(); + secondCheckTemplate.tripleWithRelation( + action, + ScType.EdgeDCommonVar, + keynodes[conceptPauseMode], + ScType.EdgeAccessVarPosPerm, + keynodes[nrelMode] + ); + const secondCheckResult = await client.templateSearch(secondCheckTemplate); + if (secondCheckResult.length==0) + { + return "" + + } + console.log("pause"); + return "pause" + } + console.log("talking"); + return "talking" + +} + +export const audio_mode_on = async(keynodes: Record)=>{ + const template = new ScTemplate(); + const _action_standard_message_reply_var="_action_standard_message_reply_var" + const _decomposition_node="_decomposition_node" + const _answer_message="_answer_message" + const _action_text_to_speech_var="_action_text_to_speech_var" + + + template.triple( + keynodes[messageProcessingProgram], + ScType.EdgeAccessVarPosPerm, + [ScType.NodeTuple,_decomposition_node], + ); + template.triple( + _decomposition_node, + ScType.EdgeAccess, + [ScType.Node,_action_standard_message_reply_var], + ); + template.triple( + keynodes[actionStandartMessageReply], + ScType.EdgeAccess, + _action_standard_message_reply_var, + ) + template.tripleWithRelation( + _action_standard_message_reply_var, + ScType.EdgeAccess, + [ScType.Node,_answer_message], + ScType.EdgeAccess, + keynodes[rrel2] + ) + const templateResult1 = await client.templateSearch(template); + template.tripleWithRelation( + _action_standard_message_reply_var, + ScType.EdgeDCommon, + [ScType.Node,_action_text_to_speech_var], + ScType.EdgeAccess, + keynodes[nrelGoTo] + ) + const templateResult2 = await client.templateSearch(template); + if (templateResult1.length!=0) + { + if (templateResult2.length!=0) + { + console.log("Already on"); + return false + } + const action_standard_message_reply_var = templateResult1[0].get(_action_standard_message_reply_var); + const decomposition_node = templateResult1[0].get(_decomposition_node); + const answer_message = templateResult1[0].get(_answer_message); + if (await update_message_processing_programm(action_standard_message_reply_var,decomposition_node,answer_message,keynodes)) return true + return false + + } + return false + + } + +export const update_message_processing_programm= async(action_example,node_var_tuple,answer_message,keynodes: Record)=>{ + const _action_tts_var="_action_tts_var" + const _edge_decomposition_of_action_membership="_edge_decomposition_of_action_membership" + const _edge_goto_realtion="_edge_goto_realtion" + const _edge_nrel_goto_membership="_edge_nrel_goto_membership" + const _edge_action_tts_membership="_edge_action_tts_membership" + const _edge_action_membership="_edge_action_membership" + const _edge_argument_relation="_edge_argument_relation" + const _edge_rrel1_membership="_edge_rrel1_membership" + const updatedConstruction = new ScConstruction(); + updatedConstruction.createNode(ScType.NodeVar,_action_tts_var); //1 + updatedConstruction.createEdge(ScType.EdgeDCommonVar,action_example,_action_tts_var,_edge_goto_realtion); + updatedConstruction.createEdge(ScType.EdgeAccessVarPosPerm,keynodes[nrelGoTo],_edge_goto_realtion,_edge_nrel_goto_membership); + updatedConstruction.createEdge(ScType.EdgeAccessVarPosPerm,node_var_tuple,_action_tts_var,_edge_decomposition_of_action_membership); //2 + updatedConstruction.createEdge(ScType.EdgeAccessVarPosPerm,keynodes[actionText2Speech],_action_tts_var,_edge_action_tts_membership); //3 + updatedConstruction.createEdge(ScType.EdgeAccessVarPosPerm,keynodes[actionClass],_action_tts_var,_edge_action_membership); //4 + updatedConstruction.createEdge(ScType.EdgeAccessVarPosPerm,_action_tts_var,answer_message,_edge_argument_relation); //5 + updatedConstruction.createEdge(ScType.EdgeAccessVarPosPerm,keynodes[rrel1],_edge_argument_relation,_edge_rrel1_membership); + updatedConstruction.createEdge(ScType.EdgeAccessConstPosPerm,keynodes[messageProcessingProgram],_action_tts_var); + updatedConstruction.createEdge(ScType.EdgeAccessConstPosPerm,keynodes[messageProcessingProgram],keynodes[actionText2Speech]); + updatedConstruction.createEdge(ScType.EdgeAccessConstPosPerm,keynodes[messageProcessingProgram],_edge_goto_realtion); + updatedConstruction.createEdge(ScType.EdgeAccessConstPosPerm,keynodes[messageProcessingProgram],_edge_decomposition_of_action_membership); + updatedConstruction.createEdge(ScType.EdgeAccessConstPosPerm,keynodes[messageProcessingProgram],_edge_nrel_goto_membership); + updatedConstruction.createEdge(ScType.EdgeAccessConstPosPerm,keynodes[messageProcessingProgram],_edge_action_tts_membership); + updatedConstruction.createEdge(ScType.EdgeAccessConstPosPerm,keynodes[messageProcessingProgram],_edge_action_membership); + updatedConstruction.createEdge(ScType.EdgeAccessConstPosPerm,keynodes[messageProcessingProgram],_edge_argument_relation); + updatedConstruction.createEdge(ScType.EdgeAccessConstPosPerm,keynodes[messageProcessingProgram],_edge_rrel1_membership); + const res = await client.createElements(updatedConstruction); // множестов + if (res && res.length > 0) { + return true + } + return false +} + +export const audio_mode_off = async(keynodes: Record)=>{ + const template = new ScTemplate(); + const _action_text_to_speech_var="_action_text_to_speech_var" + const _to_tts="_to_tts" + + template.triple( + keynodes[messageProcessingProgram], + ScType.EdgeAccessVarPosPerm, + [ScType.Node,_action_text_to_speech_var], + ); + + template.triple( + keynodes[messageProcessingProgram], + [ScType.EdgeAccessVarPosPerm, _to_tts], + keynodes[actionText2Speech], + ); + + template.triple( + keynodes[actionText2Speech], + ScType.EdgeAccess, + _action_text_to_speech_var, + ); + const templateResult = await client.templateSearch(template); + if (templateResult.length==0) + { + console.log("Already off"); + return false + + } + const action_tts_example = templateResult[0].get(_action_text_to_speech_var); + const to_tts = templateResult[0].get(_to_tts); + const res = await client.deleteElements([action_tts_example,to_tts]); + if (res) { + return true + } + return false + } + +export const switchToAudioModeAgent = async(subscribedAddr: ScAddr, foundEgde: ScAddr, action: ScAddr) => { + const keynodes = await client.resolveKeynodes(baseKeynodes); + if(await check(action,keynodes)===false) + { + return; + } + const mode=check_mode(action,keynodes); + if (await mode==="talking") + { + if (await audio_mode_on(keynodes)) + { + const construction = new ScConstruction(); + construction.createEdge(ScType.EdgeAccessConstPosPerm,keynodes[actionFinishedSuccesfully], action); + client.createElements(construction); + } + } + if (await mode==="pause") + { + if (await audio_mode_off(keynodes)) + { + const construction = new ScConstruction(); + construction.createEdge(ScType.EdgeAccessConstPosPerm,keynodes[actionFinishedSuccesfully], action); + client.createElements(construction); + } + } + const construction = new ScConstruction(); + construction.createEdge(ScType.EdgeAccessConstPosPerm,keynodes[actionFinishedUnsuccesfully], action); + client.createElements(construction); + return; +}; \ No newline at end of file diff --git a/interface/src/components/Chat/Chat.tsx b/interface/src/components/Chat/Chat.tsx index fc63408a26..90606cd3e0 100644 --- a/interface/src/components/Chat/Chat.tsx +++ b/interface/src/components/Chat/Chat.tsx @@ -28,11 +28,16 @@ import { WrapperFooter, } from './styled'; import { ReactComponent as SendIcon } from '@assets/icon/send-icon.svg'; +import { ReactComponent as AudioIcon } from '@assets/icon/TextToSpeech-icon.svg'; +import { ReactComponent as PauseIcon } from '@assets/icon/pause-icon.svg'; + import { ReactComponent as ArrowIcon } from '@assets/icon/arrowBackToLastMessage.svg'; import { Spinner } from '@components/Spinners/LoadSpinner'; import { WaitingSpinner } from '@components/Spinners/WaitingSpinner'; import { refSetter, throttle } from '@utils'; import { useLanguage } from '@hooks/useLanguage'; +import { callSwitchToAudioModeAgent } from '@api/sc/agents/switchToAudioModeAgent'; + interface IProps { onSend: (message: string) => void; @@ -129,6 +134,18 @@ export const Chat = forwardRef>( } }; const hookLanguage = useLanguage(); + const [isTalking, setIsTalking] = useState(false); + const talking = async () => { + if (isTalking) { + setIsTalking(false); + await callSwitchToAudioModeAgent("pause"); + + }else{ + setIsTalking(true); + await callSwitchToAudioModeAgent("talking"); + } + + }; useEffect(() => { const LOADING_HEIGHT = 43; @@ -225,6 +242,15 @@ export const Chat = forwardRef>( + + + {isTalking ? ( + + ) : ( + + )} + + diff --git a/kb/audio_mode/concept_audio_mode_off message.scs b/kb/audio_mode/concept_audio_mode_off message.scs new file mode 100644 index 0000000000..ec76bf3ddf --- /dev/null +++ b/kb/audio_mode/concept_audio_mode_off message.scs @@ -0,0 +1,24 @@ +concept_audio_mode_off_message +<- sc_node_not_relation; +<- rrel_key_sc_element: + ... + (* + => nrel_main_idtf: + [Опр. (сообщение о выключении режима озвучивания)] + (* <- lang_ru;; *);; + <- definition;; + <= nrel_sc_text_translation: + ... + (* + -> rrel_example: + [Сообщение о выключении режима озвучивания - атомарное сообщение, + которое говорит, что режим озвучивания выключен.] + (* <- lang_ru;; *);; + *);; + <= nrel_using_constants: + { + concept_atomic_message + };; + *);; + + diff --git a/kb/audio_mode/concept_audio_mode_on message.scs b/kb/audio_mode/concept_audio_mode_on message.scs new file mode 100644 index 0000000000..340151c4e3 --- /dev/null +++ b/kb/audio_mode/concept_audio_mode_on message.scs @@ -0,0 +1,24 @@ +concept_audio_mode_on_message +<- sc_node_not_relation; +<- rrel_key_sc_element: + ... + (* + => nrel_main_idtf: + [Опр. (сообщение о включении режима озвучивания)] + (* <- lang_ru;; *);; + <- definition;; + <= nrel_sc_text_translation: + ... + (* + -> rrel_example: + [Сообщение о включении режима озвучивания - атомарное сообщение, + которое говорит, что режим озвучивания включен.] + (* <- lang_ru;; *);; + *);; + <= nrel_using_constants: + { + concept_atomic_message + };; + *);; + + diff --git a/kb/audio_mode/lr_audio_mode_off_message.gwf b/kb/audio_mode/lr_audio_mode_off_message.gwf new file mode 100644 index 0000000000..b57cf30c1c --- /dev/null +++ b/kb/audio_mode/lr_audio_mode_off_message.gwf @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kb/audio_mode/lr_audio_mode_on_message.gwf b/kb/audio_mode/lr_audio_mode_on_message.gwf new file mode 100644 index 0000000000..55f97cf029 --- /dev/null +++ b/kb/audio_mode/lr_audio_mode_on_message.gwf @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kb/audio_mode/messages.gwf b/kb/audio_mode/messages.gwf new file mode 100644 index 0000000000..3607521288 --- /dev/null +++ b/kb/audio_mode/messages.gwf @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kb/audio_mode/phrases.gwf b/kb/audio_mode/phrases.gwf new file mode 100644 index 0000000000..fc4afa7f42 --- /dev/null +++ b/kb/audio_mode/phrases.gwf @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kb/section_subject_domain_of_dialogues/section_subject_domain_of_dialog_control/section_subject_domain_of_dialogue_control_using_template_responses/logic_rules/lr_alternative_classification/lr_classify_audio_mode_off_message.gwf b/kb/section_subject_domain_of_dialogues/section_subject_domain_of_dialog_control/section_subject_domain_of_dialogue_control_using_template_responses/logic_rules/lr_alternative_classification/lr_classify_audio_mode_off_message.gwf new file mode 100644 index 0000000000..ff9bf7f390 --- /dev/null +++ b/kb/section_subject_domain_of_dialogues/section_subject_domain_of_dialog_control/section_subject_domain_of_dialogue_control_using_template_responses/logic_rules/lr_alternative_classification/lr_classify_audio_mode_off_message.gwf @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kb/section_subject_domain_of_dialogues/section_subject_domain_of_dialog_control/section_subject_domain_of_dialogue_control_using_template_responses/logic_rules/lr_alternative_classification/lr_classify_audio_mode_on_message.gwf b/kb/section_subject_domain_of_dialogues/section_subject_domain_of_dialog_control/section_subject_domain_of_dialogue_control_using_template_responses/logic_rules/lr_alternative_classification/lr_classify_audio_mode_on_message.gwf new file mode 100644 index 0000000000..d15fc2714c --- /dev/null +++ b/kb/section_subject_domain_of_dialogues/section_subject_domain_of_dialog_control/section_subject_domain_of_dialogue_control_using_template_responses/logic_rules/lr_alternative_classification/lr_classify_audio_mode_on_message.gwf @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nika.ini b/nika.ini index 6b96d6010b..414758dc92 100644 --- a/nika.ini +++ b/nika.ini @@ -37,5 +37,5 @@ input_path = repo.path log_file = sc-builder.log [wit-ai] -server_token = ZFJMO24MDEMWCYZRGEAPXOOVUQFQ3C2Y +server_token = HMJV2FWND74OLTLEGAQKPFX2MRHY4U3G url = https://api.wit.ai/message From eeceb409e6c56688f0dfbbc2c860ccc83c365176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B8=D1=81=D0=B0?= Date: Sun, 4 May 2025 20:46:48 +0300 Subject: [PATCH 09/10] updated token --- nika.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nika.ini b/nika.ini index 414758dc92..7ee1b28c18 100644 --- a/nika.ini +++ b/nika.ini @@ -37,5 +37,5 @@ input_path = repo.path log_file = sc-builder.log [wit-ai] -server_token = HMJV2FWND74OLTLEGAQKPFX2MRHY4U3G +server_token = 623XLL7BD2TFT2YMQCCKVBJ2ESULV7J7 url = https://api.wit.ai/message From 3c4ef3a49a9269d1846492709e7af5c9b963d291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B8=D1=81=D0=B0?= Date: Sat, 10 May 2025 23:45:30 +0300 Subject: [PATCH 10/10] audioAgent update --- interface/src/api/sc/agents/audioAgent.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/interface/src/api/sc/agents/audioAgent.ts b/interface/src/api/sc/agents/audioAgent.ts index f901b73812..011b966049 100644 --- a/interface/src/api/sc/agents/audioAgent.ts +++ b/interface/src/api/sc/agents/audioAgent.ts @@ -27,16 +27,19 @@ const describeAgent = async ( keynodes: Record, ) => { const actionNodeAlias = '_action_node'; + const argNodeAlias='_arg_node' const template = new ScTemplate(); template.triple(keynodes[action], ScType.EdgeAccessVarPosPerm, [ScType.NodeVar, actionNodeAlias]); template.triple(keynodes[actionText2Speech], ScType.EdgeAccessVarPosPerm, actionNodeAlias); template.tripleWithRelation( actionNodeAlias, ScType.EdgeAccessVarPosPerm, - message, + [ScType.NodeVar,argNodeAlias], ScType.EdgeAccessVarPosPerm, keynodes[rrel1], ); + template.triple(argNodeAlias, ScType.EdgeAccessVarPosTemp, message); + const generationResult = await client.templateGenerate(template, {}); if (generationResult && generationResult.size > 0) { let actionNode = generationResult.get(actionNodeAlias);