Skip to content

Commit

Permalink
ментальная карта в виде дерева
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexandr Karachev committed Jan 23, 2025
1 parent 783a541 commit b175a7c
Show file tree
Hide file tree
Showing 8 changed files with 240 additions and 105 deletions.
8 changes: 7 additions & 1 deletion asset/mental_map_quiz/MentalMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,13 @@ export default function MentalMap(element, deck, params) {
this.element.appendChild(TreeView({
name: json.name,
tree: json.treeData,
history: []
history,
params: {
story_id: params?.story_id,
slide_id: params?.slide_id,
mental_map_id: params.mentalMapId,
repetition_mode: repetitionMode,
}
}, new VoiceResponse(new MissingWordsRecognition({}))))
return
}
Expand Down
5 changes: 3 additions & 2 deletions asset/mental_map_quiz/TreeView/TreeView.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import TreeViewBody from "./TreeViewBody";
* @param name
* @param tree
* @param history
* @param {{story_id: number|null, slide_id: number|null, mental_map_id: string}} params
* @param {VoiceResponse} voiceResponse
* @returns {HTMLDivElement}
* @constructor
*/
export default function TreeView({name, tree, history}, voiceResponse) {
export default function TreeView({name, tree, history, params}, voiceResponse) {

const wrap = document.createElement('div')
wrap.style.display = 'flex'
Expand All @@ -21,7 +22,7 @@ export default function TreeView({name, tree, history}, voiceResponse) {
header.innerHTML = `<h2 class="h3 text-center">${name}</h2>`
wrap.appendChild(header)

wrap.appendChild(TreeViewBody(tree, voiceResponse, history))
wrap.appendChild(TreeViewBody(tree, voiceResponse, history, params))

return wrap
}
193 changes: 105 additions & 88 deletions asset/mental_map_quiz/TreeView/TreeViewBody.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ function createRow(node, level = 0) {
return row;
}

function processTreeNodes(list, body, history, voiceResponse) {
function processTreeNodes(list, body, history, voiceResponse, params) {
for (const listItem of list) {

const rowElement = body.querySelector(`.node-row[data-node-id='${listItem.id}']`)
Expand All @@ -108,88 +108,105 @@ function processTreeNodes(list, body, history, voiceResponse) {
const finalSpan = voiceResponseElem.querySelector('.final_span')
const interimSpan = voiceResponseElem.querySelector('.interim_span')

const treeVoiceControlElement = TreeVoiceControl(voiceResponse, (action, targetElement) => {
const startClickHandler = targetElement => {
finalSpan.innerHTML = ''
interimSpan.innerHTML = ''
resultSpan.innerHTML = ''
rowElement.querySelectorAll('.target-text').forEach(el => el.classList.add('selected'))
voiceResponse.onResult(args => {
finalSpan.innerHTML = args.args?.result
interimSpan.innerHTML = args.args?.interim
})
}

if (action === 'start') {
finalSpan.innerHTML = ''
interimSpan.innerHTML = ''
resultSpan.innerHTML = ''
const stopClickHandler = targetElement => {

rowElement.querySelectorAll('.target-text').forEach(el => el.classList.add('selected'))
rowElement.querySelectorAll('.target-text').forEach(el => el.classList.remove('selected'))

voiceResponse.onResult(args => {
finalSpan.innerHTML = args.args?.result
interimSpan.innerHTML = args.args?.interim
})
const userResponse = finalSpan.innerHTML
if (!userResponse) {
return
}
if (action === 'stop') {

rowElement.querySelectorAll('.target-text').forEach(el => el.classList.remove('selected'))
const rootElement = targetElement.closest('.node-row')
const backdrop = createRewriteContent('Обработка ответа...')
rootElement.appendChild(backdrop.getElement())

sendMessage(
`/admin/index.php?r=gpt/stream/retelling-rewrite`,
{
userResponse,
slideTexts: listItem.title
},
(message) => resultSpan.innerText = message,
(error) => console.log('error', error),
() => {
if (resultSpan.innerText.length === 0) {
backdrop.remove()
return
}
retellingResponseSpan.innerText = ''
sendMessage(`/admin/index.php?r=gpt/stream/retelling`, {
userResponse: resultSpan.innerText,
slideTexts: listItem.title
},
(message) => retellingResponseSpan.innerText = message,
(error) => console.log('error', error),
() => {

const userResponse = finalSpan.innerHTML
if (!userResponse) {
return
}
const content = rowElement.querySelector('.node-title').innerHTML

const rootElement = targetElement.closest('.node-row')
const backdrop = createRewriteContent('Обработка ответа...')
rootElement.appendChild(backdrop.getElement())

sendMessage(
`/admin/index.php?r=gpt/stream/retelling-rewrite`,
{
userResponse,
slideTexts: listItem.title
},
(message) => resultSpan.innerText = message,
(error) => console.log('error', error),
() => {
if (resultSpan.innerText.length === 0) {
backdrop.remove()
return
}
retellingResponseSpan.innerText = ''
sendMessage(`/admin/index.php?r=gpt/stream/retelling`, {
userResponse: resultSpan.innerText,
slideTexts: listItem.title
},
(message) => retellingResponseSpan.innerText = message,
(error) => console.log('error', error),
() => {
backdrop.remove()

const json = processOutputAsJson(retellingResponseSpan.innerText)
if (json === null) {
console.log('no json')
return
}
const val = Number(json?.overall_similarity)

const historyItem = history.find(i => i.id === nodeId)
if (val > 50) {
nodeStatusElement.innerHTML = nodeStatusSuccessHtml
const json = processOutputAsJson(retellingResponseSpan.innerText)
if (json === null) {
console.log('no json')
return
}
const val = Number(json?.overall_similarity)

if (historyItem) {
historyItem.done = true
} else {
history.push({id: nodeId, done: true})
}
const historyItem = history.find(i => i.id === nodeId)
if (val > 50) {
nodeStatusElement.innerHTML = nodeStatusSuccessHtml

processTreeNodes(list, body, history, voiceResponse)
if (historyItem) {
historyItem.done = true
} else {
if (historyItem) {
historyItem.done = false
} else {
history.push({id: nodeId, done: false})
}
nodeStatusElement.innerHTML = nodeStatusFailedHtml
history.push({id: nodeId, done: true})
}

processTreeNodes(list, body, history, voiceResponse, params)
} else {
if (historyItem) {
historyItem.done = false
} else {
history.push({id: nodeId, done: false})
}
nodeStatusElement.innerHTML = nodeStatusFailedHtml
}
)
}
)
}
})

saveUserResult({
...params,
image_fragment_id: nodeId,
overall_similarity: Number(json?.overall_similarity),
text_hiding_percentage: 0, // textHidingPercentage,
text_target_percentage: 0, // textTargetPercentage,
content,
}).then(response => {
if (response && response?.success) {
historyItem.all = response.history.all
historyItem.hiding = response.history.hiding
historyItem.target = response.history.target
}
})
}
)
}
)
}

const treeVoiceControlElement = TreeVoiceControl(voiceResponse, startClickHandler, stopClickHandler)

rowElement.querySelector('.node-control').appendChild(treeVoiceControlElement)

if (!rowElement.checkVisibility()) {
Expand All @@ -201,22 +218,6 @@ function processTreeNodes(list, body, history, voiceResponse) {
}

break

/*} else {
nodeStatusElement.innerHTML = historyItem.done ? nodeStatusSuccessHtml : nodeStatusFailedHtml
if (!historyItem.done) {
const treeVoiceControlElement = TreeVoiceControl(voiceResponse, (action, targetElement) => {
historyItem.done = true
processTreeNodes(list, body, history, voiceResponse)
})
rowElement.querySelector('.node-control').appendChild(treeVoiceControlElement)
break
}
}*/
}
}

Expand All @@ -227,7 +228,7 @@ function flatten(nodes, level = 0) {
])
}

export default function TreeViewBody(tree, voiceResponse, history) {
export default function TreeViewBody(tree, voiceResponse, history, params) {

const body = document.createElement('div')
body.classList.add('tree-body')
Expand All @@ -237,8 +238,8 @@ export default function TreeViewBody(tree, voiceResponse, history) {
body.appendChild(createRow(node))
})

const sortedList = [...list].sort((a, b) => a.level - b.level)
processTreeNodes(sortedList, body, history, voiceResponse)
//const sortedList = [...list].sort((a, b) => a.level - b.level)
processTreeNodes(list, body, history, voiceResponse, params)

return body
}
Expand Down Expand Up @@ -284,3 +285,19 @@ function resetNodeRow(row) {
.map(s => voiceResponseElem.querySelector(`${s}`).innerHTML = '')
row.querySelector('.node-control').innerHTML = ''
}

async function saveUserResult(payload) {
const response = await fetch(`/mental-map/save`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-CSRF-Token': $('meta[name=csrf-token]').attr('content')
},
body: JSON.stringify(payload),
})
if (!response.ok) {
throw new Error(response.statusText)
}
return await response.json()
}
6 changes: 3 additions & 3 deletions asset/mental_map_quiz/TreeView/TreeVoiceControl.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* @returns {HTMLDivElement}
* @constructor
*/
export default function TreeVoiceControl(voiceResponse, clickHandler) {
export default function TreeVoiceControl(voiceResponse, startClickHandler, stopClickHandler) {
const elem = document.createElement('div')
elem.classList.add('question-voice')
elem.style.bottom = '0'
Expand All @@ -30,10 +30,10 @@ export default function TreeVoiceControl(voiceResponse, clickHandler) {
voiceResponse.stop((args) => {
elem.querySelector('.gn').classList.remove('recording')
elem.querySelector('.pulse-ring').remove()
clickHandler('stop', e.target)
stopClickHandler(e.target)
})
} else {
clickHandler('start', e.target)
startClickHandler(e.target)
setTimeout(() => {
voiceResponse.start(new Event('voiceResponseStart'), 'ru-RU', function () {
const ring = document.createElement('div')
Expand Down
32 changes: 32 additions & 0 deletions backend/MentalMap/MentalMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,38 @@ public function getImages(): array
return $this->payload['map']['images'] ?? [];
}

public function isMentalMapAsTree(): bool
{
return $this->payload['treeView'] ?? false;
}

public function getTreeData(): array
{
return $this->payload['treeData'] ?? [];
}

private function flatten(array $element): array
{
$flatArray = [];
foreach ($element as $key => $node) {
if (array_key_exists('children', $node)) {
$flatArray = array_merge($flatArray, $this->flatten($node['children'] ?? []));
unset($node['children']);
}
$flatArray[] = $node;
}
return $flatArray;
}

public function getItems(): array
{
$items = $this->getImages();
if ($this->isMentalMapAsTree()) {
return $this->flatten($this->getTreeData());
}
return $items;
}

public function updateMapText(string $text): void
{
$payload = $this->payload;
Expand Down
4 changes: 2 additions & 2 deletions backend/views/mental-map-history/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class="h2"><?= $this->title ?></h1>
</thead>
<tbody>
<?php
foreach ($mentalMap->getImages() as $image): ?>
foreach ($mentalMap->getItems() as $image): ?>
<?php
$imageData = array_values(
array_filter(
Expand All @@ -93,7 +93,7 @@ static function (array $row) use ($mentalMap, $image): bool {
)[0] ?? [];
?>
<tr>
<td><?= $image['text']; ?></td>
<td><?= $image['text'] ?? $image['title']; ?></td>
<td><?= $imageData['content'] ?? '-'; ?></td>
<td><?= $imageData['all'] ?? '-'; ?></td>
<td><?= $imageData['hiding'] ?? '-'; ?></td>
Expand Down
12 changes: 11 additions & 1 deletion frontend/MentalMap/MentalMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,18 @@ public static function isDone(array $history): bool
if (count($history) === 0) {
return false;
}
return array_reduce($history, static function(bool $carry, array $item): bool {
return array_reduce($history, static function (bool $carry, array $item): bool {
return $carry && (int) $item['all'] > 0;
}, true);
}

public function isMentalMapAsTree(): bool
{
return $this->payload['treeView'] ?? false;
}

public function getTreeData(): array
{
return $this->payload['treeData'] ?? [];
}
}
Loading

0 comments on commit b175a7c

Please sign in to comment.