Skip to content

Instantly share code, notes, and snippets.

@konard
Created January 26, 2026 20:05
Show Gist options
  • Select an option

  • Save konard/0abd132e1d07dcf8adfe18ea2adeedc6 to your computer and use it in GitHub Desktop.

Select an option

Save konard/0abd132e1d07dcf8adfe18ea2adeedc6 to your computer and use it in GitHub Desktop.
This file has been truncated, but you can view the full file.
# Solve.mjs Log - 2026-01-26T19:12:36.059Z
[2026-01-26T19:12:36.060Z] [INFO] 📁 Log file: /home/hive/solve-2026-01-26T19-12-36-059Z.log
[2026-01-26T19:12:36.061Z] [INFO] (All output will be logged here)
[2026-01-26T19:12:37.070Z] [INFO]
[2026-01-26T19:12:37.071Z] [INFO] 🚀 solve v1.9.0
[2026-01-26T19:12:37.071Z] [INFO] 🔧 Raw command executed:
[2026-01-26T19:12:37.071Z] [INFO] /home/hive/.nvm/versions/node/v20.20.0/bin/node /home/hive/.bun/bin/solve https://github.com/bpmbpm/rdf-grapher/issues/169 --tool agent --model opencode/big-pickle --attach-logs --verbose --no-tool-check --auto-resume-on-limit-reset --tokens-budget-stats
[2026-01-26T19:12:37.072Z] [INFO]
[2026-01-26T19:12:37.535Z] [INFO]
[2026-01-26T19:12:37.536Z] [WARNING] ⚠️ SECURITY WARNING: --attach-logs is ENABLED
[2026-01-26T19:12:37.536Z] [INFO]
[2026-01-26T19:12:37.536Z] [INFO] This option will upload the complete solution draft log file to the Pull Request.
[2026-01-26T19:12:37.537Z] [INFO] The log may contain sensitive information such as:
[2026-01-26T19:12:37.537Z] [INFO] • API keys, tokens, or secrets
[2026-01-26T19:12:37.537Z] [INFO] • File paths and directory structures
[2026-01-26T19:12:37.537Z] [INFO] • Command outputs and error messages
[2026-01-26T19:12:37.537Z] [INFO] • Internal system information
[2026-01-26T19:12:37.537Z] [INFO]
[2026-01-26T19:12:37.538Z] [INFO] ⚠️ DO NOT use this option with public repositories or if the log
[2026-01-26T19:12:37.538Z] [INFO] might contain sensitive data that should not be shared publicly.
[2026-01-26T19:12:37.538Z] [INFO]
[2026-01-26T19:12:37.538Z] [INFO] Continuing in 5 seconds... (Press Ctrl+C to abort)
[2026-01-26T19:12:37.538Z] [INFO]
[2026-01-26T19:12:42.544Z] [INFO]
[2026-01-26T19:12:42.573Z] [INFO] 💾 Disk space check: 43373MB available (2048MB required) ✅
[2026-01-26T19:12:42.575Z] [INFO] 🧠 Memory check: 11201MB available, swap: 4095MB (0MB used), total: 15296MB (256MB required) ✅
[2026-01-26T19:12:42.589Z] [INFO] ⏩ Skipping tool connection validation (dry-run mode or skip-tool-connection-check enabled)
[2026-01-26T19:12:42.589Z] [INFO] ⏩ Skipping GitHub authentication check (dry-run mode or skip-tool-connection-check enabled)
[2026-01-26T19:12:42.589Z] [INFO] 📋 URL validation:
[2026-01-26T19:12:42.590Z] [INFO] Input URL: https://github.com/bpmbpm/rdf-grapher/issues/169
[2026-01-26T19:12:42.590Z] [INFO] Is Issue URL: true
[2026-01-26T19:12:42.590Z] [INFO] Is PR URL: false
[2026-01-26T19:12:42.590Z] [INFO] 🔍 Checking repository access for auto-fork...
[2026-01-26T19:12:43.277Z] [INFO] Repository visibility: public
[2026-01-26T19:12:43.277Z] [INFO] ✅ Auto-fork: No write access detected, enabling fork mode
[2026-01-26T19:12:43.278Z] [INFO] ✅ Repository access check: Skipped (fork mode enabled)
[2026-01-26T19:12:43.631Z] [INFO] Repository visibility: public
[2026-01-26T19:12:43.631Z] [INFO] Auto-cleanup default: false (repository is public)
[2026-01-26T19:12:43.632Z] [INFO] 🔍 Auto-continue enabled: Checking for existing PRs for issue #169...
[2026-01-26T19:12:45.103Z] [INFO] 🔍 Fork mode: Checking for existing branches in konard/bpmbpm-rdf-grapher...
[2026-01-26T19:12:45.447Z] [INFO] 📋 Found 2 existing branch(es) in fork matching pattern 'issue-169-*':
[2026-01-26T19:12:45.447Z] [INFO] • issue-169-20db20d577e1
[2026-01-26T19:12:45.448Z] [INFO] • issue-169-a832e45b9abe
[2026-01-26T19:12:45.928Z] [INFO] 📝 No existing PRs found for issue #169 - creating new PR
[2026-01-26T19:12:45.929Z] [INFO] ✅ Using existing branch from fork: issue-169-a832e45b9abe
[2026-01-26T19:12:45.929Z] [INFO] Found 2 matching branch(es), selected most recent
[2026-01-26T19:12:46.275Z] [INFO] Branch issue-169-a832e45b9abe has a MERGED PR #171 - cannot reuse
[2026-01-26T19:12:46.275Z] [INFO] Will create a new branch for issue #169
[2026-01-26T19:12:46.275Z] [INFO] 📝 Issue mode: Working with issue #169
[2026-01-26T19:12:46.276Z] [INFO]
Creating temporary directory: /tmp/gh-issue-solver-1769454766276
[2026-01-26T19:12:46.278Z] [INFO]
🍴 Fork mode: ENABLED
[2026-01-26T19:12:46.279Z] [INFO] Checking fork status...
[2026-01-26T19:12:46.593Z] [INFO] 🔍 Detecting fork conflicts...
[2026-01-26T19:12:47.681Z] [INFO] ✅ No fork conflict: Safe to proceed
[2026-01-26T19:12:48.008Z] [INFO] ✅ Fork exists: konard/bpmbpm-rdf-grapher
[2026-01-26T19:12:48.008Z] [INFO] 🔍 Validating fork parent...
[2026-01-26T19:12:48.401Z] [INFO] ✅ Fork parent validated: bpmbpm/rdf-grapher
[2026-01-26T19:12:48.403Z] [INFO]
📥 Cloning repository: konard/bpmbpm-rdf-grapher
[2026-01-26T19:12:49.813Z] [INFO] ✅ Cloned to: /tmp/gh-issue-solver-1769454766276
[2026-01-26T19:12:49.846Z] [INFO] 🔗 Setting upstream: bpmbpm/rdf-grapher
[2026-01-26T19:12:49.881Z] [INFO] ℹ️ Upstream exists: Using existing upstream remote
[2026-01-26T19:12:49.881Z] [INFO] 🔄 Fetching upstream...
[2026-01-26T19:12:50.184Z] [INFO] ✅ Upstream fetched: Successfully
[2026-01-26T19:12:50.184Z] [INFO] 🔄 Syncing default branch...
[2026-01-26T19:12:50.567Z] [INFO] ℹ️ Default branch: main
[2026-01-26T19:12:50.658Z] [INFO] ✅ Default branch synced: with upstream/main
[2026-01-26T19:12:50.658Z] [INFO] 🔄 Pushing to fork: main branch
[2026-01-26T19:12:51.462Z] [INFO] ✅ Fork updated: Default branch pushed to fork
[2026-01-26T19:12:51.596Z] [INFO]
📌 Default branch: main
[2026-01-26T19:12:51.632Z] [INFO]
🌿 Creating branch: issue-169-a8b9d2f4d864 from main (default)
[2026-01-26T19:12:51.672Z] [INFO] 🔍 Verifying: Branch creation...
[2026-01-26T19:12:51.709Z] [INFO] ✅ Branch created: issue-169-a8b9d2f4d864
[2026-01-26T19:12:51.709Z] [INFO] ✅ Current branch: issue-169-a8b9d2f4d864
[2026-01-26T19:12:51.710Z] [INFO] Branch operation: Create new branch
[2026-01-26T19:12:51.710Z] [INFO] Branch verification: Matches expected
[2026-01-26T19:12:51.713Z] [INFO]
🚀 Auto PR creation: ENABLED
[2026-01-26T19:12:51.713Z] [INFO] Creating: Initial commit and draft PR...
[2026-01-26T19:12:51.713Z] [INFO]
[2026-01-26T19:12:51.750Z] [INFO] Using CLAUDE.md mode (--claude-file=true, --gitkeep-file=false, --auto-gitkeep-file=true)
[2026-01-26T19:12:51.750Z] [INFO] 📝 Creating: CLAUDE.md with task details
[2026-01-26T19:12:51.750Z] [INFO] Issue URL from argv['issue-url']: https://github.com/bpmbpm/rdf-grapher/issues/169
[2026-01-26T19:12:51.751Z] [INFO] Issue URL from argv._[0]: undefined
[2026-01-26T19:12:51.751Z] [INFO] Final issue URL: https://github.com/bpmbpm/rdf-grapher/issues/169
[2026-01-26T19:12:51.751Z] [INFO] CLAUDE.md already exists, appending task info...
[2026-01-26T19:12:51.751Z] [INFO] ✅ File created: CLAUDE.md
[2026-01-26T19:12:51.751Z] [INFO] 📦 Adding file: To git staging
[2026-01-26T19:12:51.828Z] [INFO] Git status after add: M CLAUDE.md
[2026-01-26T19:12:51.829Z] [INFO] 📝 Creating commit: With CLAUDE.md file
[2026-01-26T19:12:51.867Z] [INFO] ✅ Commit created: Successfully with CLAUDE.md
[2026-01-26T19:12:51.868Z] [INFO] Commit output: [issue-169-a8b9d2f4d864 1046695] Initial commit with task details
1 file changed, 14 insertions(+), 1 deletion(-)
[2026-01-26T19:12:51.909Z] [INFO] Commit hash: 1046695...
[2026-01-26T19:12:51.946Z] [INFO] Latest commit: 1046695 Initial commit with task details
[2026-01-26T19:12:51.991Z] [INFO] Git status: clean
[2026-01-26T19:12:52.036Z] [INFO] Remotes: origin https://github.com/konard/bpmbpm-rdf-grapher.git (fetch)
[2026-01-26T19:12:52.075Z] [INFO] Branch info: * issue-169-a8b9d2f4d864 1046695 [origin/main: ahead 1] Initial commit with task details
main 5d46575 [origin/main] Update readme.md
[2026-01-26T19:12:52.076Z] [INFO] 📤 Pushing branch: To remote repository...
[2026-01-26T19:12:52.076Z] [INFO] Push command: git push -u origin issue-169-a8b9d2f4d864
[2026-01-26T19:12:52.885Z] [INFO] Push exit code: 0
[2026-01-26T19:12:52.885Z] [INFO] Push output: remote:
remote: Create a pull request for 'issue-169-a8b9d2f4d864' on GitHub by visiting:
remote: https://github.com/konard/bpmbpm-rdf-grapher/pull/new/issue-169-a8b9d2f4d864
remote:
To https://github.com/konard/bpmbpm-rdf-grapher.git
* [new branch] issue-169-a8b9d2f4d864 -> issue-169-a8b9d2f4d864
branch 'issue-169-a8b9d2f4d864' set up to track 'origin/issue-169-a8b9d2f4d864'.
[2026-01-26T19:12:52.885Z] [INFO] ✅ Branch pushed: Successfully to remote
[2026-01-26T19:12:52.886Z] [INFO] Push output: remote:
remote: Create a pull request for 'issue-169-a8b9d2f4d864' on GitHub by visiting:
remote: https://github.com/konard/bpmbpm-rdf-grapher/pull/new/issue-169-a8b9d2f4d864
remote:
To https://github.com/konard/bpmbpm-rdf-grapher.git
* [new branch] issue-169-a8b9d2f4d864 -> issue-169-a8b9d2f4d864
branch 'issue-169-a8b9d2f4d864' set up to track 'origin/issue-169-a8b9d2f4d864'.
[2026-01-26T19:12:52.886Z] [INFO] Waiting for GitHub to sync...
[2026-01-26T19:12:55.375Z] [INFO] Compare API check: 1 commit(s) ahead of main
[2026-01-26T19:12:55.376Z] [INFO] GitHub compare API ready: 1 commit(s) found
[2026-01-26T19:12:55.904Z] [INFO] Branch verified on GitHub: issue-169-a8b9d2f4d864
[2026-01-26T19:12:56.228Z] [INFO] Remote commit SHA: 1046695...
[2026-01-26T19:12:56.229Z] [INFO] 📋 Getting issue: Title from GitHub...
[2026-01-26T19:12:56.559Z] [INFO] Issue title: "ver8a_a1b_agent1"
[2026-01-26T19:12:56.560Z] [INFO] 👤 Getting user: Current GitHub account...
[2026-01-26T19:12:56.853Z] [INFO] Current user: konard
[2026-01-26T19:12:57.123Z] [INFO] User is not a collaborator (will skip assignment)
[2026-01-26T19:12:57.125Z] [INFO] User is not a collaborator (will skip assignment)
[2026-01-26T19:12:57.126Z] [INFO] 🔄 Fetching: Latest main branch...
[2026-01-26T19:12:57.458Z] [INFO] ✅ Base updated: Fetched latest main
[2026-01-26T19:12:57.458Z] [INFO] 🔍 Checking: Commits between branches...
[2026-01-26T19:12:57.497Z] [INFO] Commits ahead of origin/main: 1
[2026-01-26T19:12:57.497Z] [INFO] ✅ Commits found: 1 commit(s) ahead
[2026-01-26T19:12:57.498Z] [INFO] 🔀 Creating PR: Draft pull request...
[2026-01-26T19:12:57.498Z] [INFO] 🎯 Target branch: main (default)
[2026-01-26T19:12:57.498Z] [INFO] PR Title: [WIP] ver8a_a1b_agent1
[2026-01-26T19:12:57.498Z] [INFO] Base branch: main
[2026-01-26T19:12:57.499Z] [INFO] Head branch: issue-169-a8b9d2f4d864
[2026-01-26T19:12:57.499Z] [INFO] Assignee: konard
[2026-01-26T19:12:57.499Z] [INFO] PR Body:
## 🤖 AI-Powered Solution Draft
This pull request is being automatically generated to solve issue bpmbpm/rdf-grapher#169.
### 📋 Issue Reference
Fixes bpmbpm/rdf-grapher#169
### 🚧 Status
**Work in Progress** - The AI assistant is currently analyzing and implementing the solution draft.
### 📝 Implementation Details
_Details will be added as the solution draft is developed..._
---
*This PR was created automatically by the AI issue solver*
[2026-01-26T19:12:57.501Z] [INFO] Command: cd "/tmp/gh-issue-solver-1769454766276" && gh pr create --draft --title "$(cat '/tmp/pr-title-1769454777501.txt')" --body-file "/tmp/pr-body-1769454777500.md" --base main --head konard:issue-169-a8b9d2f4d864 --repo bpmbpm/rdf-grapher
[2026-01-26T19:12:59.334Z] [INFO] 🔍 Verifying: PR creation...
[2026-01-26T19:12:59.702Z] [INFO] ✅ Verification: PR exists on GitHub
[2026-01-26T19:12:59.703Z] [INFO] ✅ PR created: #172
[2026-01-26T19:12:59.704Z] [INFO] 📍 PR URL: https://github.com/bpmbpm/rdf-grapher/pull/172
[2026-01-26T19:12:59.704Z] [INFO] ℹ️ Note: Could not assign (no permission)
[2026-01-26T19:12:59.704Z] [INFO] 🔗 Linking: Issue #169 to PR #172...
[2026-01-26T19:13:00.059Z] [INFO] Issue node ID: I_kwDOQxMaq87l5TDL
[2026-01-26T19:13:00.399Z] [INFO] PR node ID: PR_kwDOQxMaq86_bw_e
[2026-01-26T19:13:00.876Z] [INFO]
[2026-01-26T19:13:00.876Z] [WARNING] ⚠️ ISSUE LINK MISSING: PR not linked to issue
[2026-01-26T19:13:00.876Z] [INFO]
[2026-01-26T19:13:00.877Z] [WARNING] The PR was created from a fork but wasn't linked to the issue.
[2026-01-26T19:13:00.877Z] [WARNING] Expected: "Fixes bpmbpm/rdf-grapher#169" in PR body
[2026-01-26T19:13:00.877Z] [INFO]
[2026-01-26T19:13:00.877Z] [WARNING] To fix manually:
[2026-01-26T19:13:00.877Z] [WARNING] 1. Edit the PR description at: https://github.com/bpmbpm/rdf-grapher/pull/172
[2026-01-26T19:13:00.877Z] [WARNING] 2. Add this line: Fixes bpmbpm/rdf-grapher#169
[2026-01-26T19:13:00.878Z] [INFO]
[2026-01-26T19:13:01.235Z] [INFO] 👤 Current user: konard
[2026-01-26T19:13:01.235Z] [INFO]
📊 Comment counting conditions:
[2026-01-26T19:13:01.236Z] [INFO] prNumber: 172
[2026-01-26T19:13:01.236Z] [INFO] branchName: issue-169-a8b9d2f4d864
[2026-01-26T19:13:01.236Z] [INFO] isContinueMode: false
[2026-01-26T19:13:01.237Z] [INFO] Will count comments: true
[2026-01-26T19:13:01.237Z] [INFO] 💬 Counting comments: Checking for new comments since last commit...
[2026-01-26T19:13:01.238Z] [INFO] PR #172 on branch: issue-169-a8b9d2f4d864
[2026-01-26T19:13:01.238Z] [INFO] Owner/Repo: bpmbpm/rdf-grapher
[2026-01-26T19:13:01.710Z] [INFO] 📅 Last commit time (from API): 2026-01-26T19:12:51.000Z
[2026-01-26T19:13:02.694Z] [INFO] 💬 New PR comments: 0
[2026-01-26T19:13:02.695Z] [INFO] 💬 New PR review comments: 0
[2026-01-26T19:13:02.695Z] [INFO] 💬 New issue comments: 0
[2026-01-26T19:13:02.695Z] [INFO] Total new comments: 0
[2026-01-26T19:13:02.696Z] [INFO] Comment lines to add: No (saving tokens)
[2026-01-26T19:13:02.696Z] [INFO] PR review comments fetched: 0
[2026-01-26T19:13:02.696Z] [INFO] PR conversation comments fetched: 0
[2026-01-26T19:13:02.696Z] [INFO] Total PR comments checked: 0
[2026-01-26T19:13:05.579Z] [INFO] Feedback info will be added to prompt:
[2026-01-26T19:13:05.580Z] [INFO] - Pull request description was edited after last commit
[2026-01-26T19:13:05.580Z] [INFO] 📅 Getting timestamps: From GitHub servers...
[2026-01-26T19:13:06.003Z] [INFO] 📝 Issue updated: 2026-01-26T18:45:37.000Z
[2026-01-26T19:13:06.492Z] [INFO] 💬 Comments: None found
[2026-01-26T19:13:06.867Z] [INFO] 🔀 Recent PR: 2026-01-26T19:12:58.000Z
[2026-01-26T19:13:06.867Z] [INFO]
✅ Reference time: 2026-01-26T19:12:58.000Z
[2026-01-26T19:13:06.868Z] [INFO]
🔍 Checking for uncommitted changes to include as feedback...
[2026-01-26T19:13:06.910Z] [INFO] ✅ No uncommitted changes found
[2026-01-26T19:13:07.431Z] [INFO]
📝 Final prompt structure:
[2026-01-26T19:13:07.431Z] [INFO] Characters: 364
[2026-01-26T19:13:07.431Z] [INFO] System prompt characters: 7788
[2026-01-26T19:13:07.432Z] [INFO] Feedback info: Included
[2026-01-26T19:13:07.432Z] [INFO]
🤖 Executing Agent: OPENCODE/BIG-PICKLE
[2026-01-26T19:13:07.433Z] [INFO] Model: opencode/big-pickle
[2026-01-26T19:13:07.434Z] [INFO] Working directory: /tmp/gh-issue-solver-1769454766276
[2026-01-26T19:13:07.434Z] [INFO] Branch: issue-169-a8b9d2f4d864
[2026-01-26T19:13:07.434Z] [INFO] Prompt length: 364 chars
[2026-01-26T19:13:07.434Z] [INFO] System prompt length: 7788 chars
[2026-01-26T19:13:07.435Z] [INFO] Feedback info included: Yes (1 lines)
[2026-01-26T19:13:07.481Z] [INFO] 📈 System resources before execution:
[2026-01-26T19:13:07.482Z] [INFO] Memory: MemFree: 10817004 kB
[2026-01-26T19:13:07.482Z] [INFO] Load: 0.36 0.14 0.08 1/265 135690
[2026-01-26T19:13:07.483Z] [INFO]
📝 Raw command:
[2026-01-26T19:13:07.483Z] [INFO] (cd "/tmp/gh-issue-solver-1769454766276" && cat "/tmp/agent_prompt_1769454787482_133791.txt" | agent --model opencode/big-pickle --verbose)
[2026-01-26T19:13:07.483Z] [INFO]
[2026-01-26T19:13:07.483Z] [INFO] 📋 Command details:
[2026-01-26T19:13:07.483Z] [INFO] 📂 Working directory: /tmp/gh-issue-solver-1769454766276
[2026-01-26T19:13:07.483Z] [INFO] 🌿 Branch: issue-169-a8b9d2f4d864
[2026-01-26T19:13:07.484Z] [INFO] 🤖 Model: Agent OPENCODE/BIG-PICKLE
[2026-01-26T19:13:07.484Z] [INFO] 🍴 Fork: konard/bpmbpm-rdf-grapher
[2026-01-26T19:13:07.484Z] [INFO]
▶️ Streaming output:
[2026-01-26T19:13:07.795Z] [INFO] {
"type": "status",
"mode": "stdin-stream",
"message": "Agent CLI in continuous listening mode. Accepts JSON and plain text input.",
"hint": "Press CTRL+C to exit. Use --help for options.",
"acceptedFormats": [
"JSON object with \"message\" field",
"Plain text"
],
"options": {
"interactive": true,
"autoMergeQueuedMessages": true,
"alwaysAcceptStdin": true,
"compactJson": false
}
}
[2026-01-26T19:13:09.694Z] [INFO] {
[2026-01-26T19:13:09.695Z] [INFO] "type": "step_start",
[2026-01-26T19:13:09.695Z] [INFO] "timestamp": 1769454789693,
[2026-01-26T19:13:09.696Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:09.696Z] [INFO] "part": {
[2026-01-26T19:13:09.696Z] [INFO] "id": "prt_bfbb9643c001EJG9HZmFQ77vnL",
[2026-01-26T19:13:09.696Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:09.696Z] [INFO] "messageID": "msg_bfbb95d88001qeNDJOJ7OzcO2D",
[2026-01-26T19:13:09.697Z] [INFO] "type": "step-start",
[2026-01-26T19:13:09.697Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08"
[2026-01-26T19:13:09.698Z] [INFO] }
[2026-01-26T19:13:09.698Z] [INFO] }
[2026-01-26T19:13:14.865Z] [INFO] {
[2026-01-26T19:13:14.866Z] [INFO] "type": "text",
[2026-01-26T19:13:14.866Z] [INFO] "timestamp": 1769454794864,
[2026-01-26T19:13:14.866Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:14.867Z] [INFO] "part": {
[2026-01-26T19:13:14.867Z] [INFO] "id": "prt_bfbb974880017ExIq58fvSxk1I",
[2026-01-26T19:13:14.867Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:14.867Z] [INFO] "messageID": "msg_bfbb95d88001qeNDJOJ7OzcO2D",
[2026-01-26T19:13:14.868Z] [INFO] "type": "text",
[2026-01-26T19:13:14.868Z] [INFO] "text": "\nI'll help you solve issue #169. Let me start by understanding the issue details and checking the current state.",
[2026-01-26T19:13:14.868Z] [INFO] "time": {
[2026-01-26T19:13:14.868Z] [INFO] "start": 1769454794864,
[2026-01-26T19:13:14.868Z] [INFO] "end": 1769454794864
[2026-01-26T19:13:14.868Z] [INFO] }
[2026-01-26T19:13:14.869Z] [INFO] }
[2026-01-26T19:13:14.869Z] [INFO] }
[2026-01-26T19:13:15.506Z] [INFO] {
[2026-01-26T19:13:15.507Z] [INFO] "type": "tool_use",
[2026-01-26T19:13:15.507Z] [INFO] "timestamp": 1769454795505,
[2026-01-26T19:13:15.507Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:15.507Z] [INFO] "part": {
[2026-01-26T19:13:15.507Z] [INFO] "id": "prt_bfbb97844001qbL4YMIG3qnepV",
[2026-01-26T19:13:15.508Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:15.508Z] [INFO] "messageID": "msg_bfbb95d88001qeNDJOJ7OzcO2D",
[2026-01-26T19:13:15.508Z] [INFO] "type": "tool",
[2026-01-26T19:13:15.508Z] [INFO] "callID": "call_2f34f8d0fb854b3fa4780d86",
[2026-01-26T19:13:15.508Z] [INFO] "tool": "bash",
[2026-01-26T19:13:15.508Z] [INFO] "state": {
[2026-01-26T19:13:15.508Z] [INFO] "status": "completed",
[2026-01-26T19:13:15.508Z] [INFO] "input": {
[2026-01-26T19:13:15.509Z] [INFO] "command": "gh issue view 169 --repo bpmbpm/rdf-grapher",
[2026-01-26T19:13:15.509Z] [INFO] "description": "View issue 169 details"
[2026-01-26T19:13:15.509Z] [INFO] },
[2026-01-26T19:13:15.509Z] [INFO] "output": "title:\tver8a_a1b_agent1\nstate:\tCLOSED\nauthor:\tbpmbpm\nlabels:\t\ncomments:\t0\nassignees:\t\nprojects:\t\nmilestone:\t\nnumber:\t169\n--\n1 Проблема не решена: [ver8a_a1b_agent](https://github.com/bpmbpm/rdf-grapher/issues/167)\nНаличие файла Trig_VADv4.ttl в папке не влияет на загрузку в RDF данные или URI:. \nНе работает ни прямая загрузка файлов, так и механизм считывая в окно RDF данные или URI: из кода [index.html](https://github.com/bpmbpm/rdf-grapher/blob/main/ver8a/index.html). \nПроблема в коде, а не в наличии Trig_VADv4.ttl в https://github.com/bpmbpm/rdf-grapher/blob/main/ver8a \n\n2 Более того, даже копирования RDF-данных через буфер обмена не помогает: по кнопке \"Показать\" ничего не происходит. Также не работают Кнопки: Очистить, Тест, Сохранить как.\nОкно \"RDF данные или URI:\" отображается не полностью, например, исчезло поле \"Макс. длина VAD:\".\nДругие окна, например, Smart Design, Result in SPARQL тоже не отображаются.\n\nИсправь код. \n\nНапоминаю, что рабочая папка:\nhttps://github.com/bpmbpm/rdf-grapher/tree/main/ver8a\n\n",
[2026-01-26T19:13:15.509Z] [INFO] "title": "gh issue view 169 --repo bpmbpm/rdf-grapher",
[2026-01-26T19:13:15.509Z] [INFO] "metadata": {
[2026-01-26T19:13:15.509Z] [INFO] "output": "title:\tver8a_a1b_agent1\nstate:\tCLOSED\nauthor:\tbpmbpm\nlabels:\t\ncomments:\t0\nassignees:\t\nprojects:\t\nmilestone:\t\nnumber:\t169\n--\n1 Проблема не решена: [ver8a_a1b_agent](https://github.com/bpmbpm/rdf-grapher/issues/167)\nНаличие файла Trig_VADv4.ttl в папке не влияет на загрузку в RDF данные или URI:. \nНе работает ни прямая загрузка файлов, так и механизм считывая в окно RDF данные или URI: из кода [index.html](https://github.com/bpmbpm/rdf-grapher/blob/main/ver8a/index.html). \nПроблема в коде, а не в наличии Trig_VADv4.ttl в https://github.com/bpmbpm/rdf-grapher/blob/main/ver8a \n\n2 Более того, даже копирования RDF-данных через буфер обмена не помогает: по кнопке \"Показать\" ничего не происходит. Также не работают Кнопки: Очистить, Тест, Сохранить как.\nОкно \"RDF данные или URI:\" отображается не полностью, например, исчезло поле \"Макс. длина VAD:\".\nДругие окна, например, Smart Design, Result in SPARQL тоже не отображаются.\n\nИсправь код. \n\nНапоминаю, что рабочая папка:\nhttps://github.com/bpmbpm/rdf-grapher/tree/main/ver8a\n\n",
[2026-01-26T19:13:15.509Z] [INFO] "exit": 0,
[2026-01-26T19:13:15.510Z] [INFO] "description": "View issue 169 details"
[2026-01-26T19:13:15.510Z] [INFO] },
[2026-01-26T19:13:15.510Z] [INFO] "time": {
[2026-01-26T19:13:15.511Z] [INFO] "start": 1769454794827,
[2026-01-26T19:13:15.511Z] [INFO] "end": 1769454795504
[2026-01-26T19:13:15.511Z] [INFO] }
[2026-01-26T19:13:15.511Z] [INFO] }
[2026-01-26T19:13:15.511Z] [INFO] }
[2026-01-26T19:13:15.512Z] [INFO] }
[2026-01-26T19:13:15.515Z] [INFO] {
[2026-01-26T19:13:15.515Z] [INFO] "type": "step_finish",
[2026-01-26T19:13:15.515Z] [INFO] "timestamp": 1769454795514,
[2026-01-26T19:13:15.515Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:15.516Z] [INFO] "part": {
[2026-01-26T19:13:15.516Z] [INFO] "id": "prt_bfbb97af3001TF8fd1YnkAIEBN",
[2026-01-26T19:13:15.517Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:15.517Z] [INFO] "messageID": "msg_bfbb95d88001qeNDJOJ7OzcO2D",
[2026-01-26T19:13:15.517Z] [INFO] "type": "step-finish",
[2026-01-26T19:13:15.517Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:13:15.517Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08",
[2026-01-26T19:13:15.518Z] [INFO] "cost": 0,
[2026-01-26T19:13:15.518Z] [INFO] "tokens": {
[2026-01-26T19:13:15.518Z] [INFO] "input": 3326,
[2026-01-26T19:13:15.518Z] [INFO] "output": 284,
[2026-01-26T19:13:15.518Z] [INFO] "reasoning": 220,
[2026-01-26T19:13:15.518Z] [INFO] "cache": {
[2026-01-26T19:13:15.519Z] [INFO] "read": 10939,
[2026-01-26T19:13:15.519Z] [INFO] "write": 0
[2026-01-26T19:13:15.519Z] [INFO] }
[2026-01-26T19:13:15.519Z] [INFO] }
[2026-01-26T19:13:15.519Z] [INFO] }
[2026-01-26T19:13:15.519Z] [INFO] }
[2026-01-26T19:13:16.611Z] [INFO] {
[2026-01-26T19:13:16.612Z] [INFO] "type": "step_start",
[2026-01-26T19:13:16.612Z] [INFO] "timestamp": 1769454796610,
[2026-01-26T19:13:16.612Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:16.612Z] [INFO] "part": {
[2026-01-26T19:13:16.613Z] [INFO] "id": "prt_bfbb97f42001fbKzYy0946JNIh",
[2026-01-26T19:13:16.613Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:16.613Z] [INFO] "messageID": "msg_bfbb97b05001br4rwyiX5F5e2O",
[2026-01-26T19:13:16.614Z] [INFO] "type": "step-start",
[2026-01-26T19:13:16.614Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08"
[2026-01-26T19:13:16.614Z] [INFO] }
[2026-01-26T19:13:16.614Z] [INFO] }
[2026-01-26T19:13:17.564Z] [INFO] {
[2026-01-26T19:13:17.565Z] [INFO] "type": "tool_use",
[2026-01-26T19:13:17.565Z] [INFO] "timestamp": 1769454797563,
[2026-01-26T19:13:17.565Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:17.565Z] [INFO] "part": {
[2026-01-26T19:13:17.566Z] [INFO] "id": "prt_bfbb982f6001BFlaVZeYCR01h1",
[2026-01-26T19:13:17.566Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:17.566Z] [INFO] "messageID": "msg_bfbb97b05001br4rwyiX5F5e2O",
[2026-01-26T19:13:17.566Z] [INFO] "type": "tool",
[2026-01-26T19:13:17.566Z] [INFO] "callID": "call_c89471f5865b4d6dbaba604a",
[2026-01-26T19:13:17.566Z] [INFO] "tool": "bash",
[2026-01-26T19:13:17.567Z] [INFO] "state": {
[2026-01-26T19:13:17.567Z] [INFO] "status": "completed",
[2026-01-26T19:13:17.567Z] [INFO] "input": {
[2026-01-26T19:13:17.567Z] [INFO] "command": "git branch --show-current",
[2026-01-26T19:13:17.567Z] [INFO] "description": "Check current branch"
[2026-01-26T19:13:17.568Z] [INFO] },
[2026-01-26T19:13:17.568Z] [INFO] "output": "issue-169-a8b9d2f4d864\n",
[2026-01-26T19:13:17.568Z] [INFO] "title": "git branch --show-current",
[2026-01-26T19:13:17.568Z] [INFO] "metadata": {
[2026-01-26T19:13:17.568Z] [INFO] "output": "issue-169-a8b9d2f4d864\n",
[2026-01-26T19:13:17.568Z] [INFO] "exit": 0,
[2026-01-26T19:13:17.569Z] [INFO] "description": "Check current branch"
[2026-01-26T19:13:17.569Z] [INFO] },
[2026-01-26T19:13:17.569Z] [INFO] "time": {
[2026-01-26T19:13:17.569Z] [INFO] "start": 1769454797560,
[2026-01-26T19:13:17.569Z] [INFO] "end": 1769454797563
[2026-01-26T19:13:17.569Z] [INFO] }
[2026-01-26T19:13:17.569Z] [INFO] }
[2026-01-26T19:13:17.569Z] [INFO] }
[2026-01-26T19:13:17.570Z] [INFO] }
[2026-01-26T19:13:17.962Z] [INFO] {
[2026-01-26T19:13:17.962Z] [INFO] "type": "tool_use",
[2026-01-26T19:13:17.963Z] [INFO] "timestamp": 1769454797960,
[2026-01-26T19:13:17.963Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:17.963Z] [INFO] "part": {
[2026-01-26T19:13:17.963Z] [INFO] "id": "prt_bfbb9847d001c64YLHregD1Bnp",
[2026-01-26T19:13:17.963Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:17.964Z] [INFO] "messageID": "msg_bfbb97b05001br4rwyiX5F5e2O",
[2026-01-26T19:13:17.964Z] [INFO] "type": "tool",
[2026-01-26T19:13:17.965Z] [INFO] "callID": "call_ed10e456df7a47d593375e68",
[2026-01-26T19:13:17.965Z] [INFO] "tool": "list",
[2026-01-26T19:13:17.965Z] [INFO] "state": {
[2026-01-26T19:13:17.965Z] [INFO] "status": "completed",
[2026-01-26T19:13:17.965Z] [INFO] "input": {
[2026-01-26T19:13:17.965Z] [INFO] "path": "/tmp/gh-issue-solver-1769454766276"
[2026-01-26T19:13:17.965Z] [INFO] },
[2026-01-26T19:13:17.965Z] [INFO] "output": "/tmp/gh-issue-solver-1769454766276/\n experiments/\n Trig_VADv3_corrected.ttl\n Trig_VADv3_from_issue.ttl\n clean_example_data.py\n errors_fixed.md\n test_aggregation_mode.html\n test_issue111_fixes.html\n test_issue50_fixes.html\n test_issue50_fixes_v2.html\n test_issue56_fixes.html\n test_issue58_edge_directions.html\n test_issue60_row_wrapping.html\n test_issue62_vad_node_ids.html\n test_issue76_sparql_prefix.html\n test_label_wrapping.html\n test_style_selection.js\n test_trig_vad.html\n test_url_params.html\n test_vad_trig.html\n test_xlabel.html\n test_xlabel_alternatives.html\n test_xlabel_below_cds.html\n test_xlabel_position.html\n validate-trig.js\n validate_trig.py\n ver1/\n old/\n index_old1.html\n ver2/\n old/\n index2_fin1.html\n index2_old1.html\n index2_old2.html\n index2_old3.html\n readme.md\n SPARQLtest.md\n index.html\n readme.md\n ver4p/\n old/\n index4fin1.html\n index4fin2.html\n index4old1.html\n index4old2.html\n index4old3.html\n ontology_old1.md\n readme.md\n index.html\n info.md\n ontology.md\n readme.md\n ver6d/\n old/\n index6old1.html\n readme.md\n index.html\n readme.md\n ver7so/\n old/\n comment1_1old.md\n comment2_2old.md\n comment3_3old.md\n index1old.html\n index3old.html\n index4old.html\n index5old.html\n index7s_fin.html\n index_5old_Onto4d.html\n prog1.docx\n readme.md\n vad-basic-ontology.mermaid1old.md\n vad-basic-ontology.mermaid3old.md\n vad-basic-ontology1old.ttl\n vad-basic-ontology3old.ttl\n vad-basic-ontology5old.ttl\n NQuads.ttl\n NTriples.ttl\n TriG.ttl\n Trig_VAD.ttl\n Trig_VADv2.ttl\n Turtle.ttl\n VAD.ttl\n appendix-to-ontology.md\n comment1.md\n comment2.md\n comment3.md\n help.md\n index.html\n index_fin_full.html\n info.md\n pr_body.md\n predicate.xlsx\n readme.md\n styles.css\n term.md\n vad-basic-ontology.mermaid.md\n vad-basic-ontology.ttl\n ver8a/\n doc/\n problem.md\n old/\n readme.md\n Trig_VADv4.ttl\n filelist.md\n index.html\n readme.md\n sparql-queries-choice.js\n sparql-queries-code.js\n styles.css\n vad-basic-ontology.ttl\n vad-basic-ontology_tech_Appendix.ttl\n vad-validation-rules.js\n virtualRDFdata_VADv4.ttl\n README.md\n",
[2026-01-26T19:13:17.966Z] [INFO] "title": "",
[2026-01-26T19:13:17.966Z] [INFO] "metadata": {
[2026-01-26T19:13:17.966Z] [INFO] "count": 100,
[2026-01-26T19:13:17.966Z] [INFO] "truncated": true
[2026-01-26T19:13:17.966Z] [INFO] },
[2026-01-26T19:13:17.966Z] [INFO] "time": {
[2026-01-26T19:13:17.966Z] [INFO] "start": 1769454797951,
[2026-01-26T19:13:17.967Z] [INFO] "end": 1769454797960
[2026-01-26T19:13:17.967Z] [INFO] }
[2026-01-26T19:13:17.967Z] [INFO] }
[2026-01-26T19:13:17.967Z] [INFO] }
[2026-01-26T19:13:17.967Z] [INFO] }
[2026-01-26T19:13:17.992Z] [INFO] {
[2026-01-26T19:13:17.993Z] [INFO] "type": "text",
[2026-01-26T19:13:17.993Z] [INFO] "timestamp": 1769454797991,
[2026-01-26T19:13:17.993Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:17.994Z] [INFO] "part": {
[2026-01-26T19:13:17.994Z] [INFO] "id": "prt_bfbb97f6c001yl1KJClV633DAo",
[2026-01-26T19:13:17.994Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:17.994Z] [INFO] "messageID": "msg_bfbb97b05001br4rwyiX5F5e2O",
[2026-01-26T19:13:17.994Z] [INFO] "type": "text",
[2026-01-26T19:13:17.994Z] [INFO] "text": "\nLet me check the current branch and explore the ver8a folder structure:",
[2026-01-26T19:13:17.995Z] [INFO] "time": {
[2026-01-26T19:13:17.995Z] [INFO] "start": 1769454797991,
[2026-01-26T19:13:17.995Z] [INFO] "end": 1769454797991
[2026-01-26T19:13:17.995Z] [INFO] }
[2026-01-26T19:13:17.995Z] [INFO] }
[2026-01-26T19:13:17.995Z] [INFO] }
[2026-01-26T19:13:17.999Z] [INFO] {
[2026-01-26T19:13:18.000Z] [INFO] "type": "step_finish",
[2026-01-26T19:13:18.000Z] [INFO] "timestamp": 1769454797999,
[2026-01-26T19:13:18.000Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:18.000Z] [INFO] "part": {
[2026-01-26T19:13:18.001Z] [INFO] "id": "prt_bfbb984a8001QU674E9exZa5Wc",
[2026-01-26T19:13:18.001Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:18.001Z] [INFO] "messageID": "msg_bfbb97b05001br4rwyiX5F5e2O",
[2026-01-26T19:13:18.001Z] [INFO] "type": "step-finish",
[2026-01-26T19:13:18.001Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:13:18.002Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08",
[2026-01-26T19:13:18.002Z] [INFO] "cost": 0,
[2026-01-26T19:13:18.002Z] [INFO] "tokens": {
[2026-01-26T19:13:18.003Z] [INFO] "input": 405,
[2026-01-26T19:13:18.003Z] [INFO] "output": 73,
[2026-01-26T19:13:18.003Z] [INFO] "reasoning": 1,
[2026-01-26T19:13:18.003Z] [INFO] "cache": {
[2026-01-26T19:13:18.003Z] [INFO] "read": 14267,
[2026-01-26T19:13:18.003Z] [INFO] "write": 0
[2026-01-26T19:13:18.004Z] [INFO] }
[2026-01-26T19:13:18.004Z] [INFO] }
[2026-01-26T19:13:18.004Z] [INFO] }
[2026-01-26T19:13:18.004Z] [INFO] }
[2026-01-26T19:13:20.555Z] [INFO] {
[2026-01-26T19:13:20.556Z] [INFO] "type": "step_start",
[2026-01-26T19:13:20.556Z] [INFO] "timestamp": 1769454800554,
[2026-01-26T19:13:20.556Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:20.556Z] [INFO] "part": {
[2026-01-26T19:13:20.557Z] [INFO] "id": "prt_bfbb98ea9001nK1cV3SRCQhvKL",
[2026-01-26T19:13:20.557Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:20.557Z] [INFO] "messageID": "msg_bfbb984bb0014qxmcQQmzb9oB8",
[2026-01-26T19:13:20.557Z] [INFO] "type": "step-start",
[2026-01-26T19:13:20.557Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08"
[2026-01-26T19:13:20.558Z] [INFO] }
[2026-01-26T19:13:20.558Z] [INFO] }
[2026-01-26T19:13:21.532Z] [INFO] {
[2026-01-26T19:13:21.533Z] [INFO] "type": "tool_use",
[2026-01-26T19:13:21.533Z] [INFO] "timestamp": 1769454801530,
[2026-01-26T19:13:21.533Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:21.533Z] [INFO] "part": {
[2026-01-26T19:13:21.534Z] [INFO] "id": "prt_bfbb99271001MvQ2v70f0z8nIy",
[2026-01-26T19:13:21.534Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:21.534Z] [INFO] "messageID": "msg_bfbb984bb0014qxmcQQmzb9oB8",
[2026-01-26T19:13:21.534Z] [INFO] "type": "tool",
[2026-01-26T19:13:21.534Z] [INFO] "callID": "call_c241b33f86cd47b1a224d53b",
[2026-01-26T19:13:21.534Z] [INFO] "tool": "read",
[2026-01-26T19:13:21.535Z] [INFO] "state": {
[2026-01-26T19:13:21.535Z] [INFO] "status": "completed",
[2026-01-26T19:13:21.535Z] [INFO] "input": {
[2026-01-26T19:13:21.535Z] [INFO] "filePath": "/tmp/gh-issue-solver-1769454766276/ver8a/index.html"
[2026-01-26T19:13:21.535Z] [INFO] },
[2026-01-26T19:13:21.535Z] [INFO] "output": "<file>\n00001| <!DOCTYPE html>\n00002| <html lang=\"ru\">\n00003| <head>\n00004| <meta charset=\"UTF-8\">\n00005| <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n00006| <title>RDF Grapher ver8 - Визуализация RDF данных с поддержкой TriG + SPARQL Smart Design + Ontology </title>\n00007| \n00008| <!--\n00009| RDF Grapher ver8a - Сервис для парсинга RDF данных и их визуализации в виде графа\n00010| Новые возможности ver8: SPARQL Smart Design + Ontology\n00011| Этот сервис является продолжением https://www.ldf.fi/service/rdf-grapher в части BPM\n00012| Использует клиентские JavaScript библиотеки:\n00013| - N3.js для парсинга RDF (замена Redland Raptor)\n00014| - Viz.js для рендеринга графов (замена Graphviz)\n00015| \n00016| Сервис работает полностью на стороне клиента и может быть размещен на GitHub Pages\n00017| -->\n00018| \n00019| <!-- Стили для интерфейса -->\n00020| <link rel=\"stylesheet\" href=\"styles.css\">\n00021| </head>\n00022| <body>\n00023| <!-- Контейнер для панелей свойств узлов -->\n00024| <div id=\"properties-panels-container\"></div>\n00025| \n00026| <div class=\"description\">\n00027| <p>\n00028| <strong>Загрузить пример RDF данных:</strong>\n00029| <span class=\"example-link\" onclick=\"loadExampleTrigVADv4()\">Trig VADv4</span>\n00030| </p>\n00031| <div id=\"example-status\" class=\"example-status\" style=\"display: none;\"></div>\n00032| </div>\n00033| \n00034| <div class=\"container\">\n00035| <div class=\"rdf-input-header\">\n00036| <label for=\"rdf-input\">RDF данные или URI:</label>\n00037| <button class=\"show-in-window-btn\" onclick=\"showRdfInSeparateWindow()\">Показать в окне</button>\n00038| <button class=\"clear-btn\" onclick=\"clearRdfInput()\">Очистить</button>\n00039| <button class=\"save-as-btn\" onclick=\"saveAsFile()\">Сохранить как</button>\n00040| <button class=\"load-btn\" onclick=\"document.getElementById('file-input').click()\">Загрузить</button>\n00041| <button class=\"test-btn\" onclick=\"testRdfValidation()\">Тест</button>\n00042| <button class=\"virtual-rdf-btn\" onclick=\"showVirtualRDFData()\">virtualRDFdata</button>\n00043| <input type=\"file\" id=\"file-input\" style=\"display: none;\" accept=\".ttl,.rdf,.nt,.nq,.trig\" onchange=\"loadFile(event)\">\n00044| </div>\n00045| <textarea id=\"rdf-input\" placeholder=\"Введите RDF данные в формате TriG...\"></textarea>\n00046| \n00047| <div class=\"format-selectors\">\n00048| <div class=\"form-group\">\n00049| <label for=\"input-format\">Входной формат:</label>\n00050| <select id=\"input-format\">\n00051| <option value=\"trig\" selected>TriG</option>\n00052| </select>\n00053| </div>\n00054| \n00055| <div class=\"form-group\">\n00056| <label for=\"output-format\">Выходной формат:</label>\n00057| <select id=\"output-format\">\n00058| <option value=\"svg\" selected>SVG</option>\n00059| <option value=\"png\">PNG</option>\n00060| </select>\n00061| </div>\n00062| \n00063| <div class=\"form-group\">\n00064| <label for=\"layout-engine\">Движок компоновки:</label>\n00065| <select id=\"layout-engine\">\n00066| <option value=\"dot\" selected>dot (иерархическая)</option>\n00067| <option value=\"neato\">neato (spring model)</option>\n00068| <option value=\"fdp\">fdp (force-directed)</option>\n00069| <option value=\"circo\">circo (круговая)</option>\n00070| <option value=\"twopi\">twopi (радиальная)</option>\n00071| </select>\n00072| </div>\n00073| \n00074| <div class=\"form-group\">\n00075| <label for=\"visualization-mode\">Режим визуализации:</label>\n00076| <select id=\"visualization-mode\" onchange=\"updateModeDescription()\">\n00077| <option value=\"vad-trig\" selected>Режим VAD TriG</option>\n00078| </select>\n00079| <div class=\"mode-description\" id=\"mode-description\">\n00080| VAD диаграмма с поддержкой иерархии TriG графов\n00081| </div>\n00082| </div>\n00083| \n00084| <div class=\"form-group\">\n00085| <label for=\"sparql-mode\">Режим SPARQL:</label>\n00086| <select id=\"sparql-mode\" onchange=\"toggleSparqlPanel()\">\n00087| <option value=\"smart-design\" selected>SPARQL Smart Design</option>\n00088| </select>\n00089| <div class=\"mode-description\" id=\"sparql-mode-description\">\n00090| Smart Design для создания SPARQL запросов\n00091| </div>\n00092| </div>\n00093| \n00094| <div class=\"form-group\" id=\"max-label-length-group\">\n00095| <label for=\"max-label-length\">Макс. длина имени:</label>\n00096| <input type=\"number\" id=\"max-label-length\" value=\"25\" min=\"5\" max=\"200\" style=\"width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;\">\n00097| <div class=\"mode-description\" id=\"max-label-length-description\">\n00098| Перенос по словам при превышении\n00099| </div>\n00100| </div>\n00101| \n00102| <div class=\"form-group\" id=\"max-vad-row-length-group\" style=\"display: none;\">\n00103| <label for=\"max-vad-row-length\">Макс. длина VAD:</label>\n00104| <input type=\"number\" id=\"max-vad-row-length\" value=\"8\" min=\"2\" max=\"20\" style=\"width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;\">\n00105| <div class=\"mode-description\" id=\"max-vad-row-length-description\">\n00106| Количество VAD-элементов в строке до переноса\n00107| </div>\n00108| </div>\n00109| </div>\n00110| \n00111| <button id=\"visualize-btn\" onclick=\"visualize()\">Показать</button>\n00112| </div>\n00113| \n00114| <!-- Smart Design Container - отображается только при выборе \"SPARQL Smart Design\" в режиме VAD TriG -->\n00115| <div class=\"smart-design-container\" id=\"smart-design-container\">\n00116| <!-- Панель Smart Design -->\n00117| <div class=\"smart-design-panel\">\n00118| <h3>Smart Design</h3>\n00119| <!-- Верхний ряд: TriG -->\n00120| <div class=\"smart-design-trig-row\">\n00121| <div class=\"smart-design-field smart-design-field-trig\">\n00122| <label for=\"smart-design-trig\">TriG:</label>\n00123| <select id=\"smart-design-trig\">\n00124| <option value=\"\">-- Выберите TriG --</option>\n00125| </select>\n00126| </div>\n00127| </div>\n00128| <!-- Нижний ряд: Subject Type, Subject, Predicate, Object -->\n00129| <div class=\"smart-design-fields\">\n00130| <div class=\"smart-design-field\">\n00131| <label for=\"smart-design-subject-type\">Subject Type:</label>\n00132| <div class=\"smart-design-field-wrapper\">\n00133| <select id=\"smart-design-subject-type\">\n00134| <option value=\"\">-- Выберите тип Subject --</option>\n00135| </select>\n00136| <button class=\"copy-to-clipboard-btn\" onclick=\"copyFieldValue('smart-design-subject-type')\" title=\"Копировать значение\">📋</button>\n00137| </div>\n00138| </div>\n00139| <div class=\"smart-design-field\">\n00140| <label for=\"smart-design-subject\">Subject:</label>\n00141| <div class=\"smart-design-field-wrapper\">\n00142| <select id=\"smart-design-subject\">\n00143| <option value=\"\">-- Выберите Subject --</option>\n00144| </select>\n00145| <button class=\"copy-to-clipboard-btn\" onclick=\"copyFieldValue('smart-design-subject')\" title=\"Копировать значение\">📋</button>\n00146| </div>\n00147| </div>\n00148| <div class=\"smart-design-field\">\n00149| <label for=\"smart-design-predicate\">Predicate:</label>\n00150| <div class=\"smart-design-field-wrapper\">\n00151| <select id=\"smart-design-predicate\">\n00152| <option value=\"\">-- Выберите Predicate --</option>\n00153| </select>\n00154| <button class=\"copy-to-clipboard-btn\" onclick=\"copyFieldValue('smart-design-predicate')\" title=\"Копировать значение\">📋</button>\n00155| </div>\n00156| </div>\n00157| <div class=\"smart-design-field\">\n00158| <label for=\"smart-design-object\">Object:</label>\n00159| <div class=\"smart-design-field-wrapper\">\n00160| <select id=\"smart-design-object\">\n00161| <option value=\"\">-- Выберите Object --</option>\n00162| </select>\n00163| <button class=\"copy-to-clipboard-btn\" onclick=\"copyFieldValue('smart-design-object')\" title=\"Копировать значение\">📋</button>\n00164| </div>\n00165| </div>\n00166| </div>\n00167| <div class=\"smart-design-buttons\">\n00168| <button class=\"new-trig-btn\" onclick=\"openNewTrigModal()\">New TriG (VADProcessDia)</button>\n00169| <button class=\"del-trig-btn\" onclick=\"openDelTrigModal()\">Del TriG</button>\n00170| <button onclick=\"smartDesignCreate()\">Создать SPARQL</button>\n00171| <button onclick=\"smartDesignCreateWithPrefix()\" style=\"background-color: #1565C0;\">Создать SPARQL (prefix)</button>\n00172| <button class=\"delete-btn\" onclick=\"smartDesignDelete()\">Удалить триплет</button>\n00173| <button class=\"clear-btn\" onclick=\"smartDesignClear()\">Очистить</button>\n00174| </div>\n00175| <div id=\"smart-design-message\" class=\"smart-design-message\" style=\"display: none;\"></div>\n00176| </div>\n00177| <!-- Панель Result in SPARQL -->\n00178| <div class=\"result-sparql-panel\">\n00179| <h3>Result in SPARQL</h3>\n00180| <textarea class=\"result-sparql-textarea\" id=\"result-sparql-query\" placeholder=\"Здесь будет сгенерированный SPARQL запрос...\"></textarea>\n00181| <div class=\"smart-design-buttons\" style=\"margin-top: 10px;\">\n00182| <button class=\"apply-btn\" onclick=\"smartDesignApply()\">Применить как Simple Triple</button>\n00183| <button class=\"apply-btn\" onclick=\"smartDesignApplyShorthand()\" style=\"background-color: #8E24AA;\">Применить как Shorthand Triple</button>\n00184| </div>\n00185| <div id=\"result-sparql-message\" class=\"smart-design-message\" style=\"display: none;\"></div>\n00186| </div>\n00187| </div>\n00188| \n00189| <!-- Модальное окно для создания нового TriG -->\n00190| <div id=\"new-trig-modal\" class=\"new-trig-modal\">\n00191| <div class=\"new-trig-modal-content\">\n00192| <div class=\"new-trig-modal-header\">\n00193| <h3>Создание нового TriG контейнера (VADProcessDia)</h3>\n00194| <button class=\"new-trig-modal-close\" onclick=\"closeNewTrigModal()\">&times;</button>\n00195| </div>\n00196| <div class=\"new-trig-modal-body\">\n00197| <div class=\"new-trig-field\">\n00198| <label for=\"new-trig-process-concept\">Концепт процесса:</label>\n00199| <select id=\"new-trig-process-concept\" onchange=\"updateNewTrigFields()\">\n00200| <option value=\"\">-- Выберите концепт процесса --</option>\n00201| </select>\n00202| <small class=\"field-hint\">ID и label TriG формируются автоматически на основе выбранного концепта</small>\n00203| </div>\n00204| <div class=\"new-trig-field\">\n00205| <label for=\"new-trig-id\">ID нового TriG (автоматически):</label>\n00206| <input type=\"text\" id=\"new-trig-id\" placeholder=\"t_{processId}\" readonly style=\"background-color: #f5f5f5;\">\n00207| </div>\n00208| <div class=\"new-trig-field\">\n00209| <label for=\"new-trig-label\">rdfs:label (автоматически):</label>\n00210| <input type=\"text\" id=\"new-trig-label\" placeholder=\"Схема процесса {processLabel}\" readonly style=\"background-color: #f5f5f5;\">\n00211| </div>\n00212| <div class=\"new-trig-field\">\n00213| <label for=\"new-trig-parent\">vad:hasParentTrig (родитель):</label>\n00214| <select id=\"new-trig-parent\">\n00215| <option value=\"\">-- Выберите родителя --</option>\n00216| </select>\n00217| </div>\n00218| </div>\n00219| <div class=\"new-trig-modal-buttons\">\n00220| <button class=\"new-trig-cancel-btn\" onclick=\"closeNewTrigModal()\">Отмена</button>\n00221| <button class=\"new-trig-create-btn\" onclick=\"createNewTrig()\">Создать запрос New TriG</button>\n00222| </div>\n00223| </div>\n00224| </div>\n00225| \n00226| <!-- Модальное окно для удаления TriG -->\n00227| <div id=\"del-trig-modal\" class=\"new-trig-modal\">\n00228| <div class=\"new-trig-modal-content\">\n00229| <div class=\"new-trig-modal-header\" style=\"background-color: #f44336;\">\n00230| <h3>Удаление TriG контейнера</h3>\n00231| <button class=\"new-trig-modal-close\" onclick=\"closeDelTrigModal()\">&times;</button>\n00232| </div>\n00233| <div class=\"new-trig-modal-body\">\n00234| <div class=\"new-trig-field\">\n00235| <label for=\"del-trig-select\">Выберите TriG для удаления:</label>\n00236| <select id=\"del-trig-select\" onchange=\"updateDelTrigInfo()\">\n00237| <option value=\"\">-- Выберите TriG --</option>\n00238| </select>\n00239| </div>\n00240| <div class=\"new-trig-field\">\n00241| <label>Информация о TriG:</label>\n00242| <div id=\"del-trig-info\" style=\"padding: 10px; background-color: #f5f5f5; border-radius: 4px; font-size: 13px; min-height: 60px;\">\n00243| Выберите TriG для просмотра информации\n00244| </div>\n00245| </div>\n00246| <div id=\"del-trig-warning\" style=\"display: none; padding: 10px; background-color: #ffebee; border: 1px solid #f44336; border-radius: 4px; margin-top: 10px; color: #c62828;\">\n00247| </div>\n00248| </div>\n00249| <div class=\"new-trig-modal-buttons\">\n00250| <button class=\"new-trig-cancel-btn\" onclick=\"closeDelTrigModal()\">Отмена</button>\n00251| <button class=\"new-trig-create-btn\" id=\"del-trig-confirm-btn\" onclick=\"deleteTriG()\" style=\"background-color: #f44336;\" disabled>Создать запрос Del TriG</button>\n00252| </div>\n00253| </div>\n00254| </div>\n00255| \n00256| <div class=\"result-container\" id=\"result-container\">\n00257| <h2>Результат:</h2>\n00258| \n00259| <!-- VAD TriG режим: контейнер с диаграммой слева и панелями справа -->\n00260| <div class=\"vad-trig-container\" id=\"vad-trig-container\" style=\"display: none;\">\n00261| <!-- Обертка для диаграммы -->\n00262| <div class=\"vad-trig-diagram-wrapper\">\n00263| <div class=\"diagram-panel\">\n00264| <div class=\"diagram-panel-header\">Диаграмма</div>\n00265| <div class=\"diagram-panel-content\">\n00266| <div class=\"zoom-container vad-trig-zoom-container\" id=\"vad-trig-zoom-container\">\n00267| <div class=\"zoom-content\" id=\"vad-trig-zoom-content\">\n00268| <div id=\"vad-trig-output\"></div>\n00269| </div>\n00270| </div>\n00271| </div>\n00272| <div class=\"zoom-controls vad-trig-zoom-controls\" id=\"vad-trig-zoom-controls\" style=\"display: none;\">\n00273| <button onclick=\"zoomOut()\">-</button>\n00274| <span id=\"vad-trig-zoom-level\">100%</span>\n00275| <button onclick=\"zoomIn()\">+</button>\n00276| <button onclick=\"zoomReset()\">Сброс</button>\n00277| <button onclick=\"zoomFit()\">Вписать</button>\n00278| </div>\n00279| </div>\n00280| </div>\n00281| \n00282| <!-- Панели слева -->\n00283| <div class=\"vad-trig-left-panels\">\n00284| <!-- Панель дерева TriG -->\n00285| <div class=\"trig-tree-panel\">\n00286| <div class=\"trig-tree-header\">Дерево TriG</div>\n00287| <div class=\"trig-tree-content\" id=\"trig-tree-content\">\n00288| <div class=\"trig-properties-empty\">Выберите данные TriG</div>\n00289| </div>\n00290| </div>\n00291| \n00292| <!-- Панель свойств объекта -->\n00293| <div class=\"trig-properties-panel\">\n00294| <div class=\"trig-properties-header\">Окно свойств объекта</div>\n00295| <div class=\"trig-properties-content\" id=\"trig-properties-content\">\n00296| <div class=\"trig-properties-empty\">Выберите TriG в дереве</div>\n00297| </div>\n00298| </div>\n00299| </div>\n00300| </div>\n00301| \n00302| <div class=\"zoom-controls\" id=\"zoom-controls\" style=\"display: none;\">\n00303| <button onclick=\"zoomOut()\">-</button>\n00304| <span id=\"zoom-level\">100%</span>\n00305| <button onclick=\"zoomIn()\">+</button>\n00306| <button onclick=\"zoomReset()\">Сброс</button>\n00307| <button onclick=\"zoomFit()\">Вписать</button>\n00308| </div>\n00309| <div class=\"zoom-container\" id=\"zoom-container\">\n00310| <div class=\"zoom-content\" id=\"zoom-content\">\n00311| <div id=\"output\"></div>\n00312| </div>\n00313| </div>\n00314| <div class=\"export-buttons\" id=\"export-buttons\" style=\"display: none;\">\n00315| <button onclick=\"downloadSVG()\">Скачать SVG</button>\n00316| <button onclick=\"downloadPNG()\">Скачать PNG</button>\n00317| <button onclick=\"openInNewWindowGitHub()\">Показать в окне github</button>\n00318| <button onclick=\"openInNewWindowLdfFi()\">Показать в окне ldf.fi</button>\n00319| <button onclick=\"openInNewWindowGraphvizOnline()\">Показать в окне GraphvizOnline</button>\n00320| </div>\n00321| <div class=\"legend-panel\" id=\"legend-panel\" style=\"display: none;\">\n00322| <h3>Легенда стилей:</h3>\n00323| <div class=\"legend-content\" id=\"legend-content\"></div>\n00324| </div>\n00325| <div class=\"prefixes-panel\" id=\"prefixes-panel\" style=\"display: none;\">\n00326| <h3>Prefixes:</h3>\n00327| <div class=\"prefixes-content\" id=\"prefixes-content\"></div>\n00328| </div>\n00329| <div class=\"sparql-panel\" id=\"sparql-panel\">\n00330| <h3>SPARQL запрос:</h3>\n00331| <textarea class=\"sparql-query-textarea\" id=\"sparql-query\" placeholder=\"Введите SPARQL запрос...\">SELECT ?s ?p ?o\n00332| WHERE {\n00333| ?s ?p ?o .\n00334| }</textarea>\n00335| <div class=\"sparql-buttons\">\n00336| <button onclick=\"executeSparqlQuery()\">Выполнить запрос</button>\n00337| <button onclick=\"resetSparqlQuery()\">Сбросить</button>\n00338| </div>\n00339| <div class=\"sparql-results\" id=\"sparql-results\" style=\"display: none;\">\n00340| <h4>Результаты запроса:</h4>\n00341| <div id=\"sparql-results-content\"></div>\n00342| </div>\n00343| </div>\n00344| </div>\n00345| \n00346| <!-- Подключение библиотеки N3.js для парсинга RDF -->\n00347| <script src=\"https://unpkg.com/[email protected]/browser/n3.min.js\"></script>\n00348| \n00349| <!-- Подключение библиотеки Viz.js для рендеринга графов -->\n00350| <script src=\"https://unpkg.com/@viz-js/[email protected]/lib/viz-standalone.js\"></script>\n00351| \n00352| <!-- Подключение библиотеки Comunica для SPARQL запросов -->\n00353| <script src=\"https://rdf.js.org/comunica-browser/versions/v4/engines/query-sparql-rdfjs/comunica-browser.js\"></script>\n00354| \n00355| <!-- Подключение SPARQL запросов для Smart Design (справочники) -->\n00356| <script src=\"sparql-queries-choice.js\"></script>\n00357| \n00358| <!-- Подключение SPARQL запросов для логики кода -->\n00359| <script src=\"sparql-queries-code.js\"></script>\n00360| \n00361| <!-- Подключение правил валидации VAD схемы -->\n00362| <script src=\"vad-validation-rules.js\"></script>\n00363| \n00364| <script>\n00365| /**\n00366| * RDF Grapher ver4p - Основной JavaScript модуль\n00367| */\n00368| \n00369| // ============================================================================\n00370| // РЕЖИМ ВИЗУАЛИЗАЦИИ\n00371| // ============================================================================\n00372| \n00373| const Mode = 'notation';\n00374| \n00375| // ============================================================================\n00376| // EMBEDDED EXAMPLE DATA\n00377| // ============================================================================\n00378| \n00379| const EXAMPLE_DATA = {\n00380| // TriG VADv4 пример\n00381| 'trig-vad-v4': `# Пример TriG VADv4 (Value Added Chain Diagram с иерархией TriG)\n00382| # Демонстрация иерархии TriG графов через hasParentTrig\n00383| # Исполнители определены в vad:rtree (Дерево Исполнителей)\n00384| #\n00385| # vad:processSubtype вычисляется автоматически в virtualRDFdata:\n00386| # - vad:DetailedChild: процесс с vad:hasTrig, где дочерний TriG указывает на текущий\n00387| # - vad:notDetailedChild: процесс с vad:hasParentProcess, совпадающим с родителем текущей схемы\n00388| # - vad:NotDefinedType: процесс с vad:hasParentProcess = vad:NotDefined\n00389| #\n00390| # ВАЖНЫЕ ПРАВИЛА:\n00391| # 1. vad:hasTrig и vad:hasParentProcess - взаимоисключающие предикаты\n00392| # 2. vad:processSubtype вычисляется автоматически в virtualRDFdata\n00393| # 3. vad:isSubprocessTrig указывается первым для явной связи индивида с TriG\n00394| # 4. ID группы исполнителей формируется как: ExecutorGroup_ + ID процесса\n00395| \n00396| @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n00397| @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n00398| @prefix dcterms: <http://purl.org/dc/terms/> .\n00399| @prefix vad: <http://example.org/vad#> .\n00400| \n00401| # ============================================================================\n00402| # Дерево Процессов (ptree) - общий граф с метаданными всех процессов\n00403| # ============================================================================\n00404| \n00405| vad:ptree {\n00406| vad:ptree rdf:type vad:ProcessTree ;\n00407| rdfs:label \"Дерево Процессов (TriG)\" ;\n00408| vad:hasParentTrig vad:root .\n00409| \n00410| vad:p1 rdf:type vad:TypeProcess ;\n00411| rdfs:label \"p1 Процесс 1\" ;\n00412| dcterms:description \"p1 Процесс 1\" ;\n00413| vad:hasTrig vad:t_p1 .\n00414| \n00415| vad:p1.1 rdf:type vad:TypeProcess ;\n00416| rdfs:label \"p1.1 Процесс 1.1\" ;\n00417| dcterms:description \"Первый процесс в цепочке добавленной стоимости\" ;\n00418| vad:hasTrig vad:t_p1.1 .\n00419| \n00420| vad:p1.2 rdf:type vad:TypeProcess ;\n00421| rdfs:label \"Процесс 1.2\" ;\n00422| dcterms:description \"Второй процесс, выполняется после Процесса 1\" ;\n00423| vad:hasParentProcess vad:p1 .\n00424| \n00425| # p1.1.1\n00426| vad:p1.1.1 rdf:type vad:TypeProcess ;\n00427| rdfs:label \"p1.1.1 Процесс 1.1.1\" ;\n00428| dcterms:description \"Первый процесс в цепочке добавленной стоимости\" ;\n00429| vad:hasParentProcess vad:p1.1 .\n00430| \n00431| vad:p1.1.2 rdf:type vad:TypeProcess ;\n00432| rdfs:label \"p1.1.2 Процесс 1.1.2\" ;\n00433| dcterms:description \"Второй процесс в цепочке добавленной стоимости\" ;\n00434| vad:hasParentProcess vad:p2 .\n00435| \n00436| vad:p2 rdf:type vad:TypeProcess ;\n00437| rdfs:label \"p2 Процесс 2\" ;\n00438| dcterms:description \"p2 Процесс 2\" ;\n00439| vad:hasTrig vad:t_p2 .\n00440| \n00441| vad:p2.1 rdf:type vad:TypeProcess ;\n00442| rdfs:label \"p2.1 Процесс 2.1\" ;\n00443| dcterms:description \"Первый процесс в цепочке добавленной стоимости\" ;\n00444| vad:hasParentProcess vad:p2 .\n00445| \n00446| vad:p2.2 rdf:type vad:TypeProcess ;\n00447| rdfs:label \"p2.2 Процесс 2.2\" ;\n00448| dcterms:description \"Второй процесс\" ;\n00449| vad:hasParentProcess vad:NotDefined .\n00450| }\n00451| \n00452| # ============================================================================\n00453| # Дерево Исполнителей (rtree) - общий граф с метаданными всех исполнителей\n00454| # Содержит rdf:type, rdfs:label для всех vad:Executor\n00455| # ============================================================================\n00456| \n00457| vad:rtree {\n00458| vad:rtree rdf:type vad:ExecutorTree ;\n00459| rdfs:label \"Дерево Исполнителей (TriG)\" ;\n00460| vad:hasParentTrig vad:root .\n00461| \n00462| # Исполнители\n00463| vad:Executor1 rdf:type vad:TypeExecutor ;\n00464| rdfs:label \"Исполнитель 1\" .\n00465| \n00466| vad:Executor2 rdf:type vad:TypeExecutor ;\n00467| rdfs:label \"Исполнитель 2\" .\n00468| \n00469| vad:Executor3 rdf:type vad:TypeExecutor ;\n00470| rdfs:label \"Исполнитель 3\" .\n00471| \n00472| vad:Executor4 rdf:type vad:TypeExecutor ;\n00473| rdfs:label \"Исполнитель 4\" .\n00474| \n00475| vad:Executor5 rdf:type vad:TypeExecutor ;\n00476| rdfs:label \"Исполнитель 5\" .\n00477| \n00478| vad:Executor6 rdf:type vad:TypeExecutor ;\n00479| rdfs:label \"Исполнитель 6\" .\n00480| \n00481| vad:Executor7 rdf:type vad:TypeExecutor ;\n00482| rdfs:label \"Исполнитель 7\" .\n00483| \n00484| vad:Executor8 rdf:type vad:TypeExecutor ;\n00485| rdfs:label \"Исполнитель 8\" .\n00486| \n00487| # Исполнители\n00488| vad:Executor21 rdf:type vad:TypeExecutor ;\n00489| rdfs:label \"Исполнитель 21\" .\n00490| \n00491| vad:Executor22 rdf:type vad:TypeExecutor ;\n00492| rdfs:label \"Исполнитель 22\" .\n00493| \n00494| vad:Executor23 rdf:type vad:TypeExecutor ;\n00495| rdfs:label \"Исполнитель 23\" .\n00496| \n00497| vad:Executor24 rdf:type vad:TypeExecutor ;\n00498| rdfs:label \"Исполнитель 24\" .\n00499| \n00500| vad:Executor25 rdf:type vad:TypeExecutor ;\n00501| rdfs:label \"Исполнитель 25\" .\n00502| \n00503| vad:Executor26 rdf:type vad:TypeExecutor ;\n00504| rdfs:label \"Исполнитель 26\" .\n00505| \n00506| vad:Executor27 rdf:type vad:TypeExecutor ;\n00507| rdfs:label \"Исполнитель 27\" .\n00508| \n00509| vad:Executor28 rdf:type vad:TypeExecutor ;\n00510| rdfs:label \"Исполнитель 28\" .\n00511| }\n00512| \n00513| # ============================================================================\n00514| # Корневой TriG граф: t_p1 (hasParentTrig = root)\n00515| # ============================================================================\n00516| \n00517| vad:t_p1 {\n00518| vad:t_p1 rdf:type vad:VADProcessDia ;\n00519| rdfs:label \"Схема t_p1 процесса p1\" ;\n00520| vad:hasParentTrig vad:root ;\n00521| vad:definesProcess vad:p1 .\n00522| \n00523| # Процесс p1.1 - DetailedChild (имеет дочернюю схему vad:t_p1.1 с hasParentTrig на текущую)\n00524| # rdf:type находится в vad:ptree\n00525| # isSubprocessTrig указывается первым для явной связи с TriG\n00526| vad:p1.1 vad:isSubprocessTrig vad:t_p1 ;\n00527| vad:hasExecutor vad:ExecutorGroup_p1.1 ;\n00528| vad:hasNext vad:p1.2 .\n00529| \n00530| # Процесс p1.2 - notDetailedChild\n00531| vad:p1.2 vad:isSubprocessTrig vad:t_p1 ;\n00532| vad:hasExecutor vad:ExecutorGroup_p1.2 .\n00533| \n00534| # Группы исполнителей (ID формируется как ExecutorGroup_ + ID процесса)\n00535| vad:ExecutorGroup_p1.1 rdf:type vad:ExecutorGroup ;\n00536| rdfs:label \"Группа исполнителей процесса p1.1\" ;\n00537| vad:includes vad:Executor1 .\n00538| \n00539| vad:ExecutorGroup_p1.2 rdf:type vad:ExecutorGroup ;\n00540| rdfs:label \"Группа исполнителей процесса p1.2\" ;\n00541| vad:includes vad:Executor1, vad:Executor2 .\n00542| \n00543| # Примечание: Исполнители (vad:Executor1..8) определены в vad:rtree\n00544| }\n00545| \n00546| # ============================================================================\n00547| # Дочерний TriG граф: t_p1.1 (hasParentTrig = t_p1)\n00548| # Это детализация процесса p1.1 из родительского графа t_p1\n00549| # ============================================================================\n00550| \n00551| vad:t_p1.1 {\n00552| vad:t_p1.1 rdf:type vad:VADProcessDia ;\n00553| rdfs:label \"Схема t_p1.1 процесса p1.1\" ;\n00554| vad:hasParentTrig vad:t_p1 ;\n00555| vad:definesProcess vad:p1.1 .\n00556| \n00557| # Процесс p1.1.1 - notDetailedChild\n00558| # rdf:type находится в vad:ptree\n00559| # isSubprocessTrig указывается первым для явной связи с TriG\n00560| vad:p1.1.1 vad:isSubprocessTrig vad:t_p1.1 ;\n00561| vad:hasExecutor vad:ExecutorGroup_p1.1.1 ;\n00562| vad:hasNext vad:p1.1.2 .\n00563| \n00564| # Процесс p1.1.2 - notDetailedExternal (hasParentProcess = p2, не совпадает с p1.1)\n00565| vad:p1.1.2 vad:isSubprocessTrig vad:t_p1.1 ;\n00566| vad:hasExecutor vad:ExecutorGroup_p1.1.2 .\n00567| \n00568| # Группы исполнителей (ID формируется как ExecutorGroup_ + ID процесса)\n00569| vad:ExecutorGroup_p1.1.1 rdf:type vad:ExecutorGroup ;\n00570| rdfs:label \"Группа исполнителей процесса p1.1.1\" ;\n00571| vad:includes vad:Executor21 .\n00572| \n00573| vad:ExecutorGroup_p1.1.2 rdf:type vad:ExecutorGroup ;\n00574| rdfs:label \"Группа исполнителей процесса p1.1.2\" ;\n00575| vad:includes vad:Executor21, vad:Executor22 .\n00576| }\n00577| \n00578| # ============================================================================\n00579| # Корневой TriG граф: t_p2 (hasParentTrig = root)\n00580| # ============================================================================\n00581| \n00582| vad:t_p2 {\n00583| vad:t_p2 rdf:type vad:VADProcessDia ;\n00584| rdfs:label \"Схема t_p2 процесса p2\" ;\n00585| vad:hasParentTrig vad:root ;\n00586| vad:definesProcess vad:p2 .\n00587| \n00588| # Процесс p2.1 - notDetailedChild\n00589| # rdf:type находится в vad:ptree\n00590| # isSubprocessTrig указывается первым для явной связи с TriG\n00591| vad:p2.1 vad:isSubprocessTrig vad:t_p2 ;\n00592| vad:hasExecutor vad:ExecutorGroup_p2.1 ;\n00593| vad:hasNext vad:p2.2 .\n00594| \n00595| # Процесс p2.2 - NotDefinedType (hasParentProcess = NotDefined)\n00596| vad:p2.2 vad:isSubprocessTrig vad:t_p2 ;\n00597| vad:hasExecutor vad:ExecutorGroup_p2.2 .\n00598| \n00599| # Группы исполнителей (ID формируется как ExecutorGroup_ + ID процесса)\n00600| vad:ExecutorGroup_p2.1 rdf:type vad:ExecutorGroup ;\n00601| rdfs:label \"Группа исполнителей процесса p2.1\" ;\n00602| vad:includes vad:Executor1 .\n00603| \n00604| vad:ExecutorGroup_p2.2 rdf:type vad:ExecutorGroup ;\n00605| rdfs:label \"Группа исполнителей процесса p2.2\" ;\n00606| vad:includes vad:Executor1, vad:Executor2 .\n00607| }\\`\n00608| };\n00609| \n00610| // ============================================================================\n00611| // КОНФИГУРАЦИЯ ФИЛЬТРОВ\n00612| // ============================================================================\n00613| \n00614| const Filter = {\n00615| hiddenPredicates: [\n00616| 'rdf:type',\n00617| 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',\n00618| 'rdfs:subClassOf',\n00619| 'http://www.w3.org/2000/01/rdf-schema#subClassOf'\n00620| ]\n00621| };\n00622| \n00623| const FilterBase = {\n00624| hiddenPredicates: []\n00625| };\n00626| \n00627| const FilterAggregation = {\n00628| hiddenPredicates: [\n00629| 'rdf:type',\n00630| 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',\n00631| 'rdfs:subClassOf',\n00632| 'http://www.w3.org/2000/01/rdf-schema#subClassOf'\n00633| ]\n00634| };\n00635| \n00636| // Фильтры для режима VAD - скрываем hasParentTrig и rdf:type\n00637| const FilterVAD = {\n00638| hiddenPredicates: [\n00639| 'rdf:type',\n00640| 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',\n00641| 'vad:hasParentTrig',\n00642| 'http://example.org/vad#hasParentTrig'\n00643| ]\n00644| };\n00645| \n00646| function getFilterConfig(mode) {\n00647| if (mode === 'base') {\n00648| return FilterBase;\n00649| } else if (mode === 'aggregation') {\n00650| return FilterAggregation;\n00651| } else if (mode === 'vad' || mode === 'vad-trig') {\n00652| return FilterVAD;\n00653| }\n00654| return Filter;\n00655| }\n00656| \n00657| // ============================================================================\n00658| // КОНФИГУРАЦИЯ АГРЕГАЦИИ\n00659| // ============================================================================\n00660| \n00661| const MaxAggregationParams = 5;\n00662| const DEFAULT_MAX_LABEL_LENGTH = 25;\n00663| let currentMaxLabelLength = DEFAULT_MAX_LABEL_LENGTH;\n00664| \n00665| // Максимальное количество VAD элементов (CDS) в одной горизонтальной строке\n00666| const DEFAULT_MAX_VAD_ROW_LENGTH = 8;\n00667| let currentMaxVadRowLength = DEFAULT_MAX_VAD_ROW_LENGTH;\n00668| \n00669| // ============================================================================\n00670| // КОНФИГУРАЦИЯ VAD (Value Added Chain Diagram)\n00671| // ============================================================================\n00672| \n00673| /**\n00674| * VAD_ALLOWED_TYPES - Разрешенные типы объектов для режима VAD\n00675| */\n00676| const VAD_ALLOWED_TYPES = [\n00677| 'vad:TypeProcess',\n00678| 'http://example.org/vad#TypeProcess',\n00679| 'vad:ExecutorGroup',\n00680| 'http://example.org/vad#ExecutorGroup',\n00681| 'vad:TypeExecutor',\n00682| 'http://example.org/vad#TypeExecutor',\n00683| 'vad:VADProcessDia',\n00684| 'http://example.org/vad#VADProcessDia',\n00685| 'vad:ProcessTree',\n00686| 'http://example.org/vad#ProcessTree',\n00687| 'vad:ExecutorTree',\n00688| 'http://example.org/vad#ExecutorTree',\n00689| // Process subtypes - parent classes\n00690| 'vad:Detailed',\n00691| 'http://example.org/vad#Detailed',\n00692| 'vad:notDetailed',\n00693| 'http://example.org/vad#notDetailed',\n00694| // Detailed subtypes (process has vad:hasTrig)\n00695| 'vad:DetailedChild',\n00696| 'http://example.org/vad#DetailedChild',\n00697| 'vad:DetailedExternal',\n00698| 'http://example.org/vad#DetailedExternal',\n00699| // notDetailed subtypes (process has vad:hasParentProcess)\n00700| 'vad:notDetailedChild',\n00701| 'http://example.org/vad#notDetailedChild',\n00702| 'vad:notDetailedExternal',\n00703| 'http://example.org/vad#notDetailedExternal',\n00704| 'vad:NotDefinedType',\n00705| 'http://example.org/vad#NotDefinedType',\n00706| // Special values\n00707| 'vad:NotDefined',\n00708| 'http://example.org/vad#NotDefined'\n00709| ];\n00710| \n00711| /**\n00712| * VAD_ALLOWED_PREDICATES - Разрешенные предикаты для режима VAD\n00713| */\n00714| const VAD_ALLOWED_PREDICATES = [\n00715| 'rdf:type',\n00716| 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',\n00717| 'rdfs:label',\n00718| 'http://www.w3.org/2000/01/rdf-schema#label',\n00719| 'dcterms:description',\n00720| 'http://purl.org/dc/terms/description',\n00721| 'vad:hasNext',\n00722| 'http://example.org/vad#hasNext',\n00723| 'vad:hasExecutor',\n00724| 'http://example.org/vad#hasExecutor',\n00725| 'vad:hasParentTrig',\n00726| 'http://example.org/vad#hasParentTrig',\n00727| 'vad:includes',\n00728| 'http://example.org/vad#includes',\n00729| 'vad:processSubtype',\n00730| 'http://example.org/vad#processSubtype',\n00731| 'vad:hasTrig',\n00732| 'http://example.org/vad#hasTrig',\n00733| 'vad:definesProcess',\n00734| 'http://example.org/vad#definesProcess',\n00735| 'vad:isSubprocessTrig',\n00736| 'http://example.org/vad#isSubprocessTrig',\n00737| 'vad:hasParentProcess',\n00738| 'http://example.org/vad#hasParentProcess'\n00739| ];\n00740| \n00741| /**\n00742| * PTREE_PREDICATES - Предикаты, которые для сущностей типа vad:TypeProcess\n00743| * должны храниться в графе vad:ptree (Дерево Процессов)\n00744| * Остальные предикаты остаются в соответствующем TriG (схеме процесса)\n00745| */\n00746| const PTREE_PREDICATES = [\n00747| 'rdf:type',\n00748| 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',\n00749| 'rdfs:label',\n00750| 'http://www.w3.org/2000/01/rdf-schema#label',\n00751| 'dcterms:description',\n00752| 'http://purl.org/dc/terms/description',\n00753| 'vad:hasTrig',\n00754| 'http://example.org/vad#hasTrig',\n00755| 'vad:hasParentProcess',\n00756| 'http://example.org/vad#hasParentProcess'\n00757| ];\n00758| \n00759| /**\n00760| * RTREE_PREDICATES - Предикаты, которые для сущностей типа vad:TypeExecutor\n00761| * должны храниться в графе vad:rtree (Дерево Исполнителей)\n00762| * Остальные предикаты остаются в соответствующем TriG (схеме процесса)\n00763| */\n00764| const RTREE_PREDICATES = [\n00765| 'rdf:type',\n00766| 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',\n00767| 'rdfs:label',\n00768| 'http://www.w3.org/2000/01/rdf-schema#label'\n00769| ];\n00770| \n00771| /**\n00772| * TRIG_TYPES - Типы TriG графов в VAD онтологии\n00773| * Используется для валидации и определения типов графов\n00774| */\n00775| const TRIG_TYPES = {\n00776| PROCESS_TREE: ['vad:ProcessTree', 'http://example.org/vad#ProcessTree'],\n00777| EXECUTOR_TREE: ['vad:ExecutorTree', 'http://example.org/vad#ExecutorTree'],\n00778| VAD_PROCESS_DIA: ['vad:VADProcessDia', 'http://example.org/vad#VADProcessDia']\n00779| };\n00780| \n00781| /**\n00782| * PROCESS_SUBTYPES - Подтипы процессов в VAD онтологии\n00783| *\n00784| * Detailed (имеет vad:hasTrig):\n00785| * - DetailedChild: дочерняя схема указывает на текущую (hasParentTrig)\n00786| * - DetailedExternal: дочерняя схема НЕ указывает на текущую\n00787| *\n00788| * notDetailed (НЕ имеет vad:hasTrig):\n00789| * - notDetailedChild: hasParentProcess совпадает с текущим процессом\n00790| * - notDetailedExternal: hasParentProcess НЕ совпадает\n00791| * - NotDefinedType: hasParentProcess = NotDefined\n00792| *\n00793| * Basic и Detailed - устаревшие, для обратной совместимости\n00794| */\n00795| const PROCESS_SUBTYPES = {\n00796| // Устаревшие (для обратной совместимости)\n00797| BASIC: ['vad:Basic', 'http://example.org/vad#Basic'],\n00798| DETAILED: ['vad:Detailed', 'http://example.org/vad#Detailed'],\n00799| // Детализированные (имеют vad:hasTrig)\n00800| DETAILED_CHILD: ['vad:DetailedChild', 'http://example.org/vad#DetailedChild'],\n00801| DETAILED_EXTERNAL: ['vad:DetailedExternal', 'http://example.org/vad#DetailedExternal'],\n00802| // Недетализированные (НЕ имеют vad:hasTrig)\n00803| NOT_DETAILED: ['vad:notDetailed', 'http://example.org/vad#notDetailed'],\n00804| NOT_DETAILED_CHILD: ['vad:notDetailedChild', 'http://example.org/vad#notDetailedChild'],\n00805| NOT_DETAILED_EXTERNAL: ['vad:notDetailedExternal', 'http://example.org/vad#notDetailedExternal'],\n00806| NOT_DEFINED_TYPE: ['vad:NotDefinedType', 'http://example.org/vad#NotDefinedType']\n00807| };\n00808| \n00809| /**\n00810| * TYPE_PREDICATE_MAP - Сводная таблица «Объект-Предикат» (Приложение 1 к онтологии)\n00811| * Определяет допустимые предикаты для каждого типа объекта в зависимости от контекста TriG\n00812| *\n00813| * Связь с онтологией: Приложение 1 в vad-basic-ontology.ttl\n00814| */\n00815| const TYPE_PREDICATE_MAP = {\n00816| // Process в ptree (концепт) - общие свойства\n00817| 'vad:TypeProcess': {\n00818| ptree: [\n00819| 'rdf:type',\n00820| 'rdfs:label',\n00821| 'dcterms:description',\n00822| 'vad:hasTrig',\n00823| 'vad:hasParentProcess'\n00824| ],\n00825| // Process в VADProcessDia (индивид) - индивидуальные свойства\n00826| vadProcessDia: [\n00827| 'vad:isSubprocessTrig',\n00828| 'vad:hasExecutor',\n00829| 'vad:processSubtype',\n00830| 'vad:hasNext'\n00831| ]\n00832| },\n00833| // Executor в rtree - общие свойства\n00834| 'vad:TypeExecutor': {\n00835| rtree: [\n00836| 'rdf:type',\n00837| 'rdfs:label'\n00838| ]\n00839| },\n00840| // ExecutorGroup в VADProcessDia\n00841| 'vad:ExecutorGroup': {\n00842| vadProcessDia: [\n00843| 'rdf:type',\n00844| 'rdfs:label',\n00845| 'vad:includes'\n00846| ]\n00847| },\n00848| // VADProcessDia - свойства схемы процесса\n00849| 'vad:VADProcessDia': {\n00850| vadProcessDia: [\n00851| 'rdf:type',\n00852| 'rdfs:label',\n00853| 'vad:hasParentTrig',\n00854| 'vad:definesProcess'\n00855| ]\n00856| },\n00857| // ProcessTree (vad:ptree)\n00858| 'vad:ProcessTree': {\n00859| ptree: [\n00860| 'rdf:type',\n00861| 'rdfs:label',\n00862| 'vad:hasParentTrig'\n00863| ]\n00864| },\n00865| // ExecutorTree (vad:rtree)\n00866| 'vad:ExecutorTree': {\n00867| rtree: [\n00868| 'rdf:type',\n00869| 'rdfs:label',\n00870| 'vad:hasParentTrig'\n00871| ]\n00872| }\n00873| };\n00874| \n00875| /**\n00876| * Режим работы Smart Design:\n00877| * - 'filtered' - справочники фильтруются по связанным значениям (Subject Type -> Predicate)\n00878| * - 'full' - справочники отображают полный набор значений\n00879| */\n00880| let smartDesignMode = 'filtered';\n00881| \n00882| /**\n00883| * Проверяет, является ли предикат предикатом для ptree\n00884| * @param {string} predicateUri - URI или prefixed name предиката\n00885| * @returns {boolean}\n00886| */\n00887| function isPtreePredicate(predicateUri) {\n00888| return PTREE_PREDICATES.some(allowed =>\n00889| predicateUri === allowed || predicateUri.endsWith('#' + allowed.split(':')[1])\n00890| );\n00891| }\n00892| \n00893| /**\n00894| * Проверяет, является ли предикат предикатом для rtree\n00895| * @param {string} predicateUri - URI или prefixed name предиката\n00896| * @returns {boolean}\n00897| */\n00898| function isRtreePredicate(predicateUri) {\n00899| return RTREE_PREDICATES.some(allowed =>\n00900| predicateUri === allowed || predicateUri.endsWith('#' + allowed.split(':')[1])\n00901| );\n00902| }\n00903| \n00904| /**\n00905| * Проверяет, является ли субъект типом vad:TypeProcess\n00906| * @param {string} subjectUri - URI субъекта\n00907| * @returns {boolean}\n00908| */\n00909| function isSubjectVadProcess(subjectUri) {\n00910| const types = nodeTypesCache[subjectUri] || [];\n00911| return types.some(t =>\n00912| t === 'vad:TypeProcess' || t === 'http://example.org/vad#TypeProcess'\n00913| );\n00914| }\n00915| \n00916| /**\n00917| * Проверяет, является ли субъект типом vad:TypeExecutor\n00918| * @param {string} subjectUri - URI субъекта\n00919| * @returns {boolean}\n00920| */\n00921| function isSubjectVadExecutor(subjectUri) {\n00922| const types = nodeTypesCache[subjectUri] || [];\n00923| return types.some(t =>\n00924| t === 'vad:TypeExecutor' || t === 'http://example.org/vad#TypeExecutor'\n00925| );\n00926| }\n00927| \n00928| /**\n00929| * Определяет тип TriG графа\n00930| * @param {string} trigUri - URI TriG графа\n00931| * @param {Array} quads - Массив квадов\n00932| * @returns {string|null} - Тип графа или null\n00933| */\n00934| function getTrigType(trigUri, quads) {\n00935| const typeQuad = quads.find(quad =>\n00936| quad.subject.value === trigUri &&\n00937| (quad.predicate.value.endsWith('#type') ||\n00938| quad.predicate.value === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type')\n00939| );\n00940| \n00941| if (typeQuad) {\n00942| const typeValue = typeQuad.object.value;\n00943| if (TRIG_TYPES.PROCESS_TREE.some(t => typeValue === t || typeValue.endsWith('#ProcessTree'))) {\n00944| return 'vad:ProcessTree';\n00945| }\n00946| if (TRIG_TYPES.EXECUTOR_TREE.some(t => typeValue === t || typeValue.endsWith('#ExecutorTree'))) {\n00947| return 'vad:ExecutorTree';\n00948| }\n00949| if (TRIG_TYPES.VAD_PROCESS_DIA.some(t => typeValue === t || typeValue.endsWith('#VADProcessDia'))) {\n00950| return 'vad:VADProcessDia';\n00951| }\n00952| }\n00953| \n00954| // Определение типа по эвристике (для обратной совместимости)\n00955| if (trigUri.endsWith('#ptree') || trigUri.includes('ptree')) {\n00956| return 'vad:ProcessTree';\n00957| }\n00958| if (trigUri.endsWith('#rtree') || trigUri.includes('rtree')) {\n00959| return 'vad:ExecutorTree';\n00960| }\n00961| return 'vad:VADProcessDia';\n00962| }\n00963| \n00964| /**\n00965| * Проверяет, является ли тип типом ProcessTree\n00966| * @param {string} typeUri - URI типа\n00967| * @returns {boolean}\n00968| */\n00969| function isProcessTreeType(typeUri) {\n00970| return TRIG_TYPES.PROCESS_TREE.some(t =>\n00971| typeUri === t || typeUri.endsWith('#ProcessTree')\n00972| );\n00973| }\n00974| \n00975| /**\n00976| * Проверяет, является ли тип типом ExecutorTree\n00977| * @param {string} typeUri - URI типа\n00978| * @returns {boolean}\n00979| */\n00980| function isExecutorTreeType(typeUri) {\n00981| return TRIG_TYPES.EXECUTOR_TREE.some(t =>\n00982| typeUri === t || typeUri.endsWith('#ExecutorTree')\n00983| );\n00984| }\n00985| \n00986| /**\n00987| * Проверяет, является ли тип типом VADProcessDia\n00988| * @param {string} typeUri - URI типа\n00989| * @returns {boolean}\n00990| */\n00991| function isVADProcessDiaType(typeUri) {\n00992| return TRIG_TYPES.VAD_PROCESS_DIA.some(t =>\n00993| typeUri === t || typeUri.endsWith('#VADProcessDia')\n00994| );\n00995| }\n00996| \n00997| /**\n00998| * Проверяет, является ли подтип процесса детализированным (Detailed, DetailedChild, DetailedExternal)\n00999| * @param {string} subtypeUri - URI подтипа\n01000| * @returns {boolean}\n01001| */\n01002| function isDetailedSubtype(subtypeUri) {\n01003| return PROCESS_SUBTYPES.DETAILED.some(t => subtypeUri === t || subtypeUri.endsWith('#Detailed')) ||\n01004| PROCESS_SUBTYPES.DETAILED_CHILD.some(t => subtypeUri === t || subtypeUri.endsWith('#DetailedChild')) ||\n01005| PROCESS_SUBTYPES.DETAILED_EXTERNAL.some(t => subtypeUri === t || subtypeUri.endsWith('#DetailedExternal'));\n01006| }\n01007| \n01008| /**\n01009| * Получает стиль VADNodeStyle для процесса на основе его processSubtype\n01010| * Используется для визуализации процессов с разными цветами в зависимости от подтипа\n01011| * @param {Array<string>} nodeSubtypes - массив подтипов процесса\n01012| * @returns {string} - DOT-строка стиля для узла\n01013| */\n01014| function getProcessStyleForSubtype(nodeSubtypes) {\n01015| // Порядок приоритета проверки:\n01016| // 1. Конкретные подтипы (DetailedChild, DetailedExternal, notDetailedChild, notDetailedExternal, NotDefinedType)\n01017| // 2. Общие подтипы (Detailed, notDetailed)\n01018| // 3. Устаревшие подтипы (Basic)\n01019| // 4. По умолчанию - notDetailedChild (зеленый)\n01020| \n01021| for (const subtype of nodeSubtypes) {\n01022| // DetailedChild - голубой\n01023| if (PROCESS_SUBTYPES.DETAILED_CHILD.some(t => subtype === t || subtype.endsWith('#DetailedChild'))) {\n01024| return VADNodeStyles['ProcessStyleDetailedChild'].dot;\n01025| }\n01026| // DetailedExternal - оранжевый\n01027| if (PROCESS_SUBTYPES.DETAILED_EXTERNAL.some(t => subtype === t || subtype.endsWith('#DetailedExternal'))) {\n01028| return VADNodeStyles['ProcessStyleDetailedExternal'].dot;\n01029| }\n01030| // notDetailedChild - зеленый\n01031| if (PROCESS_SUBTYPES.NOT_DETAILED_CHILD.some(t => subtype === t || subtype.endsWith('#notDetailedChild'))) {\n01032| return VADNodeStyles['ProcessStyleNotDetailedChild'].dot;\n01033| }\n01034| // notDetailedExternal - серый\n01035| if (PROCESS_SUBTYPES.NOT_DETAILED_EXTERNAL.some(t => subtype === t || subtype.endsWith('#notDetailedExternal'))) {\n01036| return VADNodeStyles['ProcessStyleNotDetailedExternal'].dot;\n01037| }\n01038| // NotDefinedType - красноватый\n01039| if (PROCESS_SUBTYPES.NOT_DEFINED_TYPE.some(t => subtype === t || subtype.endsWith('#NotDefinedType'))) {\n01040| return VADNodeStyles['ProcessStyleNotDefinedType'].dot;\n01041| }\n01042| // Общий Detailed - голубой (для обратной совместимости)\n01043| if (PROCESS_SUBTYPES.DETAILED.some(t => subtype === t || subtype.endsWith('#Detailed'))) {\n01044| return VADNodeStyles['ProcessStyleDetailed'].dot;\n01045| }\n01046| // notDetailed общий - зеленый\n01047| if (PROCESS_SUBTYPES.NOT_DETAILED.some(t => subtype === t || subtype.endsWith('#notDetailed'))) {\n01048| return VADNodeStyles['ProcessStyleNotDetailedChild'].dot;\n01049| }\n01050| // Basic (устаревший) - зеленый\n01051| if (PROCESS_SUBTYPES.BASIC.some(t => subtype === t || subtype.endsWith('#Basic'))) {\n01052| return VADNodeStyles['ProcessStyleBasic'].dot;\n01053| }\n01054| }\n01055| \n01056| // По умолчанию - notDetailedChild (зеленый)\n01057| return VADNodeStyles['ProcessStyleNotDetailedChild'].dot;\n01058| }\n01059| \n01060| /**\n01061| * Проверяет существование триплета во всех графах (для проверки дубликатов)\n01062| * Поддерживает проверку как полных URI, так и prefixed names\n01063| * @param {string} subjectValue - URI или prefixed name субъекта\n01064| * @param {string} predicateValue - URI или prefixed name предиката\n01065| * @param {string} objectValue - URI, prefixed name или литерал объекта\n01066| * @returns {Object|null} - {graphUri, graphLabel} если найден дубликат, иначе null\n01067| */\n01068| function findDuplicateTriple(subjectValue, predicateValue, objectValue) {\n01069| // Преобразуем prefixed names в полные URI для сравнения\n01070| let subjectUri = subjectValue;\n01071| let predicateUri = predicateValue;\n01072| let objectUri = objectValue;\n01073| \n01074| for (const [prefix, namespace] of Object.entries(currentPrefixes)) {\n01075| if (subjectValue.startsWith(prefix + ':')) {\n01076| subjectUri = namespace + subjectValue.substring(prefix.length + 1);\n01077| }\n01078| if (predicateValue.startsWith(prefix + ':')) {\n01079| predicateUri = namespace + predicateValue.substring(prefix.length + 1);\n01080| }\n01081| if (objectValue.startsWith(prefix + ':')) {\n01082| objectUri = namespace + objectValue.substring(prefix.length + 1);\n01083| }\n01084| }\n01085| \n01086| // Также преобразуем полные URI в prefixed names для альтернативной проверки\n01087| const subjectPrefixed = getPrefixedName(subjectUri, currentPrefixes);\n01088| const predicatePrefixed = getPrefixedName(predicateUri, currentPrefixes);\n01089| const objectPrefixed = getPrefixedName(objectUri, currentPrefixes);\n01090| \n01091| for (const quad of currentQuads) {\n01092| const qSubjectUri = quad.subject.value;\n01093| const qPredicateUri = quad.predicate.value;\n01094| const qObjectValue = quad.object.value;\n01095| const qSubjectPrefixed = getPrefixedName(qSubjectUri, currentPrefixes);\n01096| const qPredicatePrefixed = getPrefixedName(qPredicateUri, currentPrefixes);\n01097| const qObjectPrefixed = quad.object.termType === 'Literal'\n01098| ? quad.object.value\n01099| : getPrefixedName(qObjectValue, currentPrefixes);\n01100| \n01101| // Сравниваем как полные URI, так и prefixed names\n01102| const subjectMatch = (subjectUri === qSubjectUri) || (subjectPrefixed === qSubjectPrefixed);\n01103| const predicateMatch = (predicateUri === qPredicateUri) || (predicatePrefixed === qPredicatePrefixed);\n01104| const objectMatch = (objectUri === qObjectValue) || (objectPrefixed === qObjectPrefixed) ||\n01105| (objectValue === qObjectValue) || (objectValue === qObjectPrefixed);\n01106| \n01107| if (subjectMatch && predica
[2026-01-26T19:13:21.538Z] [INFO] teMatch && objectMatch) {\n01108| const graphUri = quad.graph ? quad.graph.value : null;\n01109| const graphLabel = graphUri ? getPrefixedName(graphUri, currentPrefixes) : 'default graph';\n01110| return { graphUri, graphLabel };\n01111| }\n01112| }\n01113| \n01114| return null;\n01115| }\n01116| \n01117| /**\n01118| * Определяет целевой граф для триплета на основе правил ptree\n01119| * Если субъект является vad:TypeProcess и предикат в PTREE_PREDICATES,\n01120| * триплет должен быть добавлен в vad:ptree\n01121| * @param {string} subjectValue - URI или prefixed name субъекта\n01122| * @param {string} predicateValue - URI или prefixed name предиката\n01123| * @param {string} originalTrigValue - Исходный выбранный граф\n01124| * @param {string} objectValue - URI или prefixed name объекта (опционально, для проверки rdf:type)\n01125| * @returns {string} - URI графа для добавления триплета\n01126| */\n01127| function determineTargetGraph(subjectValue, predicateValue, originalTrigValue, objectValue = null) {\n01128| // Преобразуем prefixed names в полные URI\n01129| let subjectUri = subjectValue;\n01130| let predicateUri = predicateValue;\n01131| let objectUri = objectValue;\n01132| \n01133| for (const [prefix, namespace] of Object.entries(currentPrefixes)) {\n01134| if (subjectValue.startsWith(prefix + ':')) {\n01135| subjectUri = namespace + subjectValue.substring(prefix.length + 1);\n01136| }\n01137| if (predicateValue.startsWith(prefix + ':')) {\n01138| predicateUri = namespace + predicateValue.substring(prefix.length + 1);\n01139| }\n01140| if (objectValue && objectValue.startsWith(prefix + ':')) {\n01141| objectUri = namespace + objectValue.substring(prefix.length + 1);\n01142| }\n01143| }\n01144| \n01145| // Специальный случай: добавление rdf:type vad:TypeProcess - всегда в ptree\n01146| const isRdfType = (predicateUri === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' ||\n01147| predicateValue === 'rdf:type');\n01148| const isProcessType = (objectUri === 'http://example.org/vad#TypeProcess' ||\n01149| objectValue === 'vad:TypeProcess');\n01150| if (isRdfType && isProcessType) {\n01151| return 'vad:ptree';\n01152| }\n01153| \n01154| // Проверяем, является ли субъект типом vad:TypeProcess\n01155| if (isSubjectVadProcess(subjectUri)) {\n01156| // Проверяем, является ли предикат предикатом для ptree\n01157| if (isPtreePredicate(predicateUri) || isPtreePredicate(predicateValue)) {\n01158| // Возвращаем vad:ptree\n01159| return 'vad:ptree';\n01160| }\n01161| }\n01162| \n01163| return originalTrigValue;\n01164| }\n01165| \n01166| /**\n01167| * Валидирует RDF триплеты на соответствие схеме VAD онтологии\n01168| *\n01169| * Проверяет, что все предикаты и типы объектов в RDF данных соответствуют\n01170| * разрешенным значениям, определенным в VAD онтологии (VAD_ALLOWED_PREDICATES,\n01171| * VAD_ALLOWED_TYPES). Используется для проверки корректности данных перед\n01172| * визуализацией и при ручном тестировании через кнопку \"Тест\".\n01173| *\n01174| * @param {Array} quads - Массив RDF квадов (N3.js quad objects)\n01175| * @param {Object} prefixes - Объект с префиксами {prefix: namespace}\n01176| * @returns {Object} Результат валидации с полями:\n01177| * - valid {boolean} - true если данные валидны, false если есть ошибки\n01178| * - errors {Array<Object>} - Массив ошибок, каждая с полями:\n01179| * - triple {string} - Триплет с ошибкой\n01180| * - position {string} - Позиция ошибки (predicate, object, etc.)\n01181| * - value {string} - Недопустимое значение\n01182| * - message {string} - Описание ошибки\n01183| *\n01184| * @example\n01185| * const quads = [...]; // Parsed RDF quads\n01186| * const prefixes = { vad: 'http://example.org/vad#', rdf: '...' };\n01187| * const result = validateVAD(quads, prefixes);\n01188| * if (!result.valid) {\n01189| * console.error('Validation errors:', result.errors);\n01190| * }\n01191| *\n01192| * @see VAD_ALLOWED_PREDICATES - Список разрешенных предикатов\n01193| * @see VAD_ALLOWED_TYPES - Список разрешенных типов объектов\n01194| * @see formatVADErrors - Форматирование ошибок для отображения\n01195| */\n01196| function validateVAD(quads, prefixes) {\n01197| const errors = [];\n01198| \n01199| quads.forEach((quad, index) => {\n01200| const predicateUri = quad.predicate.value;\n01201| const predicateLabel = getPrefixedName(predicateUri, prefixes);\n01202| \n01203| // Проверяем, что предикат разрешен\n01204| const predicateAllowed = VAD_ALLOWED_PREDICATES.some(allowed =>\n01205| predicateUri === allowed || predicateLabel === allowed\n01206| );\n01207| \n01208| if (!predicateAllowed) {\n01209| const subjectLabel = getPrefixedName(quad.subject.value, prefixes);\n01210| const objectLabel = quad.object.termType === 'Literal'\n01211| ? `\"${quad.object.value}\"`\n01212| : getPrefixedName(quad.object.value, prefixes);\n01213| \n01214| errors.push({\n01215| triple: `${subjectLabel} ${predicateLabel} ${objectLabel}`,\n01216| position: 'predicate',\n01217| value: predicateLabel,\n01218| message: `Недопустимый предикат: ${predicateLabel}`\n01219| });\n01220| }\n01221| \n01222| // Если предикат - rdf:type, проверяем, что тип разрешен\n01223| const typePredicates = [\n01224| 'rdf:type',\n01225| 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'\n01226| ];\n01227| \n01228| if (typePredicates.includes(predicateUri) || typePredicates.includes(predicateLabel)) {\n01229| const typeUri = quad.object.value;\n01230| const typeLabel = getPrefixedName(typeUri, prefixes);\n01231| \n01232| const typeAllowed = VAD_ALLOWED_TYPES.some(allowed =>\n01233| typeUri === allowed || typeLabel === allowed\n01234| );\n01235| \n01236| if (!typeAllowed) {\n01237| const subjectLabel = getPrefixedName(quad.subject.value, prefixes);\n01238| \n01239| errors.push({\n01240| triple: `${subjectLabel} ${predicateLabel} ${typeLabel}`,\n01241| position: 'object (type)',\n01242| value: typeLabel,\n01243| message: `Недопустимый тип объекта: ${typeLabel}`\n01244| });\n01245| }\n01246| }\n01247| });\n01248| \n01249| return {\n01250| valid: errors.length === 0,\n01251| errors: errors\n01252| };\n01253| }\n01254| \n01255| /**\n01256| * Форматирует ошибки валидации VAD для отображения\n01257| * @param {Array} errors - Массив ошибок\n01258| * @returns {string} - Отформатированное сообщение\n01259| */\n01260| function formatVADErrors(errors) {\n01261| let message = 'ОШИБКА ВАЛИДАЦИИ VAD\\n';\n01262| message += '═══════════════════════════════════════\\n\\n';\n01263| \n01264| errors.forEach((error, index) => {\n01265| message += `Ошибка ${index + 1}:\\n`;\n01266| message += ` Триплет: ${error.triple}\\n`;\n01267| message += ` Позиция: ${error.position}\\n`;\n01268| message += ` Значение: ${error.value}\\n`;\n01269| message += ` ${error.message}\\n\\n`;\n01270| });\n01271| \n01272| message += '═══════════════════════════════════════\\n';\n01273| message += `Всего ошибок: ${errors.length}\\n`;\n01274| message += '\\nРазрешенные типы: vad:TypeProcess, vad:ExecutorGroup, vad:TypeExecutor\\n';\n01275| message += 'Разрешенные предикаты: rdf:type, rdfs:label, dcterms:description,\\n';\n01276| message += ' vad:hasNext, vad:hasExecutor, vad:hasParentTrig, vad:includes,\\n';\n01277| message += ' vad:processSubtype, vad:hasTrig';\n01278| \n01279| return message;\n01280| }\n01281| \n01282| // ============================================================================\n01283| // КОНФИГУРАЦИЯ СТИЛЕЙ\n01284| // ============================================================================\n01285| \n01286| const StyleName = {\n01287| nodeStyles: {\n01288| 'PersonStyle': {\n01289| types: ['foaf:Person', 'schema:Person', 'http://xmlns.com/foaf/0.1/Person'],\n01290| dot: 'shape=\"octagon\" height=\"0.75\" width=\"0.75\" fixedsize=\"true\" color=\"#9C27B0\" fillcolor=\"#F3E5F5\" fontname=\"Arial\" fontsize=\"10\" style=\"filled\"',\n01291| label: 'Люди (foaf:Person)',\n01292| description: 'Объекты типа foaf:Person или schema:Person'\n01293| },\n01294| 'OrganizationStyle': {\n01295| types: ['foaf:Organization', 'schema:Organization', 'http://xmlns.com/foaf/0.1/Organization'],\n01296| dot: 'shape=\"box\" height=\"0.6\" width=\"1.2\" color=\"Blue\" fillcolor=\"#E6F3FF\" fontname=\"Arial\" fontsize=\"10\" style=\"filled,bold\"',\n01297| label: 'Организации (foaf:Organization)',\n01298| description: 'Объекты типа foaf:Organization'\n01299| },\n01300| 'DocumentStyle': {\n01301| types: ['foaf:Document', 'schema:Document', 'http://xmlns.com/foaf/0.1/Document'],\n01302| dot: 'shape=\"note\" height=\"0.6\" width=\"1.0\" color=\"Green\" fillcolor=\"#E8F5E9\" fontname=\"Arial\" fontsize=\"10\" style=\"filled\"',\n01303| label: 'Документы (foaf:Document)',\n01304| description: 'Объекты типа foaf:Document'\n01305| },\n01306| 'LiteralStyle': {\n01307| types: ['_Literal'],\n01308| dot: 'shape=\"box\" color=\"#666666\" fillcolor=\"#FFFFCC\" fontname=\"Arial\" fontsize=\"9\" style=\"filled,rounded\"',\n01309| label: 'Литералы (Literal)',\n01310| description: 'Строковые значения, числа, даты'\n01311| },\n01312| 'BlankNodeStyle': {\n01313| types: ['_BlankNode'],\n01314| dot: 'shape=\"ellipse\" color=\"#999999\" fillcolor=\"#E0E0E0\" fontname=\"Arial\" fontsize=\"9\" style=\"filled,dashed\"',\n01315| label: 'Пустые узлы (BlankNode)',\n01316| description: 'Анонимные узлы без URI'\n01317| },\n01318| 'default': {\n01319| types: [],\n01320| dot: 'shape=\"ellipse\" color=\"#1976D2\" fillcolor=\"#CCE5FF\" fontname=\"Arial\" fontsize=\"10\" style=\"filled\"',\n01321| label: 'По умолчанию (URI)',\n01322| description: 'Все остальные URI-ресурсы'\n01323| }\n01324| },\n01325| edgeStyles: {\n01326| 'TypeStyle': {\n01327| predicates: ['rdf:type', 'a', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'],\n01328| dot: 'color=\"#9C27B0\" penwidth=\"2\" style=\"dashed\" arrowhead=\"empty\"',\n01329| label: 'Тип объекта (rdf:type)',\n01330| description: 'Связь объекта с его RDF-типом'\n01331| },\n01332| 'KnowsStyle': {\n01333| predicates: ['foaf:knows', 'http://xmlns.com/foaf/0.1/knows'],\n01334| dot: 'color=\"#4CAF50\" penwidth=\"2\" style=\"solid\" arrowhead=\"vee\"',\n01335| label: 'Знает (foaf:knows)',\n01336| description: 'Социальная связь между людьми'\n01337| },\n01338| 'MemberStyle': {\n01339| predicates: ['foaf:member', 'schema:member', 'http://xmlns.com/foaf/0.1/member'],\n01340| dot: 'color=\"#795548\" penwidth=\"2\" style=\"solid\" arrowhead=\"diamond\"',\n01341| label: 'Член (foaf:member)',\n01342| description: 'Членство в организации'\n01343| },\n01344| 'AttributeStyle': {\n01345| predicates: [\n01346| 'foaf:name', 'http://xmlns.com/foaf/0.1/name',\n01347| 'foaf:age', 'http://xmlns.com/foaf/0.1/age',\n01348| 'rdfs:label', 'http://www.w3.org/2000/01/rdf-schema#label',\n01349| 'rdfs:comment', 'http://www.w3.org/2000/01/rdf-schema#comment'\n01350| ],\n01351| dot: 'color=\"#2196F3\" penwidth=\"1\" style=\"dotted\" arrowhead=\"normal\"',\n01352| label: 'Атрибуты (name, label...)',\n01353| description: 'Свойства объекта: имя, возраст и др.'\n01354| },\n01355| 'default': {\n01356| predicates: [],\n01357| dot: 'color=\"#666666\" penwidth=\"1\" style=\"solid\" arrowhead=\"normal\"',\n01358| label: 'По умолчанию',\n01359| description: 'Все остальные предикаты'\n01360| }\n01361| }\n01362| };\n01363| \n01364| // ============================================================================\n01365| // СТИЛИ VAD (Value Added Chain Diagram)\n01366| // ============================================================================\n01367| \n01368| const VADNodeStyles = {\n01369| // =================================================================\n01370| // Детализированные процессы (Detailed) - имеют vad:hasTrig\n01371| // =================================================================\n01372| 'ProcessStyleDetailedChild': {\n01373| types: ['vad:TypeProcess', 'http://example.org/vad#TypeProcess'],\n01374| subtypes: ['vad:DetailedChild', 'http://example.org/vad#DetailedChild'],\n01375| // cds shape с голубой заливкой для детализированного подпроцесса\n01376| dot: 'shape=\"cds\" height=\"0.8\" width=\"1.5\" color=\"#1565C0\" fillcolor=\"#90CAF9\" fontname=\"Arial\" fontsize=\"11\" style=\"filled\"',\n01377| label: 'Детализированный подпроцесс (vad:DetailedChild)',\n01378| description: 'Детализированный процесс, являющийся подпроцессом текущей схемы'\n01379| },\n01380| 'ProcessStyleDetailedExternal': {\n01381| types: ['vad:TypeProcess', 'http://example.org/vad#TypeProcess'],\n01382| subtypes: ['vad:DetailedExternal', 'http://example.org/vad#DetailedExternal'],\n01383| // cds shape с оранжевой заливкой для детализированного внешнего процесса\n01384| dot: 'shape=\"cds\" height=\"0.8\" width=\"1.5\" color=\"#E65100\" fillcolor=\"#FFB74D\" fontname=\"Arial\" fontsize=\"11\" style=\"filled\"',\n01385| label: 'Детализированный внешний (vad:DetailedExternal)',\n01386| description: 'Детализированный процесс, НЕ являющийся подпроцессом текущей схемы'\n01387| },\n01388| // =================================================================\n01389| // Недетализированные процессы (notDetailed) - НЕ имеют vad:hasTrig\n01390| // =================================================================\n01391| 'ProcessStyleNotDetailedChild': {\n01392| types: ['vad:TypeProcess', 'http://example.org/vad#TypeProcess'],\n01393| subtypes: ['vad:notDetailedChild', 'http://example.org/vad#notDetailedChild'],\n01394| // cds shape с зелёной заливкой для недетализированного подпроцесса\n01395| dot: 'shape=\"cds\" height=\"0.8\" width=\"1.5\" color=\"#2E7D32\" fillcolor=\"#A5D6A7\" fontname=\"Arial\" fontsize=\"11\" style=\"filled\"',\n01396| label: 'Недетализированный подпроцесс (vad:notDetailedChild)',\n01397| description: 'Недетализированный процесс-подпроцесс'\n01398| },\n01399| 'ProcessStyleNotDetailedExternal': {\n01400| types: ['vad:TypeProcess', 'http://example.org/vad#TypeProcess'],\n01401| subtypes: ['vad:notDetailedExternal', 'http://example.org/vad#notDetailedExternal'],\n01402| // cds shape со светло-серой заливкой для недетализированного внешнего процесса\n01403| dot: 'shape=\"cds\" height=\"0.8\" width=\"1.5\" color=\"#616161\" fillcolor=\"#E0E0E0\" fontname=\"Arial\" fontsize=\"11\" style=\"filled\"',\n01404| label: 'Недетализированный внешний (vad:notDetailedExternal)',\n01405| description: 'Недетализированный внешний процесс'\n01406| },\n01407| 'ProcessStyleNotDefinedType': {\n01408| types: ['vad:TypeProcess', 'http://example.org/vad#TypeProcess'],\n01409| subtypes: ['vad:NotDefinedType', 'http://example.org/vad#NotDefinedType'],\n01410| // cds shape с красноватой заливкой для процесса с неопределенным типом\n01411| dot: 'shape=\"cds\" height=\"0.8\" width=\"1.5\" color=\"#C62828\" fillcolor=\"#FFCDD2\" fontname=\"Arial\" fontsize=\"11\" style=\"filled\"',\n01412| label: 'Тип не определен (vad:NotDefinedType)',\n01413| description: 'Процесс с неопределенным родительским процессом'\n01414| },\n01415| // =================================================================\n01416| // Устаревшие стили для обратной совместимости\n01417| // =================================================================\n01418| 'ProcessStyleBasic': {\n01419| types: ['vad:TypeProcess', 'http://example.org/vad#TypeProcess'],\n01420| subtypes: ['vad:Basic', 'http://example.org/vad#Basic'],\n01421| // cds shape (chevron) с зелёной заливкой для базового типа (устаревший)\n01422| dot: 'shape=\"cds\" height=\"0.8\" width=\"1.5\" color=\"#2E7D32\" fillcolor=\"#A5D6A7\" fontname=\"Arial\" fontsize=\"11\" style=\"filled\"',\n01423| label: 'Процесс Базовый (vad:Basic) [устаревший]',\n01424| description: 'Устаревший базовый бизнес-процесс в VAD диаграмме'\n01425| },\n01426| 'ProcessStyleDetailed': {\n01427| types: ['vad:TypeProcess', 'http://example.org/vad#TypeProcess'],\n01428| subtypes: ['vad:Detailed', 'http://example.org/vad#Detailed'],\n01429| // cds shape (chevron) с голубой заливкой для детализированного типа (устаревший)\n01430| dot: 'shape=\"cds\" height=\"0.8\" width=\"1.5\" color=\"#1565C0\" fillcolor=\"#90CAF9\" fontname=\"Arial\" fontsize=\"11\" style=\"filled\"',\n01431| label: 'Процесс Детализированный (vad:Detailed) [устаревший]',\n01432| description: 'Устаревший детализированный бизнес-процесс в VAD диаграмме'\n01433| },\n01434| // =================================================================\n01435| // Другие элементы\n01436| // =================================================================\n01437| 'ExecutorGroupStyle': {\n01438| types: ['vad:ExecutorGroup', 'http://example.org/vad#ExecutorGroup'],\n01439| dot: 'shape=\"ellipse\" color=\"#B8860B\" fillcolor=\"#FFFFCC\" fontname=\"Arial\" fontsize=\"9\" style=\"filled\"',\n01440| label: 'Группа исполнителей (vad:ExecutorGroup)',\n01441| description: 'Группа исполнителей процесса (эллипс с желтоватой заливкой)'\n01442| },\n01443| 'ExecutorStyle': {\n01444| types: ['vad:TypeExecutor', 'http://example.org/vad#TypeExecutor'],\n01445| dot: 'shape=\"ellipse\" height=\"0.4\" width=\"0.8\" color=\"#6A1B9A\" fillcolor=\"#E1BEE7\" fontname=\"Arial\" fontsize=\"9\" style=\"filled\"',\n01446| label: 'Исполнитель (vad:TypeExecutor)',\n01447| description: 'Исполнитель процесса'\n01448| },\n01449| 'default': {\n01450| types: [],\n01451| dot: 'shape=\"ellipse\" color=\"#1976D2\" fillcolor=\"#CCE5FF\" fontname=\"Arial\" fontsize=\"10\" style=\"filled\"',\n01452| label: 'По умолчанию',\n01453| description: 'Другие объекты'\n01454| }\n01455| };\n01456| \n01457| const VADEdgeStyles = {\n01458| 'HasNextStyle': {\n01459| predicates: ['vad:hasNext', 'http://example.org/vad#hasNext'],\n01460| // Зелёная стрелка для связей между процессами\n01461| dot: 'color=\"#2E7D32\" penwidth=\"2\" style=\"solid\" arrowhead=\"vee\"',\n01462| label: 'Следующий (vad:hasNext)',\n01463| description: 'Связь с следующим процессом'\n01464| },\n01465| 'HasExecutorStyle': {\n01466| predicates: ['vad:hasExecutor', 'http://example.org/vad#hasExecutor'],\n01467| dot: 'color=\"#1565C0\" penwidth=\"1\" style=\"dashed\" arrowhead=\"none\"',\n01468| label: 'Исполнитель (vad:hasExecutor)',\n01469| description: 'Связь процесса с группой исполнителей (ненаправленная)'\n01470| },\n01471| 'IncludesStyle': {\n01472| predicates: ['vad:includes', 'http://example.org/vad#includes'],\n01473| dot: 'color=\"#6A1B9A\" penwidth=\"1\" style=\"dotted\" arrowhead=\"normal\"',\n01474| label: 'Включает (vad:includes)',\n01475| description: 'Связь группы с исполнителями'\n01476| },\n01477| 'HasParentStyle': {\n01478| predicates: ['vad:hasParentTrig', 'http://example.org/vad#hasParentTrig'],\n01479| dot: 'color=\"#999999\" penwidth=\"1\" style=\"dashed\" arrowhead=\"empty\"',\n01480| label: 'Родитель (vad:hasParentTrig)',\n01481| description: 'Связь с родительским процессом'\n01482| },\n01483| 'TypeStyle': {\n01484| predicates: ['rdf:type', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'],\n01485| dot: 'color=\"#9C27B0\" penwidth=\"1\" style=\"dashed\" arrowhead=\"empty\"',\n01486| label: 'Тип (rdf:type)',\n01487| description: 'Тип объекта'\n01488| },\n01489| 'default': {\n01490| predicates: [],\n01491| dot: 'color=\"#666666\" penwidth=\"1\" style=\"solid\" arrowhead=\"normal\"',\n01492| label: 'По умолчанию',\n01493| description: 'Другие связи'\n01494| }\n01495| };\n01496| \n01497| const AggregationNodeStyles = {\n01498| 'PersonStyle': {\n01499| types: ['foaf:Person', 'schema:Person', 'http://xmlns.com/foaf/0.1/Person'],\n01500| dot: 'shape=\"ellipse\" color=\"#9C27B0\" penwidth=\"2\" fillcolor=\"#F3E5F5\" fontname=\"Arial\" fontsize=\"10\" style=\"filled\"',\n01501| label: 'Люди (foaf:Person)',\n01502| description: 'Объекты типа foaf:Person'\n01503| },\n01504| 'OrganizationStyle': {\n01505| types: ['foaf:Organization', 'schema:Organization', 'http://xmlns.com/foaf/0.1/Organization'],\n01506| dot: 'shape=\"ellipse\" color=\"Blue\" penwidth=\"3\" fillcolor=\"#E3F2FD\" fontname=\"Arial\" fontsize=\"10\" style=\"filled\"',\n01507| label: 'Организации (foaf:Organization)',\n01508| description: 'Объекты типа foaf:Organization'\n01509| },\n01510| 'DocumentStyle': {\n01511| types: ['foaf:Document', 'schema:Document', 'http://xmlns.com/foaf/0.1/Document'],\n01512| dot: 'shape=\"ellipse\" color=\"Green\" penwidth=\"2\" fillcolor=\"#E8F5E9\" fontname=\"Arial\" fontsize=\"10\" style=\"filled\"',\n01513| label: 'Документы (foaf:Document)',\n01514| description: 'Объекты типа foaf:Document'\n01515| },\n01516| 'BlankNodeStyle': {\n01517| types: ['_BlankNode'],\n01518| dot: 'shape=\"ellipse\" color=\"#999999\" penwidth=\"1\" fillcolor=\"#E0E0E0\" fontname=\"Arial\" fontsize=\"9\" style=\"filled,dashed\"',\n01519| label: 'Пустые узлы (BlankNode)',\n01520| description: 'Анонимные узлы без URI'\n01521| },\n01522| 'default': {\n01523| types: [],\n01524| dot: 'shape=\"ellipse\" color=\"#1976D2\" penwidth=\"1\" fillcolor=\"#CCE5FF\" fontname=\"Arial\" fontsize=\"10\" style=\"filled\"',\n01525| label: 'По умолчанию (URI)',\n01526| description: 'Все остальные URI-ресурсы'\n01527| }\n01528| };\n01529| \n01530| // ============================================================================\n01531| // ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ\n01532| // ============================================================================\n01533| \n01534| let currentSvgElement = null;\n01535| let currentScale = 1.0;\n01536| let currentPrefixes = {};\n01537| let nodeTypesCache = {};\n01538| let nodeSubtypesCache = {};\n01539| let currentQuads = [];\n01540| let nodeLabelToUri = {};\n01541| let selectedNodeElement = null;\n01542| let propertiesPanelCounter = 0;\n01543| let openPropertiesPanels = [];\n01544| let currentMode = Mode;\n01545| let draggedPanel = null;\n01546| let dragOffsetX = 0;\n01547| let dragOffsetY = 0;\n01548| let currentStore = null;\n01549| let comunicaEngine = null;\n01550| let currentDotCode = '';\n01551| \n01552| // virtualRDFdata - хранилище вычисляемых RDF данных (vad:processSubtype и др.)\n01553| // Структура: { 'vad:vt_trigName': { quads: [...], trigData: {...} } }\n01554| let virtualRDFdata = {};\n01555| \n01556| const defaultSparqlQuery = `SELECT ?s ?p ?o\n01557| WHERE {\n01558| ?s ?p ?o .\n01559| }`;\n01560| \n01561| /**\n01562| * Генерирует SPARQL PREFIX декларации из объекта префиксов\n01563| * @param {Object} prefixes - Объект с префиксами {prefix: uri}\n01564| * @returns {string} - Строка с PREFIX декларациями\n01565| */\n01566| function generateSparqlPrefixes(prefixes) {\n01567| if (!prefixes || Object.keys(prefixes).length === 0) {\n01568| return '';\n01569| }\n01570| \n01571| let prefixLines = [];\n01572| for (const [prefix, uri] of Object.entries(prefixes)) {\n01573| prefixLines.push(`PREFIX ${prefix}: <${uri}>`);\n01574| }\n01575| return prefixLines.join('\\n') + '\\n\\n';\n01576| }\n01577| \n01578| /**\n01579| * Генерирует SPARQL запрос с GRAPH clause для указанного TriG\n01580| * @param {string} trigUri - URI TriG для фильтрации\n01581| * @returns {string} - SPARQL запрос с GRAPH clause и PREFIX декларациями\n01582| */\n01583| function getSparqlQueryForTriG(trigUri) {\n01584| if (!trigUri) return defaultSparqlQuery;\n01585| \n01586| const prefixedUri = getPrefixedName(trigUri, currentPrefixes);\n01587| // Если URI имеет префикс, используем его, иначе используем полный URI в угловых скобках\n01588| const graphRef = prefixedUri.includes(':') && !prefixedUri.startsWith('http')\n01589| ? prefixedUri\n01590| : `<${trigUri}>`;\n01591| \n01592| // Генерируем PREFIX декларации для SPARQL запроса\n01593| const prefixDeclarations = generateSparqlPrefixes(currentPrefixes);\n01594| \n01595| return `${prefixDeclarations}SELECT ?s ?p ?o\n01596| WHERE {\n01597| GRAPH ${graphRef} {\n01598| ?s ?p ?o .\n01599| }\n01600| }`;\n01601| }\n01602| \n01603| /**\n01604| * Обновляет SPARQL запрос в текстовом поле для текущего выбранного TriG\n01605| */\n01606| function updateSparqlQueryForTriG() {\n01607| if (currentMode !== 'vad-trig' || !selectedTrigUri) return;\n01608| \n01609| const queryInput = document.getElementById('sparql-query');\n01610| if (!queryInput) return;\n01611| \n01612| queryInput.value = getSparqlQueryForTriG(selectedTrigUri);\n01613| }\n01614| \n01615| let activeFilters = [...getFilterConfig(Mode).hiddenPredicates];\n01616| let allPredicates = [];\n01617| \n01618| // ============================================================================\n01619| // ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ДЛЯ VAD TriG РЕЖИМА\n01620| // ============================================================================\n01621| \n01622| let trigHierarchy = {}; // Иерархия TriG графов: { uri -> { label, hasParentTrig, children, quads } }\n01623| let selectedTrigUri = null; // Текущий выбранный TriG для отображения\n01624| let allTrigGraphs = []; // Список всех TriG графов\n01625| let isNewTrigQuery = false; // Флаг: текущий SPARQL запрос создан функцией \"New TriG\"\n01626| const PTREE_GRAPH_URI = 'http://example.org/vad#ptree'; // URI графа Дерево Процессов\n01627| const RTREE_GRAPH_URI = 'http://example.org/vad#rtree'; // URI графа Дерево Исполнителей\n01628| \n01629| /**\n01630| * Получает метаданные процесса из vad:ptree (rdfs:label, dcterms:description, vad:hasTrig)\n01631| * @param {string} processUri - URI процесса\n01632| * @param {Object} prefixes - Словарь префиксов\n01633| * @returns {Object} - { label: string|null, description: string|null, hasTrig: string|null }\n01634| */\n01635| function getProcessMetadataFromPtree(processUri, prefixes) {\n01636| const result = { label: null, description: null, hasTrig: null };\n01637| \n01638| // Если нет trigHierarchy или нет vad:ptree, возвращаем пустой результат\n01639| if (!trigHierarchy || !trigHierarchy[PTREE_GRAPH_URI]) {\n01640| return result;\n01641| }\n01642| \n01643| const ptreeQuads = trigHierarchy[PTREE_GRAPH_URI].quads;\n01644| \n01645| ptreeQuads.forEach(quad => {\n01646| if (quad.subject.value !== processUri) return;\n01647| \n01648| const predicateUri = quad.predicate.value;\n01649| const predicateLabel = getPrefixedName(predicateUri, prefixes);\n01650| \n01651| if (predicateLabel === 'rdfs:label' || predicateUri === 'http://www.w3.org/2000/01/rdf-schema#label') {\n01652| result.label = quad.object.value;\n01653| }\n01654| if (predicateLabel === 'dcterms:description' || predicateUri === 'http://purl.org/dc/terms/description') {\n01655| result.description = quad.object.value;\n01656| }\n01657| if (predicateLabel === 'vad:hasTrig' || predicateUri === 'http://example.org/vad#hasTrig') {\n01658| result.hasTrig = quad.object.value;\n01659| }\n01660| });\n01661| \n01662| return result;\n01663| }\n01664| \n01665| /**\n01666| * Получает имя исполнителя (rdfs:label) из vad:rtree\n01667| * @param {string} executorUri - URI исполнителя\n01668| * @param {Object} prefixes - Словарь префиксов\n01669| * @returns {string|null} - rdfs:label исполнителя или null\n01670| */\n01671| function getExecutorNameFromRtree(executorUri, prefixes) {\n01672| // Если нет trigHierarchy или нет vad:rtree, возвращаем null\n01673| if (!trigHierarchy || !trigHierarchy[RTREE_GRAPH_URI]) {\n01674| return null;\n01675| }\n01676| \n01677| const rtreeQuads = trigHierarchy[RTREE_GRAPH_URI].quads;\n01678| \n01679| for (const quad of rtreeQuads) {\n01680| if (quad.subject.value !== executorUri) continue;\n01681| \n01682| const predicateUri = quad.predicate.value;\n01683| const predicateLabel = getPrefixedName(predicateUri, prefixes);\n01684| \n01685| if (predicateLabel === 'rdfs:label' || predicateUri === 'http://www.w3.org/2000/01/rdf-schema#label') {\n01686| return quad.object.value;\n01687| }\n01688| }\n01689| \n01690| return null;\n01691| }\n01692| \n01693| // ============================================================================\n01694| // ФУНКЦИИ ДЛЯ VAD TriG РЕЖИМА\n01695| // ============================================================================\n01696| \n01697| /**\n01698| * Парсит иерархию TriG графов из квадов\n01699| * @param {Array} quads - Все квады из RDF данных\n01700| * @param {Object} prefixes - Словарь префиксов\n01701| * @returns {Object} - { valid: boolean, errors: [], hierarchy: {}, rootTrigUri: string }\n01702| */\n01703| function parseTriGHierarchy(quads, prefixes) {\n01704| const errors = [];\n01705| const hierarchy = {};\n01706| const graphUris = new Set();\n01707| \n01708| // Собираем все уникальные именованные графы\n01709| quads.forEach(quad => {\n01710| if (quad.graph && quad.graph.value && quad.graph.value !== '') {\n01711| graphUris.add(quad.graph.value);\n01712| }\n01713| });\n01714| \n01715| // Инициализируем структуру для каждого графа\n01716| graphUris.forEach(graphUri => {\n01717| hierarchy[graphUri] = {\n01718| uri: graphUri,\n01719| label: null,\n01720| hasParent: null,\n01721| children: [],\n01722| quads: [],\n01723| processes: []\n01724| };\n01725| });\n01726| \n01727| // Собираем квады для каждого графа\n01728| quads.forEach(quad => {\n01729| const graphUri = quad.graph?.value || '';\n01730| if (graphUri && hierarchy[graphUri]) {\n01731| hierarchy[graphUri].quads.push(quad);\n01732| }\n01733| });\n01734| \n01735| // Ищем rdfs:label и vad:hasParentTrig для каждого графа (определяются вне графа)\n01736| quads.forEach(quad => {\n01737| const subjectUri = quad.subject.value;\n01738| const predicateUri = quad.predicate.value;\n01739| const predicateLabel = getPrefixedName(predicateUri, prefixes);\n01740| \n01741| // Проверяем, является ли субъект именованным графом\n01742| if (hierarchy[subjectUri]) {\n01743| // rdfs:label для графа\n01744| if (predicateLabel === 'rdfs:label' || predicateUri === 'http://www.w3.org/2000/01/rdf-schema#label') {\n01745| hierarchy[subjectUri].label = quad.object.value;\n01746| }\n01747| \n01748| // vad:hasParentTrig для графа\n01749| if (predicateLabel === 'vad:hasParentTrig' || predicateUri === 'http://example.org/vad#hasParentTrig') {\n01750| hierarchy[subjectUri].hasParent = quad.object.value;\n01751| }\n01752| }\n01753| });\n01754| \n01755| // Проверяем, что все графы имеют hasParentTrig\n01756| Object.values(hierarchy).forEach(graphInfo => {\n01757| if (!graphInfo.hasParent) {\n01758| const graphLabel = getPrefixedName(graphInfo.uri, prefixes);\n01759| errors.push({\n01760| graph: graphLabel,\n01761| message: `TriG \"${graphLabel}\" не имеет свойства hasParentTrig. В режиме VAD TriG каждый TriG граф должен иметь свойство hasParentTrig.`\n01762| });\n01763| }\n01764| });\n01765| \n01766| if (errors.length > 0) {\n01767| return { valid: false, errors, hierarchy: null, rootTrigUris: [] };\n01768| }\n01769| \n01770| // Строим дерево: находим корневые элементы (hasParentTrig = vad:root)\n01771| const rootUri = 'http://example.org/vad#root';\n01772| const rootTrigUris = [];\n01773| \n01774| Object.values(hierarchy).forEach(graphInfo => {\n01775| const parentUri = graphInfo.hasParent;\n01776| const parentLabel = getPrefixedName(parentUri, prefixes);\n01777| \n01778| // Проверяем, является ли родитель \"root\"\n01779| if (parentUri === rootUri || parentLabel === 'vad:root') {\n01780| // vad:ptree и vad:rtree - это специальные графы для метаданных процессов и исполнителей,\n01781| // они не должны быть включены в rootTrigUris для отображения дерева\n01782| if (graphInfo.uri !== PTREE_GRAPH_URI && graphInfo.uri !== RTREE_GRAPH_URI) {\n01783| rootTrigUris.push(graphInfo.uri);\n01784| }\n01785| } else if (hierarchy[parentUri]) {\n01786| // Добавляем как дочерний элемент к родителю\n01787| hierarchy[parentUri].children.push(graphInfo.uri);\n01788| }\n01789| });\n01790| \n01791| // Собираем информацию о процессах для каждого графа\n01792| // Шаг 1: Собираем все URI процессов из всех графов (включая vad:ptree)\n01793| const allProcessUris = new Set();\n01794| Object.values(hierarchy).forEach(graphInfo => {\n01795| graphInfo.quads.forEach(quad => {\n01796| const predicateUri = quad.predicate.value;\n01797| const predicateLabel = getPrefixedName(predicateUri, prefixes);\n01798| \n01799| // Если это rdf:type и тип = vad:TypeProcess\n01800| if (predicateLabel === 'rdf:type' || predicateUri === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type') {\n01801| const typeUri = quad.object.value;\n01802| const typeLabel = getPrefixedName(typeUri, prefixes);\n01803| if (typeLabel === 'vad:TypeProcess' || typeUri === 'http://example.org/vad#TypeProcess') {\n01804| allProcessUris.add(quad.subject.value);\n01805| }\n01806| }\n01807| });\n01808| });\n01809| \n01810| // Шаг 2: Для каждого графа (кроме ptree) определяем, какие процессы в нём присутствуют\n01811| // Процесс считается присутствующим в графе, если у него есть свойства (vad:hasExecutor, vad:hasNext и т.д.) в этом графе\n01812| const processPredicates = [\n01813| 'http://example.org/vad#hasExecutor',\n01814| 'vad:hasExecutor',\n01815| 'http://example.org/vad#hasNext',\n01816| 'vad:hasNext',\n01817| 'http://example.org/vad#processSubtype',\n01818| 'vad:processSubtype'\n01819| ];\n01820| \n01821| Object.values(hierarchy).forEach(graphInfo => {\n01822| // Для vad:ptree пропускаем присваивание процессов (это граф метаданных)\n01823| if (graphInfo.uri === PTREE_GRAPH_URI) {\n01824| return;\n01825| }\n01826| \n01827| // Ищем процессы, которые имеют свойства в этом графе\n01828| const processesInGraph = new Set();\n01829| graphInfo.quads.forEach(quad => {\n01830| const subjectUri = quad.subject.value;\n01831| const predicateUri = quad.predicate.value;\n01832| const predicateLabel = getPrefixedName(predicateUri, prefixes);\n01833| \n01834| // Если субъект является процессом (из allProcessUris) и предикат - это свойство процесса\n01835| if (allProcessUris.has(subjectUri)) {\n01836| if (processPredicates.includes(predicateUri) || processPredicates.includes(predicateLabel)) {\n01837| processesInGraph.add(subjectUri);\n01838| }\n01839| }\n01840| });\n01841| \n01842| graphInfo.processes = Array.from(processesInGraph);\n01843| });\n01844| \n01845| return { valid: true, errors: [], hierarchy, rootTrigUris };\n01846| }\n01847| \n01848| /**\n01849| * Форматирует ошибки VAD TriG для отображения\n01850| * @param {Array} errors - Массив ошибок\n01851| * @returns {string} - Отформатированное сообщение\n01852| */\n01853| function formatVADTriGErrors(errors) {\n01854| let message = 'ОШИБКА ВАЛИДАЦИИ VAD TriG\\n';\n01855| message += '═══════════════════════════════════════\\n\\n';\n01856| \n01857| errors.forEach((error, index) => {\n01858| message += `Ошибка ${index + 1}:\\n`;\n01859| message += ` TriG граф: ${error.graph}\\n`;\n01860| message += ` ${error.message}\\n\\n`;\n01861| });\n01862| \n01863| message += '═══════════════════════════════════════\\n';\n01864| message += `Всего ошибок: ${errors.length}\\n`;\n01865| message += '\\nВ режиме VAD TriG каждый TriG граф должен иметь свойство vad:hasParentTrig.\\n';\n01866| message += 'Корневой граф должен иметь hasParentTrig = vad:root.\\n';\n01867| message += 'Дочерние графы указывают на родительский граф через hasParentTrig.';\n01868| \n01869| return message;\n01870| }\n01871| \n01872| /**\n01873| * Строит HTML для дерева TriG\n01874| * @param {string} trigUri - URI текущего TriG\n01875| * @param {Object} hierarchy - Иерархия TriG\n01876| * @param {Object} prefixes - Словарь префиксов\n01877| * @param {number} level - Уровень вложенности (для отступов)\n01878| * @returns {string} - HTML дерева\n01879| */\n01880| function buildTriGTreeHtml(trigUri, hierarchy, prefixes, level = 0) {\n01881| const graphInfo = hierarchy[trigUri];\n01882| if (!graphInfo) return '';\n01883| \n01884| const prefixedUri = getPrefixedName(trigUri, prefixes);\n01885| const localName = getLocalName(trigUri);\n01886| const displayLabel = graphInfo.label || localName;\n01887| const isSelected = trigUri === selectedTrigUri;\n01888| const hasChildren = graphInfo.children.length > 0;\n01889| const hasProcesses = graphInfo.processes.length > 0;\n01890| const hasExpandableContent = hasChildren || hasProcesses;\n01891| \n01892| let html = '';\n01893| \n01894| // Элемент дерева\n01895| html += `<div class=\"trig-tree-item ${isSelected ? 'selected active' : ''}\"\n01896| data-trig-uri=\"${escapeHtml(trigUri)}\"\n01897| onclick=\"selectTriG('${escapeHtml(trigUri)}')\">`;\n01898| \n01899| // Значок раскрытия/закрытия\n01900| if (hasExpandableContent) {\n01901| html += `<span class=\"trig-tree-toggle\">▼</span>`;\n01902| } else {\n01903| html += `<span class=\"trig-tree-toggle\"></span>`;\n01904| }\n01905| \n01906| // Метка с id\n01907| html += `<span class=\"trig-tree-label\">${escapeHtml(displayLabel)}</span>`;\n01908| html += `<span class=\"trig-tree-id\">(${escapeHtml(localName)})</span>`;\n01909| html += `</div>`;\n01910| \n01911| // Содержимое дерева (дочерние TriG и состав объектов)\n01912| if (hasExpandableContent) {\n01913| html += `<div class=\"trig-tree-children\">`;\n01914| \n01915| // Сначала показываем дочерние TriG\n01916| graphInfo.children.forEach(childUri => {\n01917| html += buildTriGTreeHtml(childUri, hierarchy, prefixes, level + 1);\n01918| });\n01919| \n01920| // Затем показываем \"Состав объектов\" с процессами\n01921| if (hasProcesses) {\n01922| html += buildObjectCompositionHtml(trigUri, graphInfo.processes, prefixes);\n01923| }\n01924| \n01925| html += `</div>`;\n01926| }\n01927| \n01928| return html;\n01929| }\n01930| \n01931| /**\n01932| * Строит HTML для раздела \"Состав объектов\" с процессами\n01933| * @param {string} trigUri - URI TriG, которому принадлежат процессы\n01934| * @param {Array} processes - Массив URI процессов\n01935| * @param {Object} prefixes - Словарь префиксов\n01936| * @returns {string} - HTML раздела\n01937| */\n01938| function buildObjectCompositionHtml(trigUri, processes, prefixes) {\n01939| const objectCompositionId = `obj-comp-${escapeHtml(trigUri).replace(/[^a-zA-Z0-9]/g, '_')}`;\n01940| \n01941| let html = '';\n01942| \n01943| // Заголовок \"Состав объектов\"\n01944| html += `<div class=\"trig-tree-item object-composition-header\"\n01945| onclick=\"toggleObjectComposition('${objectCompositionId}')\">`;\n01946| html += `<span class=\"trig-tree-toggle object-composition-toggle\" id=\"${objectCompositionId}-toggle\">▶</span>`;\n01947| html += `<span class=\"trig-tree-label object-composition-label\">Состав объектов</span>`;\n01948| html += `<span class=\"trig-tree-id\">(${processes.length})</span>`;\n01949| html += `</div>`;\n01950| \n01951| // Список процессов (скрыт по умолчанию)\n01952| html += `<div class=\"trig-tree-children object-composition-list\" id=\"${objectCompositionId}\" style=\"display: none;\">`;\n01953| \n01954| processes.forEach(processUri => {\n01955| const processLabel = getPrefixedName(processUri, prefixes);\n01956| const processLocalName = getLocalName(processUri);\n01957| \n01958| // Ищем rdfs:label процесса - сначала в текущем TriG, затем в vad:ptree\n01959| let processDisplayName = processLocalName;\n01960| if (trigHierarchy && trigHierarchy[trigUri]) {\n01961| const graphQuads = trigHierarchy[trigUri].quads;\n01962| const labelQuad = graphQuads.find(q =>\n01963| q.subject.value === processUri &&\n01964| (q.predicate.value === 'http://www.w3.org/2000/01/rdf-schema#label' ||\n01965| getPrefixedName(q.predicate.value, prefixes) === 'rdfs:label')\n01966| );\n01967| if (labelQuad) {\n01968| processDisplayName = labelQuad.object.value;\n01969| } else {\n01970| // Fallback: ищем в vad:ptree\n01971| const ptreeMetadata = getProcessMetadataFromPtree(processUri, prefixes);\n01972| if (ptreeMetadata.label) {\n01973| processDisplayName = ptreeMetadata.label;\n01974| }\n01975| }\n01976| }\n01977| \n01978| html += `<div class=\"trig-tree-item process-item\"\n01979| data-process-uri=\"${escapeHtml(processUri)}\"\n01980| data-trig-uri=\"${escapeHtml(trigUri)}\"\n01981| onclick=\"event.stopPropagation(); selectProcess('${escapeHtml(processUri)}', '${escapeHtml(trigUri)}')\">`;\n01982| html += `<span class=\"trig-tree-toggle\"></span>`;\n01983| html += `<span class=\"process-icon\">⚙</span>`;\n01984| html += `<span class=\"trig-tree-label\">${escapeHtml(processDisplayName)}</span>`;\n01985| html += `</div>`;\n01986| });\n01987| \n01988| html += `</div>`;\n01989| \n01990| return html;\n01991| }\n01992| \n01993| /**\n01994| * Переключает видимость списка объектов (Состав объектов)\n01995| * @param {string} listId - ID списка для переключения\n01996| */\n01997| function toggleObjectComposition(listId) {\n01998| const list = document.getElementById(listId);\n01999| const toggle = document.getElementById(listId + '-toggle');\n02000| \n\n(File has more lines. Use 'offset' parameter to read beyond line 2000)\n</file>",
[2026-01-26T19:13:21.540Z] [INFO] "title": "ver8a/index.html",
[2026-01-26T19:13:21.540Z] [INFO] "metadata": {
[2026-01-26T19:13:21.541Z] [INFO] "preview": "<!DOCTYPE html>\n<html lang=\"ru\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>RDF Grapher ver8 - Визуализация RDF данных с поддержкой TriG + SPARQL Smart Design + Ontology </title>\n\n <!--\n RDF Grapher ver8a - Сервис для парсинга RDF данных и их визуализации в виде графа\n Новые возможности ver8: SPARQL Smart Design + Ontology\n Этот сервис является продолжением https://www.ldf.fi/service/rdf-grapher в части BPM\n Использует клиентские JavaScript библиотеки:\n - N3.js для парсинга RDF (замена Redland Raptor)\n - Viz.js для рендеринга графов (замена Graphviz)\n\n Сервис работает полностью на стороне клиента и может быть размещен на GitHub Pages\n -->\n\n <!-- Стили для интерфейса -->\n <link rel=\"stylesheet\" href=\"styles.css\">"
[2026-01-26T19:13:21.541Z] [INFO] },
[2026-01-26T19:13:21.541Z] [INFO] "time": {
[2026-01-26T19:13:21.541Z] [INFO] "start": 1769454801524,
[2026-01-26T19:13:21.541Z] [INFO] "end": 1769454801529
[2026-01-26T19:13:21.542Z] [INFO] }
[2026-01-26T19:13:21.542Z] [INFO] }
[2026-01-26T19:13:21.542Z] [INFO] }
[2026-01-26T19:13:21.542Z] [INFO] }
[2026-01-26T19:13:21.569Z] [INFO] {
[2026-01-26T19:13:21.570Z] [INFO] "type": "text",
[2026-01-26T19:13:21.570Z] [INFO] "timestamp": 1769454801569,
[2026-01-26T19:13:21.570Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:21.570Z] [INFO] "part": {
[2026-01-26T19:13:21.571Z] [INFO] "id": "prt_bfbb98f30001qUpeW8xLfZFVdY",
[2026-01-26T19:13:21.571Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:21.571Z] [INFO] "messageID": "msg_bfbb984bb0014qxmcQQmzb9oB8",
[2026-01-26T19:13:21.571Z] [INFO] "type": "text",
[2026-01-26T19:13:21.571Z] [INFO] "text": "\nLet me examine the ver8a folder which is mentioned as the working folder:",
[2026-01-26T19:13:21.571Z] [INFO] "time": {
[2026-01-26T19:13:21.572Z] [INFO] "start": 1769454801568,
[2026-01-26T19:13:21.572Z] [INFO] "end": 1769454801568
[2026-01-26T19:13:21.572Z] [INFO] }
[2026-01-26T19:13:21.572Z] [INFO] }
[2026-01-26T19:13:21.572Z] [INFO] }
[2026-01-26T19:13:21.577Z] [INFO] {
[2026-01-26T19:13:21.577Z] [INFO] "type": "step_finish",
[2026-01-26T19:13:21.578Z] [INFO] "timestamp": 1769454801576,
[2026-01-26T19:13:21.578Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:21.578Z] [INFO] "part": {
[2026-01-26T19:13:21.578Z] [INFO] "id": "prt_bfbb992a1001hNS8YO7ewAOszG",
[2026-01-26T19:13:21.578Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:21.578Z] [INFO] "messageID": "msg_bfbb984bb0014qxmcQQmzb9oB8",
[2026-01-26T19:13:21.579Z] [INFO] "type": "step-finish",
[2026-01-26T19:13:21.579Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:13:21.579Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08",
[2026-01-26T19:13:21.580Z] [INFO] "cost": 0,
[2026-01-26T19:13:21.580Z] [INFO] "tokens": {
[2026-01-26T19:13:21.580Z] [INFO] "input": 767,
[2026-01-26T19:13:21.580Z] [INFO] "output": 53,
[2026-01-26T19:13:21.580Z] [INFO] "reasoning": 1,
[2026-01-26T19:13:21.580Z] [INFO] "cache": {
[2026-01-26T19:13:21.580Z] [INFO] "read": 14744,
[2026-01-26T19:13:21.580Z] [INFO] "write": 0
[2026-01-26T19:13:21.580Z] [INFO] }
[2026-01-26T19:13:21.581Z] [INFO] }
[2026-01-26T19:13:21.581Z] [INFO] }
[2026-01-26T19:13:21.581Z] [INFO] }
[2026-01-26T19:13:26.539Z] [INFO] {
[2026-01-26T19:13:26.540Z] [INFO] "type": "step_start",
[2026-01-26T19:13:26.540Z] [INFO] "timestamp": 1769454806539,
[2026-01-26T19:13:26.540Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:26.541Z] [INFO] "part": {
[2026-01-26T19:13:26.541Z] [INFO] "id": "prt_bfbb9a60a001u2RzJKG5tcl76v",
[2026-01-26T19:13:26.541Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:26.541Z] [INFO] "messageID": "msg_bfbb992b3001iiBGzWoEtam0zJ",
[2026-01-26T19:13:26.541Z] [INFO] "type": "step-start",
[2026-01-26T19:13:26.541Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08"
[2026-01-26T19:13:26.541Z] [INFO] }
[2026-01-26T19:13:26.542Z] [INFO] }
[2026-01-26T19:13:27.631Z] [INFO] {
[2026-01-26T19:13:27.632Z] [INFO] "type": "tool_use",
[2026-01-26T19:13:27.633Z] [INFO] "timestamp": 1769454807630,
[2026-01-26T19:13:27.633Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:27.633Z] [INFO] "part": {
[2026-01-26T19:13:27.633Z] [INFO] "id": "prt_bfbb9aa48001Qs5IpAhYiOtub8",
[2026-01-26T19:13:27.633Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:27.633Z] [INFO] "messageID": "msg_bfbb992b3001iiBGzWoEtam0zJ",
[2026-01-26T19:13:27.634Z] [INFO] "type": "tool",
[2026-01-26T19:13:27.634Z] [INFO] "callID": "call_3a90771871d3475389879f8f",
[2026-01-26T19:13:27.634Z] [INFO] "tool": "read",
[2026-01-26T19:13:27.635Z] [INFO] "state": {
[2026-01-26T19:13:27.635Z] [INFO] "status": "completed",
[2026-01-26T19:13:27.635Z] [INFO] "input": {
[2026-01-26T19:13:27.635Z] [INFO] "filePath": "/tmp/gh-issue-solver-1769454766276/ver8a/index.html",
[2026-01-26T19:13:27.636Z] [INFO] "limit": 2000,
[2026-01-26T19:13:27.636Z] [INFO] "offset": 2000
[2026-01-26T19:13:27.636Z] [INFO] },
[2026-01-26T19:13:27.636Z] [INFO] "output": "<file>\n02001| if (list && toggle) {\n02002| if (list.style.display === 'none') {\n02003| list.style.display = 'block';\n02004| toggle.textContent = '▼';\n02005| } else {\n02006| list.style.display = 'none';\n02007| toggle.textContent = '▶';\n02008| }\n02009| }\n02010| }\n02011| \n02012| /**\n02013| * Обработчик выбора процесса в дереве\n02014| * @param {string} processUri - URI выбранного процесса\n02015| * @param {string} trigUri - URI TriG, содержащего процесс\n02016| */\n02017| function selectProcess(processUri, trigUri) {\n02018| // Если выбранный процесс из другого TriG, сначала переключаемся на этот TriG\n02019| if (selectedTrigUri !== trigUri) {\n02020| selectTriG(trigUri);\n02021| }\n02022| \n02023| // Подсвечиваем процесс на диаграмме\n02024| highlightProcessOnDiagram(processUri);\n02025| }\n02026| \n02027| /**\n02028| * Подсвечивает процесс на диаграмме\n02029| * @param {string} processUri - URI процесса для подсветки\n02030| */\n02031| function highlightProcessOnDiagram(processUri) {\n02032| // Снимаем предыдущее выделение\n02033| const previouslySelected = document.querySelectorAll('#vad-trig-output .node.process-highlighted');\n02034| previouslySelected.forEach(node => {\n02035| node.classList.remove('process-highlighted');\n02036| });\n02037| \n02038| // Находим узел процесса по его ID (используем nodeLabelToUri для поиска)\n02039| const prefixedUri = getPrefixedName(processUri, currentPrefixes);\n02040| const nodeId = generateVadNodeId(processUri, currentPrefixes);\n02041| \n02042| // Ищем узел SVG по ID или по тексту\n02043| const output = document.getElementById('vad-trig-output');\n02044| if (!output) return;\n02045| \n02046| const nodes = output.querySelectorAll('.node');\n02047| nodes.forEach(node => {\n02048| const titleElement = node.querySelector('title');\n02049| if (titleElement) {\n02050| const nodeTitle = titleElement.textContent.trim();\n02051| // Проверяем, совпадает ли ID узла или его метка с URI процесса\n02052| if (nodeTitle === nodeId || nodeLabelToUri[nodeTitle] === processUri) {\n02053| node.classList.add('process-highlighted');\n02054| // Прокручиваем к выбранному элементу\n02055| node.scrollIntoView({ behavior: 'smooth', block: 'center' });\n02056| }\n02057| }\n02058| });\n02059| \n02060| // Также выделяем элемент в дереве\n02061| const processItems = document.querySelectorAll('.process-item');\n02062| processItems.forEach(item => {\n02063| if (item.getAttribute('data-process-uri') === processUri) {\n02064| item.classList.add('process-selected');\n02065| } else {\n02066| item.classList.remove('process-selected');\n02067| }\n02068| });\n02069| }\n02070| \n02071| /**\n02072| * Отображает дерево TriG\n02073| * @param {Object} hierarchy - Иерархия TriG\n02074| * @param {string} rootUri - URI корневого TriG\n02075| * @param {Object} prefixes - Словарь префиксов\n02076| */\n02077| function displayTriGTree(hierarchy, rootUris, prefixes) {\n02078| const treeContent = document.getElementById('trig-tree-content');\n02079| if (!treeContent) return;\n02080| \n02081| if (!rootUris || rootUris.length === 0) {\n02082| treeContent.innerHTML = '<div class=\"trig-properties-empty\">Нет доступных TriG графов</div>';\n02083| return;\n02084| }\n02085| \n02086| let html = '';\n02087| rootUris.forEach(rootUri => {\n02088| if (hierarchy[rootUri]) {\n02089| html += buildTriGTreeHtml(rootUri, hierarchy, prefixes, 0);\n02090| }\n02091| });\n02092| treeContent.innerHTML = html;\n02093| }\n02094| \n02095| /**\n02096| * Отображает свойства выбранного TriG\n02097| * @param {string} trigUri - URI TriG\n02098| * @param {Object} hierarchy - Иерархия TriG\n02099| * @param {Object} prefixes - Словарь префиксов\n02100| */\n02101| function displayTriGProperties(trigUri, hierarchy, prefixes) {\n02102| const propertiesContent = document.getElementById('trig-properties-content');\n02103| if (!propertiesContent) return;\n02104| \n02105| const graphInfo = hierarchy[trigUri];\n02106| if (!graphInfo) {\n02107| propertiesContent.innerHTML = '<div class=\"trig-properties-empty\">Выберите TriG в дереве</div>';\n02108| return;\n02109| }\n02110| \n02111| let html = '';\n02112| \n02113| // Основные свойства TriG\n02114| const prefixedUri = getPrefixedName(trigUri, prefixes);\n02115| \n02116| // URI with copy button\n02117| html += `<div class=\"trig-property-item\">`;\n02118| html += `<div class=\"trig-property-predicate\">URI</div>`;\n02119| html += `<div class=\"trig-property-value-container\">`;\n02120| html += `<div class=\"trig-property-value uri\">${escapeHtml(prefixedUri)}</div>`;\n02121| html += `<button class=\"copy-id-btn\" onclick=\"copyObjectId('${escapeHtml(trigUri)}', this)\">Копировать</button>`;\n02122| html += `</div>`;\n02123| html += `</div>`;\n02124| \n02125| // Label\n02126| if (graphInfo.label) {\n02127| html += `<div class=\"trig-property-item\">`;\n02128| html += `<div class=\"trig-property-predicate\">rdfs:label</div>`;\n02129| html += `<div class=\"trig-property-value literal\">\"${escapeHtml(graphInfo.label)}\"</div>`;\n02130| html += `</div>`;\n02131| }\n02132| \n02133| // hasParent\n02134| if (graphInfo.hasParent) {\n02135| const parentLabel = getPrefixedName(graphInfo.hasParent, prefixes);\n02136| html += `<div class=\"trig-property-item\">`;\n02137| html += `<div class=\"trig-property-predicate\">vad:hasParentTrig</div>`;\n02138| html += `<div class=\"trig-property-value uri\">${escapeHtml(parentLabel)}</div>`;\n02139| html += `</div>`;\n02140| }\n02141| \n02142| // Количество процессов\n02143| html += `<div class=\"trig-property-item\">`;\n02144| html += `<div class=\"trig-property-predicate\">Процессы (vad:TypeProcess)</div>`;\n02145| html += `<div class=\"trig-property-value\">${graphInfo.processes.length} шт.</div>`;\n02146| html += `</div>`;\n02147| \n02148| // Количество дочерних TriG\n02149| if (graphInfo.children.length > 0) {\n02150| html += `<div class=\"trig-property-item\">`;\n02151| html += `<div class=\"trig-property-predicate\">Дочерние TriG</div>`;\n02152| html += `<div class=\"trig-property-value\">${graphInfo.children.length} шт.</div>`;\n02153| html += `</div>`;\n02154| }\n02155| \n02156| // Количество триплетов\n02157| html += `<div class=\"trig-property-item\">`;\n02158| html += `<div class=\"trig-property-predicate\">Триплеты</div>`;\n02159| html += `<div class=\"trig-property-value\">${graphInfo.quads.length} шт.</div>`;\n02160| html += `</div>`;\n02161| \n02162| propertiesContent.innerHTML = html;\n02163| }\n02164| \n02165| /**\n02166| * Обработчик выбора TriG в дереве\n02167| * @param {string} trigUri - URI выбранного TriG\n02168| */\n02169| function selectTriG(trigUri) {\n02170| selectedTrigUri = trigUri;\n02171| \n02172| // Обновляем выделение в дереве\n02173| const treeItems = document.querySelectorAll('.trig-tree-item');\n02174| treeItems.forEach(item => {\n02175| if (item.getAttribute('data-trig-uri') === trigUri) {\n02176| item.classList.add('selected', 'active');\n02177| } else {\n02178| item.classList.remove('selected', 'active');\n02179| }\n02180| });\n02181| \n02182| // Отображаем свойства выбранного TriG\n02183| displayTriGProperties(trigUri, trigHierarchy, currentPrefixes);\n02184| \n02185| // Перевизуализируем граф для выбранного TriG\n02186| revisualizeTrigVAD(trigUri);\n02187| \n02188| // Обновляем SPARQL запрос для выбранного TriG\n02189| updateSparqlQueryForTriG();\n02190| }\n02191| \n02192| /**\n02193| * Перевизуализирует VAD для выбранного TriG\n02194| * @param {string} trigUri - URI TriG для отображения\n02195| */\n02196| async function revisualizeTrigVAD(trigUri) {\n02197| const graphInfo = trigHierarchy[trigUri];\n02198| if (!graphInfo) return;\n02199| \n02200| const layoutEngine = document.getElementById('layout-engine').value;\n02201| \n02202| try {\n02203| // Используем квады только из выбранного графа\n02204| const filteredQuads = graphInfo.quads.filter(quad => {\n02205| const predicateUri = quad.predicate.value;\n02206| const predicateLabel = getPrefixedName(predicateUri, currentPrefixes);\n02207| return !isPredicateHidden(predicateUri, predicateLabel);\n02208| });\n02209| \n02210| // Генерируем DOT-код в режиме VAD\n02211| const originalMode = currentMode;\n02212| currentMode = 'vad'; // Используем логику VAD для рендеринга\n02213| \n02214| // Временно заменяем currentQuads на квады выбранного графа\n02215| const originalQuads = currentQuads;\n02216| currentQuads = graphInfo.quads;\n02217| \n02218| const dotCode = rdfToDot(filteredQuads, currentPrefixes);\n02219| currentDotCode = dotCode;\n02220| console.log('VAD TriG - Сгенерированный DOT-код:', dotCode);\n02221| \n02222| // Восстанавливаем\n02223| currentQuads = originalQuads;\n02224| currentMode = originalMode;\n02225| \n02226| const viz = await Viz.instance();\n02227| const svgString = viz.renderString(dotCode, { format: 'svg', engine: layoutEngine });\n02228| \n02229| // В режиме VAD TriG используем специальный контейнер vad-trig-output\n02230| const output = document.getElementById('vad-trig-output');\n02231| output.innerHTML = svgString;\n02232| currentSvgElement = output.querySelector('svg');\n02233| document.getElementById('vad-trig-zoom-controls').style.display = 'flex';\n02234| addNodeClickHandlers();\n02235| \n02236| } catch (error) {\n02237| console.error('Ошибка при перевизуализации VAD TriG:', error);\n02238| }\n02239| }\n02240| \n02241| /**\n02242| * Показывает или скрывает панели VAD TriG режима\n02243| * @param {boolean} show - Показать или скрыть\n02244| */\n02245| function toggleVADTriGPanels(show) {\n02246| const vadTrigContainer = document.getElementById('vad-trig-container');\n02247| const regularZoomContainer = document.getElementById('zoom-container');\n02248| const regularZoomControls = document.getElementById('zoom-controls');\n02249| const regularOutput = document.getElementById('output');\n02250| \n02251| if (vadTrigContainer) {\n02252| vadTrigContainer.style.display = show ? 'flex' : 'none';\n02253| }\n02254| \n02255| // Hide regular zoom container and clear output when showing VAD TriG panels\n02256| if (regularZoomContainer) {\n02257| regularZoomContainer.style.display = show ? 'none' : 'block';\n02258| }\n02259| if (regularZoomControls && show) {\n02260| regularZoomControls.style.display = 'none';\n02261| }\n02262| if (regularOutput && show) {\n02263| regularOutput.innerHTML = '';\n02264| }\n02265| }\n02266| \n02267| // ============================================================================\n02268| // ФУНКЦИИ РАБОТЫ СО СТИЛЯМИ\n02269| // ============================================================================\n02270| \n02271| const BaseStyles = {\n02272| literal: 'shape=\"box\" style=\"filled\" fillcolor=\"#ffffcc\"',\n02273| blankNode: 'shape=\"ellipse\" style=\"filled\" fillcolor=\"#e0e0e0\"',\n02274| uri: 'shape=\"ellipse\" style=\"filled\" fillcolor=\"#cce5ff\"',\n02275| edge: ''\n02276| };\n02277| \n02278| function getNodeStyle(nodeUri, isLiteral, isBlankNode) {\n02279| if (currentMode === 'base') {\n02280| if (isLiteral) return BaseStyles.literal;\n02281| if (isBlankNode) return BaseStyles.blankNode;\n02282| return BaseStyles.uri;\n02283| }\n02284| \n02285| if (currentMode === 'aggregation') {\n02286| if (isBlankNode) return AggregationNodeStyles['BlankNodeStyle'].dot;\n02287| const nodeTypes = nodeTypesCache[nodeUri] || [];\n02288| for (const [styleName, styleConfig] of Object.entries(AggregationNodeStyles)) {\n02289| if (styleName === 'default') continue;\n02290| for (const type of styleConfig.types) {\n02291| if (type.startsWith('_')) continue;\n02292| if (nodeTypes.includes(type)) return styleConfig.dot;\n02293| }\n02294| }\n02295| return AggregationNodeStyles['default'].dot;\n02296| }\n02297| \n02298| if (currentMode === 'vad' || currentMode === 'vad-trig') {\n02299| if (isBlankNode) return VADNodeStyles['default'].dot;\n02300| const nodeTypes = nodeTypesCache[nodeUri] || [];\n02301| const nodeSubtypes = nodeSubtypesCache[nodeUri] || [];\n02302| \n02303| // First, check styles that have subtypes defined (ProcessStyleBasic, ProcessStyleDetailed)\n02304| for (const [styleName, styleConfig] of Object.entries(VADNodeStyles)) {\n02305| if (styleName === 'default') continue;\n02306| if (!styleConfig.subtypes) continue; // Skip styles without subtypes\n02307| \n02308| // Check if node has matching type\n02309| const hasMatchingType = styleConfig.types.some(type => nodeTypes.includes(type));\n02310| if (!hasMatchingType) continue;\n02311| \n02312| // Check if node has matching subtype\n02313| const hasMatchingSubtype = styleConfig.subtypes.some(subtype => nodeSubtypes.includes(subtype));\n02314| if (hasMatchingSubtype) return styleConfig.dot;\n02315| }\n02316| \n02317| // Then, check styles without subtypes (ExecutorGroupStyle, ExecutorStyle, etc.)\n02318| for (const [styleName, styleConfig] of Object.entries(VADNodeStyles)) {\n02319| if (styleName === 'default') continue;\n02320| if (styleConfig.subtypes) continue; // Skip styles with subtypes (already checked)\n02321| \n02322| for (const type of styleConfig.types) {\n02323| if (nodeTypes.includes(type)) return styleConfig.dot;\n02324| }\n02325| }\n02326| \n02327| // For Process nodes without explicit subtype, default to ProcessStyleBasic\n02328| const isProcess = nodeTypes.some(t =>\n02329| t === 'vad:TypeProcess' || t === 'http://example.org/vad#TypeProcess'\n02330| );\n02331| if (isProcess) {\n02332| return VADNodeStyles['ProcessStyleBasic'].dot;\n02333| }\n02334| \n02335| return VADNodeStyles['default'].dot;\n02336| }\n02337| \n02338| // Режим нотации\n02339| if (isLiteral) return StyleName.nodeStyles['LiteralStyle'].dot;\n02340| if (isBlankNode) return StyleName.nodeStyles['BlankNodeStyle'].dot;\n02341| \n02342| const nodeTypes = nodeTypesCache[nodeUri] || [];\n02343| for (const [styleName, styleConfig] of Object.entries(StyleName.nodeStyles)) {\n02344| if (styleName === 'default') continue;\n02345| for (const type of styleConfig.types) {\n02346| if (type.startsWith('_')) continue;\n02347| if (nodeTypes.includes(type)) return styleConfig.dot;\n02348| }\n02349| }\n02350| return StyleName.nodeStyles['default'].dot;\n02351| }\n02352| \n02353| function getEdgeStyle(predicateUri, predicateLabel) {\n02354| if (currentMode === 'base') return BaseStyles.edge;\n02355| \n02356| if (currentMode === 'vad') {\n02357| for (const [styleName, styleConfig] of Object.entries(VADEdgeStyles)) {\n02358| if (styleName === 'default') continue;\n02359| for (const predicate of styleConfig.predicates) {\n02360| if (predicateUri === predicate || predicateLabel === predicate) {\n02361| return styleConfig.dot;\n02362| }\n02363| }\n02364| }\n02365| return VADEdgeStyles['default'].dot;\n02366| }\n02367| \n02368| // Режим нотации или агрегации\n02369| for (const [styleName, styleConfig] of Object.entries(StyleName.edgeStyles)) {\n02370| if (styleName === 'default') continue;\n02371| for (const predicate of styleConfig.predicates) {\n02372| if (predicateUri === predicate || predicateLabel === predicate) {\n02373| return styleConfig.dot;\n02374| }\n02375| }\n02376| }\n02377| return StyleName.edgeStyles['default'].dot;\n02378| }\n02379| \n02380| function buildNodeTypesCache(quads, prefixes) {\n02381| nodeTypesCache = {};\n02382| nodeSubtypesCache = {};\n02383| const typePredicates = [\n02384| 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',\n02385| 'rdf:type',\n02386| 'a'\n02387| ];\n02388| const subtypePredicates = [\n02389| 'http://example.org/vad#processSubtype',\n02390| 'vad:processSubtype'\n02391| ];\n02392| \n02393| // Функция для обработки квадов и наполнения кешей\n02394| function processQuads(quadsToProcess) {\n02395| quadsToProcess.forEach(quad => {\n02396| const predicateValue = quad.predicate.value;\n02397| const predicateLabel = getPrefixedName(predicateValue, prefixes);\n02398| \n02399| // Build types cache\n02400| if (typePredicates.includes(predicateValue) ||\n02401| typePredicates.includes(predicateLabel) ||\n02402| predicateLabel === 'a') {\n02403| \n02404| const subjectUri = quad.subject.value;\n02405| const typeUri = quad.object.value;\n02406| const typeLabel = getPrefixedName(typeUri, prefixes);\n02407| \n02408| if (!nodeTypesCache[subjectUri]) {\n02409| nodeTypesCache[subjectUri] = [];\n02410| }\n02411| \n02412| if (!nodeTypesCache[subjectUri].includes(typeUri)) {\n02413| nodeTypesCache[subjectUri].push(typeUri);\n02414| }\n02415| if (!nodeTypesCache[subjectUri].includes(typeLabel)) {\n02416| nodeTypesCache[subjectUri].push(typeLabel);\n02417| }\n02418| }\n02419| \n02420| // Build subtypes cache for vad:processSubtype\n02421| if (subtypePredicates.includes(predicateValue) ||\n02422| subtypePredicates.includes(predicateLabel)) {\n02423| \n02424| const subjectUri = quad.subject.value;\n02425| const subtypeUri = quad.object.value;\n02426| const subtypeLabel = getPrefixedName(subtypeUri, prefixes);\n02427| \n02428| if (!nodeSubtypesCache[subjectUri]) {\n02429| nodeSubtypesCache[subjectUri] = [];\n02430| }\n02431| \n02432| if (!nodeSubtypesCache[subjectUri].includes(subtypeUri)) {\n02433| nodeSubtypesCache[subjectUri].push(subtypeUri);\n02434| }\n02435| if (!nodeSubtypesCache[subjectUri].includes(subtypeLabel)) {\n02436| nodeSubtypesCache[subjectUri].push(subtypeLabel);\n02437| }\n02438| }\n02439| });\n02440| }\n02441| \n02442| // Обрабатываем переданные квады\n02443| processQuads(quads);\n02444| \n02445| // Дополнительно: в режиме VAD TriG также включаем типы из vad:ptree\n02446| // чтобы rdf:type vad:TypeProcess были доступны для всех TriG графов\n02447| if (trigHierarchy && trigHierarchy[PTREE_GRAPH_URI]) {\n02448| const ptreeQuads = trigHierarchy[PTREE_GRAPH_URI].quads;\n02449| processQuads(ptreeQuads);\n02450| }\n02451| }\n02452| \n02453| // ============================================================================\n02454| // ФУНКЦИИ МАСШТАБИРОВАНИЯ\n02455| // ============================================================================\n02456| \n02457| function applyZoom() {\n02458| // Применяем масштаб к обоим контейнерам (обычный и VAD TriG)\n02459| const zoomContent = document.getElementById('zoom-content');\n02460| const zoomLevel = document.getElementById('zoom-level');\n02461| const vadTrigZoomContent = document.getElementById('vad-trig-zoom-content');\n02462| const vadTrigZoomLevel = document.getElementById('vad-trig-zoom-level');\n02463| \n02464| if (zoomContent) zoomContent.style.transform = `scale(${currentScale})`;\n02465| if (zoomLevel) zoomLevel.textContent = Math.round(currentScale * 100) + '%';\n02466| if (vadTrigZoomContent) vadTrigZoomContent.style.transform = `scale(${currentScale})`;\n02467| if (vadTrigZoomLevel) vadTrigZoomLevel.textContent = Math.round(currentScale * 100) + '%';\n02468| }\n02469| \n02470| function zoomIn() {\n02471| if (currentScale < 3.0) { currentScale += 0.1; applyZoom(); }\n02472| }\n02473| \n02474| function zoomOut() {\n02475| if (currentScale > 0.1) { currentScale -= 0.1; applyZoom(); }\n02476| }\n02477| \n02478| function zoomReset() {\n02479| currentScale = 1.0;\n02480| applyZoom();\n02481| }\n02482| \n02483| function zoomFit() {\n02484| // Определяем, какой контейнер сейчас активен\n02485| const vadTrigContainer = document.getElementById('vad-trig-container');\n02486| const isVadTrigMode = vadTrigContainer && vadTrigContainer.style.display !== 'none';\n02487| \n02488| let zoomContainer, output;\n02489| if (isVadTrigMode) {\n02490| zoomContainer = document.getElementById('vad-trig-zoom-container');\n02491| output = document.getElementById('vad-trig-output');\n02492| } else {\n02493| zoomContainer = document.getElementById('zoom-container');\n02494| output = document.getElementById('output');\n02495| }\n02496| \n02497| const svg = output ? output.querySelector('svg') : null;\n02498| if (!zoomContainer || !svg) return;\n02499| \n02500| const containerWidth = zoomContainer.clientWidth - 20;\n02501| const containerHeight = zoomContainer.clientHeight - 20;\n02502| \n02503| let svgWidth = parseFloat(svg.getAttribute('width')) || svg.getBoundingClientRect().width;\n02504| let svgHeight = parseFloat(svg.getAttribute('height')) || svg.getBoundingClientRect().height;\n02505| \n02506| const widthStr = svg.getAttribute('width') || '';\n02507| const heightStr = svg.getAttribute('height') || '';\n02508| if (widthStr.includes('pt')) svgWidth = parseFloat(widthStr) * 1.33;\n02509| if (heightStr.includes('pt')) svgHeight = parseFloat(heightStr) * 1.33;\n02510| \n02511| const scaleX = containerWidth / svgWidth;\n02512| const scaleY = containerHeight / svgHeight;\n02513| currentScale = Math.min(scaleX, scaleY, 1.0);\n02514| applyZoom();\n02515| }\n02516| \n02517| // ============================================================================\n02518| // ФУНКЦИИ ПАНЕЛИ СВОЙСТВ УЗЛА\n02519| // ============================================================================\n02520| \n02521| function closePropertiesPanel(panelId) {\n02522| const panel = document.getElementById(panelId);\n02523| if (panel) {\n02524| panel.remove();\n02525| openPropertiesPanels = openPropertiesPanels.filter(p => p.id !== panelId);\n02526| }\n02527| if (selectedNodeElement) {\n02528| selectedNodeElement.classList.remove('selected');\n02529| selectedNodeElement = null;\n02530| }\n02531| }\n02532| \n02533| function closeAllPropertiesPanels() {\n02534| const container = document.getElementById('properties-panels-container');\n02535| if (container) container.innerHTML = '';\n02536| openPropertiesPanels = [];\n02537| if (selectedNodeElement) {\n02538| selectedNodeElement.classList.remove('selected');\n02539| selectedNodeElement = null;\n02540| }\n02541| }\n02542| \n02543| function getNodeProperties(nodeUri) {\n02544| const properties = [];\n02545| currentQuads.forEach(quad => {\n02546| if (quad.subject.value === nodeUri) {\n02547| const predicateLabel = getPrefixedName(quad.predicate.value, currentPrefixes);\n02548| const isLiteral = quad.object.termType === 'Literal';\n02549| const objectLabel = isLiteral\n02550| ? `\"${quad.object.value}\"`\n02551| : getPrefixedName(quad.object.value, currentPrefixes);\n02552| \n02553| properties.push({\n02554| predicate: quad.predicate.value,\n02555| predicateLabel: predicateLabel,\n02556| object: quad.object.value,\n02557| objectLabel: objectLabel,\n02558| isLiteral: isLiteral\n02559| });\n02560| }\n02561| });\n02562| return properties;\n02563| }\n02564| \n02565| function showNodeProperties(nodeUri, nodeLabel) {\n02566| const container = document.getElementById('properties-panels-container');\n02567| if (!container) return;\n02568| \n02569| const existingPanel = openPropertiesPanels.find(p => p.uri === nodeUri);\n02570| if (existingPanel) {\n02571| const panel = document.getElementById(existingPanel.id);\n02572| if (panel) bringPanelToFront(panel);\n02573| return;\n02574| }\n02575| \n02576| propertiesPanelCounter++;\n02577| const panelId = 'properties-panel-' + propertiesPanelCounter;\n02578| \n02579| const offsetMultiplier = openPropertiesPanels.length % 5;\n02580| const rightOffset = 20 + (offsetMultiplier * 30);\n02581| const topOffset = 100 + (offsetMultiplier * 30);\n02582| \n02583| const properties = getNodeProperties(nodeUri);\n02584| \n02585| let propertiesHtml = '';\n02586| if (properties.length === 0) {\n02587| propertiesHtml = '<div class=\"properties-empty\">У этого узла нет свойств</div>';\n02588| } else {\n02589| properties.forEach(prop => {\n02590| propertiesHtml += '<div class=\"property-item\">';\n02591| propertiesHtml += `<div class=\"property-predicate\">${prop.predicateLabel}</div>`;\n02592| propertiesHtml += `<div class=\"property-value ${prop.isLiteral ? 'literal' : 'uri'}\">${prop.objectLabel}</div>`;\n02593| propertiesHtml += '</div>';\n02594| });\n02595| }\n02596| \n02597| const nodeTypes = nodeTypesCache[nodeUri] || [];\n02598| if (nodeTypes.length > 0) {\n02599| const prefixedTypes = nodeTypes.filter(t => t.includes(':') && !t.startsWith('http'));\n02600| if (prefixedTypes.length > 0) {\n02601| propertiesHtml += '<div style=\"margin-top: 15px; padding-top: 10px; border-top: 1px solid #ddd;\">';\n02602| propertiesHtml += '<div style=\"font-size: 12px; color: #666; margin-bottom: 5px;\">Тип узла:</div>';\n02603| prefixedTypes.forEach(type => {\n02604| propertiesHtml += `<span class=\"properties-type-badge\">${type}</span> `;\n02605| });\n02606| propertiesHtml += '</div>';\n02607| }\n02608| }\n02609| \n02610| // Добавляем вычисленные свойства из virtualRDFdata (с разделителем)\n02611| const computedProperties = getComputedPropertiesForNode(nodeUri);\n02612| if (computedProperties.length > 0) {\n02613| propertiesHtml += '<div style=\"margin-top: 15px; padding-top: 10px; border-top: 2px dashed #9C27B0;\">';\n02614| propertiesHtml += '<div style=\"font-size: 12px; color: #9C27B0; margin-bottom: 8px; font-weight: bold;\">Вычисляемые свойства (virtualRDFdata):</div>';\n02615| computedProperties.forEach(prop => {\n02616| propertiesHtml += '<div class=\"property-item\" style=\"background-color: #F3E5F5; border-left: 3px solid #9C27B0;\">';\n02617| propertiesHtml += `<div class=\"property-predicate\" style=\"color: #7B1FA2;\">${prop.predicateLabel}</div>`;\n02618| propertiesHtml += `<div class=\"property-value uri\" style=\"color: #4A148C;\">${prop.valueLabel}</div>`;\n02619| propertiesHtml += '</div>';\n02620| });\n02621| propertiesHtml += '</div>';\n02622| }\n02623| \n02624| const escapedNodeLabel = nodeLabel.replace(/'/g, \"\\\\'\").replace(/\"/g, '&quot;');\n02625| const panelHtml = `\n02626| <div class=\"properties-panel visible\" id=\"${panelId}\" style=\"right: ${rightOffset}px; top: ${topOffset}px;\">\n02627| <div class=\"properties-header\" onmousedown=\"startDragPanel(event, '${panelId}')\">\n02628| <div class=\"properties-header-content\">\n02629| <div class=\"properties-header-title\">Свойства объекта</div>\n02630| <div class=\"properties-header-row\">\n02631| <h3>${nodeLabel}</h3>\n02632| <button class=\"properties-copy-btn\" onclick=\"event.stopPropagation(); copyObjectId('${escapedNodeLabel}', this)\">Копировать</button>\n02633| </div>\n02634| </div>\n02635| <button class=\"properties-close-btn\" onclick=\"closePropertiesPanel('${panelId}')\">&times;</button>\n02636| </div>\n02637| <div class=\"properties-content\">\n02638| ${propertiesHtml}\n02639| </div>\n02640| </div>\n02641| `;\n02642| \n02643| container.insertAdjacentHTML('beforeend', panelHtml);\n02644| openPropertiesPanels.push({ id: panelId, uri: nodeUri, label: nodeLabel });\n02645| \n02646| const newPanel = document.getElementById(panelId);\n02647| if (newPanel) bringPanelToFront(newPanel);\n02648| }\n02649| \n02650| function bringPanelToFront(panel) {\n02651| let maxZIndex = 1000;\n02652| openPropertiesPanels.forEach(p => {\n02653| const el = document.getElementById(p.id);\n02654| if (el) {\n02655| const z = parseInt(el.style.zIndex) || 1000;\n02656| if (z > maxZIndex) maxZIndex = z;\n02657| }\n02658| });\n02659| panel.style.zIndex = maxZIndex + 1;\n02660| }\n02661| \n02662| function startDragPanel(event, panelId) {\n02663| if (event.target.classList.contains('properties-close-btn')) return;\n02664| const panel = document.getElementById(panelId);\n02665| if (!panel) return;\n02666| \n02667| draggedPanel = panel;\n02668| const rect = panel.getBoundingClientRect();\n02669| dragOffsetX = event.clientX - rect.left;\n02670| dragOffsetY = event.clientY - rect.top;\n02671| \n02672| bringPanelToFront(panel);\n02673| document.addEventListener('mousemove', dragPanel);\n02674| document.addEventListener('mouseup', stopDragPanel);\n02675| event.preventDefault();\n02676| }\n02677| \n02678| function dragPanel(event) {\n02679| if (!draggedPanel) return;\n02680| const newLeft = event.clientX - dragOffsetX;\n02681| const newTop = event.clientY - dragOffsetY;\n02682| draggedPanel.style.left = newLeft + 'px';\n02683| draggedPanel.style.top = newTop + 'px';\n02684| draggedPanel.style.right = 'auto';\n02685| }\n02686| \n02687| function stopDragPanel() {\n02688| draggedPanel = null;\n02689| document.removeEventListener('mousemove', dragPanel);\n02690| document.removeEventListener('mouseup', stopDragPanel);\n02691| }\n02692| \n02693| // ============================================================================\n02694| // ФУНКЦИИ ДЛЯ КЛИКОВ ПО УЗЛАМ\n02695| // ============================================================================\n02696| \n02697| function addNodeClickHandlers() {\n02698| // Add click handlers to both regular output and VAD TriG output\n02699| const regularSvg = document.querySelector('#output svg');\n02700| const vadTrigSvg = document.querySelector('#vad-trig-output svg');\n02701| \n02702| const svgElements = [regularSvg, vadTrigSvg].filter(svg => svg !== null);\n02703| \n02704| svgElements.forEach(svg => {\n02705| const nodes = svg.querySelectorAll('.node');\n02706| nodes.forEach(node => {\n02707| node.addEventListener('click', handleNodeClick);\n02708| node.addEventListener('dblclick', handleNodeDoubleClick);\n02709| });\n02710| });\n02711| }\n02712| \n02713| function handleNodeClick(event) {\n02714| const nodeElement = event.currentTarget;\n02715| const titleElement = nodeElement.querySelector('title');\n02716| if (!titleElement) return;\n02717| \n02718| const dotId = titleElement.textContent;\n02719| \n02720| let nodeUri = null;\n02721| let nodeLabel = null;\n02722| \n02723| for (const [label, info] of Object.entries(nodeLabelToUri)) {\n02724| if (info.dotId === dotId) {\n02725| nodeUri = info.uri;\n02726| nodeLabel = label;\n02727| break;\n02728| }\n02729| }\n02730| \n02731| if (!nodeUri) return;\n02732| \n02733| // НЕ снимаем выделение с элемента TriG-дерева при клике на узел диаграммы.\n02734| // Выделенный элемент TriG дерева всегда должен отражать, чья схема отображается на диаграмме.\n02735| // Снимаем только выделение с процессов в списке \"Состав объектов\" и подсветку на диаграмме.\n02736| const processItems = document.querySelectorAll('.process-item.process-selected');\n02737| processItems.forEach(item => {\n02738| item.classList.remove('process-selected');\n02739| });\n02740| const highlightedProcesses = document.querySelectorAll('.node.process-highlighted');\n02741| highlightedProcesses.forEach(node => {\n02742| node.classList.remove('process-highlighted');\n02743| });\n02744| \n02745| if (selectedNodeElement) {\n02746| selectedNodeElement.classList.remove('selected');\n02747| }\n02748| nodeElement.classList.add('selected');\n02749| selectedNodeElement = nodeElement;\n02750| \n02751| showNodeProperties(nodeUri, nodeLabel);\n02752| }\n02753| \n02754| /**\n02755| * Обработчик двойного клика по узлу диаграммы\n02756| * Для процессов с подтипом \"Детализированный\" (vad:Detailed) открывает соответствующую схему TriG\n02757| * @param {Event} event - Событие двойного клика\n02758| */\n02759| function handleNodeDoubleClick(event) {\n02760| event.preventDefault();\n02761| event.stopPropagation();\n02762| \n02763| const nodeElement = event.currentTarget;\n02764| const titleElement = nodeElement.querySelector('title');\n02765| if (!titleElement) return;\n02766| \n02767| const dotId = titleElement.textContent;\n02768| \n02769| let nodeUri = null;\n02770| let nodeLabel = null;\n02771| \n02772| for (const [label, info] of Object.entries(nodeLabelToUri)) {\n02773| if (info.dotId === dotId) {\n02774| nodeUri = info.uri;\n02775| nodeLabel = label;\n02776| break;\n02777| }\n02778| }\n02779| \n02780| if (!nodeUri) return;\n02781| \n02782| // Ищем свойство vad:hasTrig для данного узла\n02783| const hasTrigPredicate = 'http://example.org/vad#hasTrig';\n02784| let targetTrigUri = null;\n02785| \n02786| // Сначала проверяем в vad:ptree (там хранятся метаданные процессов, включая hasTrig)\n02787| if (trigHierarchy && trigHierarchy[PTREE_GRAPH_URI]) {\n02788| const ptreeQuads = trigHierarchy[PTREE_GRAPH_URI].quads;\n02789| for (const quad of ptreeQuads) {\n02790| if (quad.subject.value === nodeUri && quad.predicate.value === hasTrigPredicate) {\n02791| targetTrigUri = quad.object.value;\n02792| break;\n02793| }\n02794| }\n02795| }\n02796| \n02797| // Если не найдено в ptree, проверяем в текущем графе или во всех графах\n02798| if (!targetTrigUri) {\n02799| const quadsToCheck = selectedTrigUri && trigHierarchy[selectedTrigUri]\n02800| ? trigHierarchy[selectedTrigUri].quads\n02801| : currentQuads;\n02802| \n02803| for (const quad of quadsToCheck) {\n02804| if (quad.subject.value === nodeUri && quad.predicate.value === hasTrigPredicate) {\n02805| targetTrigUri = quad.object.value;\n02806| break;\n02807| }\n02808| }\n02809| }\n02810| \n02811| // Если найден связанный TriG, открываем его\n02812| if (targetTrigUri && trigHierarchy[targetTrigUri]) {\n02813| selectTriG(targetTrigUri);\n02814| }\n02815| }\n02816| \n02817| /**\n02818| * Снимает выделение со всех элементов TriG-дерева\n02819| */\n02820| function clearTriGTreeSelection() {\n02821| // Снимаем выделение с TriG-элементов дерева\n02822| const treeItems = document.querySelectorAll('.trig-tree-item');\n02823| treeItems.forEach(item => {\n02824| item.classList.remove('selected', 'active');\n02825| });\n02826| \n02827| // Снимаем выделение с процессов в дереве\n02828| const processItems = document.querySelectorAll('.process-item.process-selected');\n02829| processItems.forEach(item => {\n02830| item.classList.remove('process-selected');\n02831| });\n02832| \n02833| // Снимаем подсветку процесса на диаграмме\n02834| const highlightedProcesses = document.querySelectorAll('.node.process-highlighted');\n02835| highlightedProcesses.forEach(node => {\n02836| node.classList.remove('process-highlighted');\n02837| });\n02838| }\n02839| \n02840| // ============================================================================\n02841| // ФУНКЦИИ ФИЛЬТРОВ\n02842| // ============================================================================\n02843| \n02844| function isPredicateHidden(predicateUri, predicateLabel) {\n02845| return activeFilters.includes(predicateUri) || activeFilters.includes(predicateLabel);\n02846| }\n02847| \n02848| function displayFilters() {\n02849| // filter-panel removed in minimization - function kept for compatibility\n02850| return;\n02851| \n02852| allPredicates = [];\n02853| const predicateSet = new Set();\n02854| \n02855| currentQuads.forEach(quad => {\n02856| const predicateUri = quad.predicate.value;\n02857| const predicateLabel = getPrefixedName(predicateUri, currentPrefixes);\n02858| if (!predicateSet.has(predicateLabel)) {\n02859| predicateSet.add(predicateLabel);\n02860| allPredicates.push({ uri: predicateUri, label: predicateLabel });\n02861| }\n02862| });\n02863| \n02864| if (allPredicates.length === 0) {\n02865| filterPanel.style.display = 'none';\n02866| return;\n02867| }\n02868| \n02869| allPredicates.sort((a, b) => a.label.localeCompare(b.label));\n02870| \n02871| let html = '';\n02872| allPredicates.forEach(pred => {\n02873| const isHidden = isPredicateHidden(pred.uri, pred.label);\n02874| const checkboxId = 'filter-' + pred.label.replace(/[^a-zA-Z0-9]/g, '_');\n02875| \n02876| html += `<div class=\"filter-item\">`;\n02877| html += `<input type=\"checkbox\" id=\"${checkboxId}\" ${!isHidden ? 'checked' : ''} onchange=\"togglePredicateFilter('${pred.uri}', '${pred.label}', this.checked)\">`;\n02878| html += `<label for=\"${checkboxId}\">${pred.label}</label>`;\n02879| html += `</div>`;\n02880| });\n02881| \n02882| filterContent.innerHTML = html;\n02883| filterPanel.style.display = 'block';\n02884| }\n02885| \n02886| function togglePredicateFilter(predicateUri, predicateLabel, isVisible) {\n02887| if (isVisible) {\n02888| activeFilters = activeFilters.filter(f => f !== predicateUri && f !== predicateLabel);\n02889| } else {\n02890| if (!activeFilters.includes(predicateUri)) {\n02891| activeFilters.push(predicateUri);\n02892| }\n02893| if (!activeFilters.includes(predicateLabel)) {\n02894| activeFilters.push(predicateLabel);\n02895| }\n02896| }\n02897| revisualize();\n02898| }\n02899| \n02900| function selectAllFilters() {\n02901| activeFilters = [];\n02902| displayFilters();\n02903| revisualize();\n02904| }\n02905| \n02906| function deselectAllFilters() {\n02907| activeFilters = allPredicates.flatMap(p => [p.uri, p.label]);\n02908| displayFilters();\n02909| revisualize();\n02910| }\n02911| \n02912| async function revisualize() {\n02913| const layoutEngine = document.getElementById('layout-engine').value;\n02914| \n02915| try {\n02916| const filteredQuads = currentQuads.filter(quad => {\n02917| const predicateUri = quad.predicate.value;\n02918| const predicateLabel = getPrefixedName(predicateUri, currentPrefixes);\n02919| return !isPredicateHidden(predicateUri, predicateLabel);\n02920| });\n02921| \n02922| const dotCode = rdfToDot(filteredQuads, currentPrefixes);\n02923| currentDotCode = dotCode;\n02924| console.log('Пересгенерированный DOT-код:', dotCode);\n02925| \n02926| const viz = await Viz.instance();\n02927| const svgString = viz.renderString(dotCode, { format: 'svg', engine: layoutEngine });\n02928| \n02929| const output = document.getElementById('output');\n02930| output.innerHTML = svgString;\n02931| currentSvgElement = output.querySelector('svg');\n02932| addNodeClickHandlers();\n02933| \n02934| } catch (error) {\n02935| console.error('Ошибка при перевизуализации:', error);\n02936| }\n02937| }\n02938| \n02939| // ============================================================================\n02940| // ФУНКЦИИ ЗАГРУЗКИ ПРИМЕРОВ\n02941| // ============================================================================\n02942| \n02943| /**\n02944| * Универсальная функция загрузки примера из файла.\n02945| * Пытается загрузить файл через fetch. При ошибке CORS (локальный файл)\n02946| * показывает информационное сообщение и использует встроенные данные как fallback.\n02947| * @param {string} filename - Имя файла для загрузки\n02948| * @param {string} exampleName - Человекочитаемое имя примера\n02949| * @param {string} inputFormat - Формат ввода (turtle, n-triples, n-quads, trig)\n02950| * @param {string} visualizationMode - Режим визуализации (notation, vad, vad-trig)\n02951| * @param {string} fallbackDataKey - Ключ для доступа к встроенным данным EXAMPLE_DATA\n02952| */\n02953| async function loadExampleFromFile(filename, exampleName, inputFormat, visualizationMode, fallbackDataKey) {\n02954| const statusEl = document.getElementById('example-status');\n02955| \n02956| statusEl.textContent = `Загрузка примера ${exampleName}...`;\n02957| statusEl.style.display = 'block';\n02958| statusEl.style.backgroundColor = '#fff3cd';\n02959| statusEl.style.borderColor = '#ffc107';\n02960| statusEl.style.color = '#856404';\n02961| \n02962| try {\n02963| const response = await fetch(filename);\n02964| if (!response.ok) {\n02965| throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n02966| }\n02967| const data = await response.text();\n02968| \n02969| document.getElementById('rdf-input').value = data;\n02970| document.getElementById('input-format').value = inputFormat;\n02971| document.getElementById('visualization-mode').value = visualizationMode;\n02972| updateModeDescription();\n02973| \n02974| statusEl.textContent = `Пример ${exampleName} успешно загружен из файла ${filename}`;\n02975| statusEl.style.backgroundColor = '#d4edda';\n02976| statusEl.style.borderColor = '#c3e6cb';\n02977| statusEl.style.color = '#155724';\n02978| } catch (error) {\n02979| console.warn(`Не удалось загрузить файл ${filename}:`, error.message);\n02980| \n02981| // Проверяем, это ошибка CORS (локальный файл) или сетевая ошибка\n02982| const isCorsError = error.message.includes('Failed to fetch') ||\n02983| error.message.includes('NetworkError') ||\n02984| error.message.includes('CORS');\n02985| \n02986| if (isCorsError) {\n02987| // Для локальных файлов показываем информационное сообщение и используем встроенные данные\n02988| statusEl.textContent = `Файл ${filename} недоступен (CORS). Используются встроенные данные.`;\n02989| statusEl.style.backgroundColor = '#fff3cd';\n02990| statusEl.style.borderColor = '#ffc107';\n02991| statusEl.style.color = '#856404';\n02992| } else {\n02993| // Для серверных ошибок показываем ошибку, но всё равно используем fallback\n02994| statusEl.textContent = `Ошибка загрузки ${filename}: ${error.message}. Используются встроенные данные.`;\n02995| statusEl.style.backgroundColor = '#fff3cd';\n02996| statusEl.style.borderColor = '#ffc107';\n02997| statusEl.style.color = '#856404';\n02998| }\n02999| \n03000| // Используем встроенные данные как fallback\n03001| try {\n03002| document.getElementById('rdf-input').value = EXAMPLE_DATA[fallbackDataKey];\n03003| document.getElementById('input-format').value = inputFormat;\n03004| document.getElementById('visualization-mode').value = visualizationMode;\n03005| updateModeDescription();\n03006| } catch (fallbackError) {\n03007| console.error(`Ошибка при использовании встроенных данных:`, fallbackError);\n03008| statusEl.textContent = `Ошибка загрузки примера ${exampleName}: ${fallbackError.message}`;\n03009| statusEl.style.backgroundColor = '#f8d7da';\n03010| statusEl.style.borderColor = '#f5c6cb';\n03011| statusEl.style.color = '#721c24';\n03012| }\n03013| }\n03014| }\n03015| \n03016| /**\n03017| * Загружает пример Turtle\n03018| * Алгоритм загрузки файла:\n03019| * 1. Отображает статус загрузки\n03020| * 2. Выполняет fetch запрос к файлу 'Turtle.ttl' (относительный путь от текущего расположения HTML файла)\n03021| * 3. При успехе: заполняет текстовое поле RDF данными, устанавливает формат ввода и режим визуализации\n03022| * 4. При ошибке CORS (локальный файл): показывает информацию и использует встроенные данные\n03023| * 5. При другой ошибке: отображает подробную информацию об ошибке\n03024| */\n03025| /**\n03026| * Загружает пример TriG VADv4 (Value Added Chain Diagram с иерархией TriG)\n03027| * Демонстрирует:\n03028| * - Иерархию процессов: p1, p1.1, p1.2, p1.1.1, p1.1.2, p2, p2.1, p2.2\n03029| * - Корневые TriG графы: t_p1, t_p2 (hasParentTrig = root)\n03030| * - Дочерний TriG граф: t_p1.1 (hasParentTrig = t_p1)\n03031| * - Вычисляемые подтипы: DetailedChild, notDetailedChild, notDetailedExternal, NotDefinedType\n03032| */\n03033| function loadExampleTrigVADv4() {\n03034| loadExampleFromFile('Trig_VADv4.ttl', 'Trig VADv4', 'trig', 'vad-trig', 'trig-vad-v4');\n03035| }\n03036| \n03037| /**\n03038| * Для обратной совместимости: вызывает загрузку примера Trig VADv4\n03039| */\n03040| function loadExample() {\n03041| loadExampleTrigVADv4();\n03042| }\n03043| \n03044| /**\n03045| * Очищает содержимое поля RDF данных\n03046| */\n03047| function clearRdfInput() {\n03048| document.getElementById('rdf-input').value = '';\n03049| }\n03050| \n03051| /**\n03052| * Сохраняет содержимое RDF поля как файл\n03053| */\n03054| function saveAsFile() {\n03055| const content = document.getElementById('rdf-input').value;\n03056| if (!content.trim()) {\n03057| alert('Нет данных для сохранения');\n03058| return;\n03059| }\n03060| \n03061| const format = document.getElementById('input-format').value;\n03062| let extension = 'ttl';\n03063| if (format === 'n-triples') extension = 'nt';\n03064| else if (format === 'n-quads') extension = 'nq';\n03065| else if (format === 'trig') extension = 'trig';\n03066| \n03067| const blob = new Blob([content], { type: 'text/plain' });\n03068| const url = URL.createObjectURL(blob);\n03069| const a = document.createElement('a');\n03070| a.href = url;\n03071| a.download = `rdf-data.${extension}`;\n03072| document.body.appendChild(a);\n03073| a.click();\n03074| document.body.removeChild(a);\n03075| URL.revokeObjectURL(url);\n03076| }\n03077| \n03078| /**\n03079| * Загружает файл, выбранный пользователем\n03080| */\n03081| function loadFile(event) {\n03082| const file = event.target.files[0];\n03083| if (!file) return;\n03084| \n03085| const reader = new FileReader();\n03086| reader.onload = function(e) {\n03087| document.getElementById('rdf-input').value = e.target.result;\n03088| \n03089| // Определяем формат по расширению\n03090| const extension = file.name.split('.').pop().toLowerCase();\n03091| let format = 'turtle';\n03092| if (extension === 'nt') format = 'n-triples';\n03093| else if (extension === 'nq') format = 'n-quads';\n03094| else if (extension === 'trig') format = 'trig';\n03095| \n03096| document.getElementById('input-format').value = format;\n03097| \n03098| // Обновляем статус загрузки\n03099| const statusEl = document.getElementById('example-status');\n03100| statusEl.textContent = `Файл ${file.name} успешно загружен`;\n03101| statusEl.style.display = 'block';\n03102| statusEl.style.backgroundColor = '#d4edda';\n03103| statusEl.style.borderColor = '#c3e6cb';\n03104| statusEl.style.color = '#155724';\n03105| \n03106| // Сбрасываем значение input для возможности повторной загрузки того же файла\n03107| event.target.value = '';\n03108| };\n03109| reader.readAsText(file);\n03110| }\n03111| \n03112| /**\n03113| * Тестирует валидность RDF данных вручную\n03114| *\n03115| * Выполняет комплексную проверку RDF данных из текстового поля:\n03116| * 1. Проверяет синтаксическую корректность (парсинг через N3.js)\n03117| * 2. Проверяет соответствие правилам VAD онтологии (через validateVAD)\n03118| *\n03119| * Вызывается при нажатии кнопки \"Тест\" в заголовке поля RDF данных.\n03120| * Результаты отображаются через alert() с подробным описанием ошибок.\n03121| *\n03122| * @returns {void}\n03123| *\n03124| * @example\n03125| * // Вызов при нажатии кнопки \"Тест\"\n03126| * <button onclick=\"testRdfValidation()\">Тест</button>\n03127| *\n03128| * @see validateVAD - Функция валидации правил VAD\n03129| * @see formatVADErrors - Форматирование ошибок валидации\n03130| */\n03131| function testRdfValidation() {\n03132| const rdfInput = document.getElementById('rdf-input');\n03133| const inputFormat = document.getElementById('input-format').value;\n03134| \n03135| if (!rdfInput || !rdfInput.value.trim()) {\n03136| alert('Нет данных для проверки. Введите RDF данные в поле выше.');\n03137| return;\n03138| }\n03139| \n03140| const rdfData = rdfInput.value;\n03141| \n03142| // Создаем парсер N3\n03143| const parser = new N3.Parser({ format: inputFormat });\n03144| \n03145| const quads = [];\n03146| const prefixes = {};\n03147| \n03148| try {\n03149| parser.parse(rdfData, (error, quad, prefixesParsed) => {\n03150| if (error) {\n03151| alert(`❌ ОШИБКА СИНТАКСИСА RDF\\n\\n${error.message}\\n\\nПроверьте корректность синтаксиса RDF данных.`);\n03152| return;\n03153| }\n03154| \n03155| if (quad) {\n03156| quads.push(quad);\n03157| } else {\n03158| // Parsing complete\n03159| Object.assign(prefixes, prefixesParsed);\n03160| \n03161| // Выполняем валидацию VAD онтологии\n03162| const validation = validateVAD(quads, prefixes);\n03163| \n03164| // Выполняем валидацию VAD схемы\n03165| const schemaValidation = validateVADSchema(quads, prefixes);\n03166| \n03167| // Формируем итоговое сообщение\n03168|
[2026-01-26T19:13:27.638Z] [INFO] let resultMessage = '';\n03169| \n03170| if (validation.valid && schemaValidation.valid) {\n03171| resultMessage = '✅ ВАЛИДАЦИЯ УСПЕШНА\\n\\n' +\n03172| 'RDF данные полностью соответствуют правилам VAD онтологии и схемы.\\n\\n' +\n03173| `Всего триплетов: ${quads.length}\\n` +\n03174| `Префиксов: ${Object.keys(prefixes).length}`;\n03175| alert(resultMessage);\n03176| } else {\n03177| // Есть ошибки - показываем детали\n03178| if (!validation.valid) {\n03179| resultMessage += formatVADErrors(validation.errors);\n03180| resultMessage += '\\n\\n';\n03181| }\n03182| if (!schemaValidation.valid || schemaValidation.warnings.length > 0) {\n03183| resultMessage += formatVADSchemaValidation(schemaValidation);\n03184| }\n03185| alert(resultMessage);\n03186| }\n03187| }\n03188| });\n03189| } catch (error) {\n03190| alert(`❌ ОШИБКА ПРИ ПРОВЕРКЕ\\n\\n${error.message}\\n\\nПроверьте корректность RDF данных.`);\n03191| }\n03192| }\n03193| \n03194| // ============================================================================\n03195| // ФУНКЦИИ ДЛЯ VIRTUALRDFDATA (вычисляемые RDF данные)\n03196| // ============================================================================\n03197| \n03198| /**\n03199| * Вычисляет processSubtype для всех процессов во всех TriG схемах\n03200| * и сохраняет результаты в virtualRDFdata\n03201| *\n03202| * Алгоритм вычисления vad:processSubtype:\n03203| * - Если процесс имеет vad:hasTrig (детализированный):\n03204| * - DetailedChild: дочерний TriG указывает на текущий (hasParentTrig = currentTrig)\n03205| * - DetailedExternal: дочерний TriG НЕ указывает на текущий\n03206| * - Если процесс имеет vad:hasParentProcess (недетализированный):\n03207| * - notDetailedChild: hasParentProcess совпадает с процессом-владельцем текущей схемы\n03208| * - notDetailedExternal: hasParentProcess НЕ совпадает\n03209| * - NotDefinedType: hasParentProcess = vad:NotDefined\n03210| *\n03211| * @returns {Object} virtualRDFdata - структура с вычисленными данными\n03212| */\n03213| function computeVirtualRDFdata() {\n03214| virtualRDFdata = {};\n03215| \n03216| // Проверяем наличие trigHierarchy\n03217| if (!trigHierarchy || Object.keys(trigHierarchy).length === 0) {\n03218| console.warn('computeVirtualRDFdata: trigHierarchy пуст или не инициализирован');\n03219| return virtualRDFdata;\n03220| }\n03221| \n03222| // Получаем данные из ptree для определения hasTrig и hasParentProcess\n03223| const ptreeQuads = trigHierarchy[PTREE_GRAPH_URI]?.quads || [];\n03224| \n03225| // Создаем карты для быстрого доступа\n03226| const processHasTrig = new Map(); // processUri -> trigUri\n03227| const processHasParentProcess = new Map(); // processUri -> parentProcessUri\n03228| const trigDefinesProcess = new Map(); // trigUri -> processUri\n03229| const trigHasParentTrig = new Map(); // trigUri -> parentTrigUri\n03230| \n03231| // Заполняем карту hasTrig из ptree\n03232| ptreeQuads.forEach(quad => {\n03233| const predicateLabel = getPrefixedName(quad.predicate.value, currentPrefixes);\n03234| if (predicateLabel === 'vad:hasTrig' || quad.predicate.value === 'http://example.org/vad#hasTrig') {\n03235| processHasTrig.set(quad.subject.value, quad.object.value);\n03236| }\n03237| if (predicateLabel === 'vad:hasParentProcess' || quad.predicate.value === 'http://example.org/vad#hasParentProcess') {\n03238| processHasParentProcess.set(quad.subject.value, quad.object.value);\n03239| }\n03240| });\n03241| \n03242| // Заполняем карты definesProcess и hasParentTrig для каждого TriG\n03243| Object.entries(trigHierarchy).forEach(([trigUri, graphInfo]) => {\n03244| if (trigUri === PTREE_GRAPH_URI || trigUri === RTREE_GRAPH_URI) return;\n03245| \n03246| graphInfo.quads.forEach(quad => {\n03247| const predicateLabel = getPrefixedName(quad.predicate.value, currentPrefixes);\n03248| if (predicateLabel === 'vad:definesProcess' || quad.predicate.value === 'http://example.org/vad#definesProcess') {\n03249| trigDefinesProcess.set(trigUri, quad.object.value);\n03250| }\n03251| if (predicateLabel === 'vad:hasParentTrig' || quad.predicate.value === 'http://example.org/vad#hasParentTrig') {\n03252| trigHasParentTrig.set(trigUri, quad.object.value);\n03253| }\n03254| });\n03255| });\n03256| \n03257| // Обрабатываем каждый TriG типа VADProcessDia\n03258| Object.entries(trigHierarchy).forEach(([trigUri, graphInfo]) => {\n03259| // Пропускаем специальные графы\n03260| if (trigUri === PTREE_GRAPH_URI || trigUri === RTREE_GRAPH_URI) return;\n03261| \n03262| // Получаем процесс, который определяется этим TriG\n03263| const definingProcess = trigDefinesProcess.get(trigUri);\n03264| \n03265| // Создаем виртуальный TriG для хранения processSubtype\n03266| const virtualTrigId = trigUri.replace(/t_/, 'vt_');\n03267| const virtualTrigLabel = getPrefixedName(virtualTrigId, currentPrefixes);\n03268| \n03269| virtualRDFdata[virtualTrigId] = {\n03270| trigUri: trigUri,\n03271| label: `Виртуальный TriG для ${getPrefixedName(trigUri, currentPrefixes)}`,\n03272| processSubtypes: {}\n03273| };\n03274| \n03275| // Находим все процессы в этом TriG (по vad:isSubprocessTrig)\n03276| const processesInTrig = [];\n03277| graphInfo.quads.forEach(quad => {\n03278| const predicateLabel = getPrefixedName(quad.predicate.value, currentPrefixes);\n03279| if (predicateLabel === 'vad:isSubprocessTrig' || quad.predicate.value === 'http://example.org/vad#isSubprocessTrig') {\n03280| processesInTrig.push(quad.subject.value);\n03281| }\n03282| });\n03283| \n03284| // Вычисляем processSubtype для каждого процесса в этом TriG\n03285| processesInTrig.forEach(processUri => {\n03286| let subtype = null;\n03287| \n03288| // Проверяем, имеет ли процесс свой TriG (детализированный)\n03289| if (processHasTrig.has(processUri)) {\n03290| const childTrigUri = processHasTrig.get(processUri);\n03291| const childParentTrig = trigHasParentTrig.get(childTrigUri);\n03292| \n03293| if (childParentTrig === trigUri) {\n03294| subtype = 'vad:DetailedChild';\n03295| } else {\n03296| subtype = 'vad:DetailedExternal';\n03297| }\n03298| }\n03299| // Проверяем, имеет ли процесс hasParentProcess (недетализированный)\n03300| else if (processHasParentProcess.has(processUri)) {\n03301| const parentProcess = processHasParentProcess.get(processUri);\n03302| const parentProcessLabel = getPrefixedName(parentProcess, currentPrefixes);\n03303| \n03304| if (parentProcessLabel === 'vad:NotDefined' || parentProcess === 'http://example.org/vad#NotDefined') {\n03305| subtype = 'vad:NotDefinedType';\n03306| } else if (parentProcess === definingProcess) {\n03307| subtype = 'vad:notDetailedChild';\n03308| } else {\n03309| subtype = 'vad:notDetailedExternal';\n03310| }\n03311| }\n03312| // По умолчанию - notDetailedChild (если ничего не определено)\n03313| else {\n03314| subtype = 'vad:notDetailedChild';\n03315| }\n03316| \n03317| virtualRDFdata[virtualTrigId].processSubtypes[processUri] = subtype;\n03318| });\n03319| });\n03320| \n03321| // Обновляем nodeSubtypesCache на основе вычисленных данных\n03322| updateNodeSubtypesCacheFromVirtualData();\n03323| \n03324| return virtualRDFdata;\n03325| }\n03326| \n03327| /**\n03328| * Обновляет nodeSubtypesCache на основе вычисленных virtualRDFdata\n03329| * Это позволяет визуализации использовать вычисленные подтипы\n03330| */\n03331| function updateNodeSubtypesCacheFromVirtualData() {\n03332| Object.values(virtualRDFdata).forEach(vTrig => {\n03333| Object.entries(vTrig.processSubtypes).forEach(([processUri, subtype]) => {\n03334| if (!nodeSubtypesCache[processUri]) {\n03335| nodeSubtypesCache[processUri] = [];\n03336| }\n03337| \n03338| const fullSubtypeUri = subtype.replace('vad:', 'http://example.org/vad#');\n03339| \n03340| if (!nodeSubtypesCache[processUri].includes(subtype)) {\n03341| nodeSubtypesCache[processUri].push(subtype);\n03342| }\n03343| if (!nodeSubtypesCache[processUri].includes(fullSubtypeUri)) {\n03344| nodeSubtypesCache[processUri].push(fullSubtypeUri);\n03345| }\n03346| });\n03347| });\n03348| }\n03349| \n03350| /**\n03351| * Получает вычисленные свойства (processSubtype) для конкретного процесса\n03352| * @param {string} nodeUri - URI узла\n03353| * @returns {Array} - массив вычисленных свойств [{predicate, value}]\n03354| */\n03355| function getComputedPropertiesForNode(nodeUri) {\n03356| const computed = [];\n03357| \n03358| // Ищем processSubtype в virtualRDFdata\n03359| Object.values(virtualRDFdata).forEach(vTrig => {\n03360| if (vTrig.processSubtypes[nodeUri]) {\n03361| computed.push({\n03362| predicate: 'vad:processSubtype',\n03363| predicateLabel: 'vad:processSubtype',\n03364| value: vTrig.processSubtypes[nodeUri],\n03365| valueLabel: vTrig.processSubtypes[nodeUri],\n03366| source: 'virtualRDFdata'\n03367| });\n03368| }\n03369| });\n03370| \n03371| return computed;\n03372| }\n03373| \n03374| /**\n03375| * Показывает virtualRDFdata в отдельном окне\n03376| * Вызывается при нажатии кнопки \"virtualRDFdata\"\n03377| */\n03378| function showVirtualRDFData() {\n03379| // Вычисляем virtualRDFdata, если еще не вычислены\n03380| if (!trigHierarchy || Object.keys(trigHierarchy).length === 0) {\n03381| // Нужно сначала распарсить RDF данные\n03382| const rdfInput = document.getElementById('rdf-input');\n03383| if (!rdfInput || !rdfInput.value.trim()) {\n03384| alert('Нет данных для анализа. Введите RDF данные и нажмите \"Показать\" для визуализации.');\n03385| return;\n03386| }\n03387| \n03388| // Парсим данные для получения trigHierarchy\n03389| const parser = new N3.Parser({ format: 'trig' });\n03390| const quads = [];\n03391| let prefixes = {};\n03392| \n03393| try {\n03394| parser.parse(rdfInput.value, (error, quad, parsedPrefixes) => {\n03395| if (error) {\n03396| alert(`Ошибка парсинга RDF: ${error.message}`);\n03397| return;\n03398| }\n03399| if (quad) {\n03400| quads.push(quad);\n03401| } else {\n03402| if (parsedPrefixes) {\n03403| prefixes = parsedPrefixes;\n03404| }\n03405| \n03406| currentPrefixes = prefixes;\n03407| currentQuads = quads;\n03408| \n03409| const hierarchyResult = parseTriGHierarchy(quads, prefixes);\n03410| if (hierarchyResult.valid) {\n03411| trigHierarchy = hierarchyResult.hierarchy;\n03412| computeVirtualRDFdata();\n03413| displayVirtualRDFDataWindow();\n03414| } else {\n03415| alert('Ошибка парсинга иерархии TriG. Убедитесь, что данные корректны.');\n03416| }\n03417| }\n03418| });\n03419| } catch (error) {\n03420| alert(`Ошибка: ${error.message}`);\n03421| }\n03422| } else {\n03423| computeVirtualRDFdata();\n03424| displayVirtualRDFDataWindow();\n03425| }\n03426| }\n03427| \n03428| /**\n03429| * Отображает virtualRDFdata в новом окне браузера\n03430| */\n03431| function displayVirtualRDFDataWindow() {\n03432| // Формируем TriG формат для virtualRDFdata\n03433| let trigContent = '# virtualRDFdata - вычисляемые RDF данные\\n';\n03434| trigContent += '# Сгенерировано автоматически на основе RDF данных\\n';\n03435| trigContent += '#\\n';\n03436| trigContent += '# Содержит вычисленные vad:processSubtype для каждого процесса в каждой схеме\\n';\n03437| trigContent += '#\\n';\n03438| trigContent += '# Подтипы процессов:\\n';\n03439| trigContent += '# - vad:DetailedChild (голубой): процесс с vad:hasTrig, дочерний TriG указывает на текущий\\n';\n03440| trigContent += '# - vad:DetailedExternal (оранжевый): процесс с vad:hasTrig, дочерний TriG НЕ указывает на текущий\\n';\n03441| trigContent += '# - vad:notDetailedChild (зелёный): процесс с vad:hasParentProcess, совпадающим с родителем схемы\\n';\n03442| trigContent += '# - vad:notDetailedExternal (серый): процесс с vad:hasParentProcess, НЕ совпадающим с родителем\\n';\n03443| trigContent += '# - vad:NotDefinedType (красноватый): процесс с vad:hasParentProcess = vad:NotDefined\\n\\n';\n03444| \n03445| trigContent += '@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\\n';\n03446| trigContent += '@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\\n';\n03447| trigContent += '@prefix vad: <http://example.org/vad#> .\\n\\n';\n03448| \n03449| // Генерируем содержимое для каждого виртуального TriG\n03450| Object.entries(virtualRDFdata).forEach(([vTrigId, vTrigData]) => {\n03451| const vTrigLabel = getPrefixedName(vTrigId, currentPrefixes);\n03452| const originalTrigLabel = getPrefixedName(vTrigData.trigUri, currentPrefixes);\n03453| \n03454| trigContent += `# ============================================================================\\n`;\n03455| trigContent += `# ${vTrigLabel} - вычисленные свойства для ${originalTrigLabel}\\n`;\n03456| trigContent += `# ============================================================================\\n\\n`;\n03457| \n03458| trigContent += `${vTrigLabel} {\\n`;\n03459| \n03460| // Добавляем processSubtype для каждого процесса\n03461| Object.entries(vTrigData.processSubtypes).forEach(([processUri, subtype]) => {\n03462| const processLabel = getPrefixedName(processUri, currentPrefixes);\n03463| trigContent += ` ${processLabel} vad:processSubtype ${subtype} .\\n`;\n03464| });\n03465| \n03466| trigContent += '}\\n\\n';\n03467| });\n03468| \n03469| // Создаем новое окно\n03470| const newWindow = window.open('', '_blank', 'width=900,height=700,scrollbars=yes,resizable=yes');\n03471| \n03472| if (newWindow) {\n03473| const htmlContent = '<!DOCTYPE html>' +\n03474| '<html><head><title>virtualRDFdata</title>' +\n03475| '<style>' +\n03476| 'body { font-family: Consolas, Monaco, monospace; padding: 20px; background-color: #f5f5f5; margin: 0; }' +\n03477| '.container { background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }' +\n03478| 'h1 { color: #333; font-size: 18px; margin-top: 0; border-bottom: 2px solid #9C27B0; padding-bottom: 10px; }' +\n03479| 'pre { white-space: pre-wrap; word-wrap: break-word; font-size: 13px; line-height: 1.5; margin: 0; color: #333; }' +\n03480| '.copy-btn { background-color: #9C27B0; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-right: 10px; margin-bottom: 15px; }' +\n03481| '.copy-btn:hover { background-color: #7B1FA2; }' +\n03482| '.save-btn { background-color: #4CAF50; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-bottom: 15px; }' +\n03483| '.save-btn:hover { background-color: #45a049; }' +\n03484| '.comment { color: #6a9955; }' +\n03485| '.keyword { color: #0000ff; }' +\n03486| '.uri { color: #795E26; }' +\n03487| '</style></head>' +\n03488| '<body><div class=\"container\">' +\n03489| '<h1>virtualRDFdata - Вычисляемые RDF данные</h1>' +\n03490| '<button class=\"copy-btn\" id=\"copy-btn\">Копировать в буфер</button>' +\n03491| '<button class=\"save-btn\" id=\"save-btn\">Сохранить как файл</button>' +\n03492| '<pre id=\"trig-content\"></pre>' +\n03493| '</div></body></html>';\n03494| \n03495| newWindow.document.write(htmlContent);\n03496| newWindow.document.close();\n03497| \n03498| // Заполняем содержимое\n03499| const preElement = newWindow.document.getElementById('trig-content');\n03500| if (preElement) {\n03501| preElement.textContent = trigContent;\n03502| }\n03503| \n03504| // Обработчик копирования\n03505| const copyBtn = newWindow.document.getElementById('copy-btn');\n03506| if (copyBtn) {\n03507| copyBtn.onclick = function() {\n03508| newWindow.navigator.clipboard.writeText(trigContent).then(function() {\n03509| newWindow.alert('Скопировано в буфер обмена');\n03510| }).catch(function() {\n03511| const textarea = newWindow.document.createElement('textarea');\n03512| textarea.value = trigContent;\n03513| newWindow.document.body.appendChild(textarea);\n03514| textarea.select();\n03515| newWindow.document.execCommand('copy');\n03516| newWindow.document.body.removeChild(textarea);\n03517| newWindow.alert('Скопировано в буфер обмена');\n03518| });\n03519| };\n03520| }\n03521| \n03522| // Обработчик сохранения\n03523| const saveBtn = newWindow.document.getElementById('save-btn');\n03524| if (saveBtn) {\n03525| saveBtn.onclick = function() {\n03526| const blob = new Blob([trigContent], { type: 'text/turtle' });\n03527| const url = URL.createObjectURL(blob);\n03528| const a = newWindow.document.createElement('a');\n03529| a.href = url;\n03530| a.download = 'virtualRDFdata.ttl';\n03531| newWindow.document.body.appendChild(a);\n03532| a.click();\n03533| newWindow.document.body.removeChild(a);\n03534| URL.revokeObjectURL(url);\n03535| };\n03536| }\n03537| }\n03538| }\n03539| \n03540| // ============================================================================\n03541| // ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ДЛЯ РАБОТЫ С URI\n03542| // ============================================================================\n03543| \n03544| function getLocalName(uri) {\n03545| if (typeof uri !== 'string') return String(uri);\n03546| const hashIndex = uri.lastIndexOf('#');\n03547| const slashIndex = uri.lastIndexOf('/');\n03548| const splitIndex = Math.max(hashIndex, slashIndex);\n03549| if (splitIndex !== -1 && splitIndex < uri.length - 1) {\n03550| return uri.substring(splitIndex + 1);\n03551| }\n03552| return uri;\n03553| }\n03554| \n03555| function getPrefixedName(uri, prefixes) {\n03556| if (typeof uri !== 'string') return String(uri);\n03557| for (const [prefix, namespace] of Object.entries(prefixes)) {\n03558| if (uri.startsWith(namespace)) {\n03559| const localName = uri.substring(namespace.length);\n03560| return prefix + ':' + localName;\n03561| }\n03562| }\n03563| return getLocalName(uri);\n03564| }\n03565| \n03566| function escapeDotString(str) {\n03567| return str.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n03568| }\n03569| \n03570| function generateNodeId(value) {\n03571| let hash = 0;\n03572| for (let i = 0; i < value.length; i++) {\n03573| const char = value.charCodeAt(i);\n03574| hash = ((hash << 5) - hash) + char;\n03575| hash = hash & hash;\n03576| }\n03577| return 'node' + Math.abs(hash);\n03578| }\n03579| \n03580| /**\n03581| * Генерирует ID узла для VAD режима на основе RDF идентификатора.\n03582| * Преобразует prefixed name (например, vad:Process1) в валидный DOT ID (vad_Process1).\n03583| * @param {string} uri - URI узла\n03584| * @param {Object} prefixes - Словарь префиксов\n03585| * @returns {string} - Валидный DOT ID\n03586| */\n03587| function generateVadNodeId(uri, prefixes) {\n03588| const prefixedName = getPrefixedName(uri, prefixes);\n03589| // Заменяем ':' на '_' для получения валидного DOT ID\n03590| // Также заменяем другие недопустимые символы на '_'\n03591| return prefixedName.replace(/[:\\-\\.\\s]/g, '_');\n03592| }\n03593| \n03594| function isNameOrLabelPredicate(predicateLabel) {\n03595| const lowerPredicate = predicateLabel.toLowerCase();\n03596| return lowerPredicate.includes('name') || lowerPredicate.includes('label');\n03597| }\n03598| \n03599| function escapeHtmlLabel(str) {\n03600| return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n03601| }\n03602| \n03603| /**\n03604| * Экранирует HTML символы для безопасного отображения\n03605| * @param {string} text - Исходный текст\n03606| * @returns {string} - Экранированный текст\n03607| */\n03608| function escapeHtml(text) {\n03609| const div = document.createElement('div');\n03610| div.textContent = text;\n03611| return div.innerHTML;\n03612| }\n03613| \n03614| /**\n03615| * Копирует ID объекта в буфер обмена\n03616| * @param {string} id - ID для копирования\n03617| * @param {HTMLElement} button - Кнопка, которая была нажата\n03618| */\n03619| function copyObjectId(id, button) {\n03620| navigator.clipboard.writeText(id).then(() => {\n03621| // Показываем подтверждение\n03622| const originalText = button.textContent;\n03623| button.textContent = 'Скопировано!';\n03624| button.classList.add('copied');\n03625| \n03626| // Возвращаем оригинальный текст через 2 секунды\n03627| setTimeout(() => {\n03628| button.textContent = originalText;\n03629| button.classList.remove('copied');\n03630| }, 2000);\n03631| }).catch(err => {\n03632| console.error('Ошибка копирования:', err);\n03633| // Fallback для старых браузеров\n03634| const textArea = document.createElement('textarea');\n03635| textArea.value = id;\n03636| textArea.style.position = 'fixed';\n03637| textArea.style.left = '-9999px';\n03638| document.body.appendChild(textArea);\n03639| textArea.select();\n03640| try {\n03641| document.execCommand('copy');\n03642| button.textContent = 'Скопировано!';\n03643| button.classList.add('copied');\n03644| setTimeout(() => {\n03645| button.textContent = 'Копировать';\n03646| button.classList.remove('copied');\n03647| }, 2000);\n03648| } catch (e) {\n03649| console.error('Fallback copy failed:', e);\n03650| }\n03651| document.body.removeChild(textArea);\n03652| });\n03653| }\n03654| \n03655| function wrapTextByWords(text, maxLength) {\n03656| if (!text || text.length <= maxLength) return [text];\n03657| const words = text.split(/\\s+/);\n03658| const lines = [];\n03659| let currentLine = '';\n03660| for (const word of words) {\n03661| if (currentLine.length === 0) {\n03662| currentLine = word;\n03663| } else if (currentLine.length + 1 + word.length <= maxLength) {\n03664| currentLine += ' ' + word;\n03665| } else {\n03666| lines.push(currentLine);\n03667| currentLine = word;\n03668| }\n03669| }\n03670| if (currentLine.length > 0) lines.push(currentLine);\n03671| return lines;\n03672| }\n03673| \n03674| function formatLabelWithWrap(label, maxLength, isBold = false) {\n03675| const lines = wrapTextByWords(label, maxLength);\n03676| const needsWrap = lines.length > 1;\n03677| if (!needsWrap) {\n03678| if (isBold) return `<B>${escapeHtmlLabel(label)}</B>`;\n03679| return escapeHtmlLabel(label);\n03680| }\n03681| let result = '';\n03682| for (let i = 0; i < lines.length; i++) {\n03683| if (i > 0) result += '<BR/>';\n03684| const escapedLine = escapeHtmlLabel(lines[i]);\n03685| if (isBold) {\n03686| result += `<FONT POINT-SIZE=\"9\"><B>${escapedLine}</B></FONT>`;\n03687| } else {\n03688| result += `<FONT POINT-SIZE=\"9\">${escapedLine}</FONT>`;\n03689| }\n03690| }\n03691| return result;\n03692| }\n03693| \n03694| // ============================================================================\n03695| // ГЕНЕРАЦИЯ DOT-КОДА\n03696| // ============================================================================\n03697| \n03698| /**\n03699| * Генерирует DOT-код для режима VAD\n03700| * В этом режиме:\n03701| * - Процессы отображаются как cds (chevron) с зеленой заливкой\n03702| * - Под процессами показываются исполнители\n03703| * - Связи hasNext идут с востока на запад\n03704| * - hasParentTrig не показывается, но есть в свойствах\n03705| */\n03706| function rdfToDotVAD(quads, prefixes = {}) {\n03707| buildNodeTypesCache(currentQuads, prefixes);\n03708| nodeLabelToUri = {};\n03709| \n03710| // Собираем информацию о процессах и их исполнителях\n03711| const processes = new Map(); // URI процесса -> информация\n03712| const executorGroups = new Map(); // URI группы -> список исполнителей\n03713| const hasNextEdges = []; // Связи hasNext между процессами\n03714| \n03715| // ver5t: Извлекаем информацию об именованном графе для отображения заголовка схемы\n03716| let namedGraphUri = null;\n03717| const graphLabels = new Map(); // URI графа -> rdfs:label\n03718| \n03719| // Собираем все именованные графы и их метки\n03720| quads.forEach(quad => {\n03721| // Получаем URI именованного графа (если есть)\n03722| if (quad.graph && quad.graph.value && quad.graph.value !== '') {\n03723| if (!namedGraphUri) {\n03724| namedGraphUri = quad.graph.value; // Берём первый именованный граф\n03725| }\n03726| }\n03727| });\n03728| \n03729| // Ищем rdfs:label для именованных графов (определяется вне графа)\n03730| quads.forEach(quad => {\n03731| const subjectUri = quad.subject.value;\n03732| const predicateUri = quad.predicate.value;\n03733| const predicateLabel = getPrefixedName(predicateUri, prefixes);\n03734| \n03735| if (predicateLabel === 'rdfs:label' || predicateUri === 'http://www.w3.org/2000/01/rdf-schema#label') {\n03736| // Проверяем, является ли subject именованным графом\n03737| if (subjectUri === namedGraphUri) {\n03738| graphLabels.set(subjectUri, quad.object.value);\n03739| }\n03740| }\n03741| });\n03742| \n03743| // Первый проход: собираем все объекты\n03744| quads.forEach(quad => {\n03745| const subjectUri = quad.subject.value;\n03746| const predicateUri = quad.predicate.value;\n03747| const predicateLabel = getPrefixedName(predicateUri, prefixes);\n03748| const objectValue = quad.object.value;\n03749| \n03750| // Проверяем типы\n03751| const subjectTypes = nodeTypesCache[subjectUri] || [];\n03752| const isProcess = subjectTypes.some(t =>\n03753| t === 'vad:TypeProcess' || t === 'http://example.org/vad#TypeProcess'\n03754| );\n03755| \n03756| if (isProcess) {\n03757| if (!processes.has(subjectUri)) {\n03758| processes.set(subjectUri, {\n03759| uri: subjectUri,\n03760| label: getPrefixedName(subjectUri, prefixes),\n03761| name: null,\n03762| executorGroup: null,\n03763| hasNext: [],\n03764| hasParent: null\n03765| });\n03766| }\n03767| \n03768| const processInfo = processes.get(subjectUri);\n03769| \n03770| // Собираем rdfs:label для имени\n03771| if (predicateLabel === 'rdfs:label' || predicateUri === 'http://www.w3.org/2000/01/rdf-schema#label') {\n03772| processInfo.name = objectValue;\n03773| }\n03774| \n03775| // Собираем hasExecutor\n03776| if (predicateLabel === 'vad:hasExecutor' || predicateUri === 'http://example.org/vad#hasExecutor') {\n03777| processInfo.executorGroup = objectValue;\n03778| }\n03779| \n03780| // Собираем hasNext\n03781| if (predicateLabel === 'vad:hasNext' || predicateUri === 'http://example.org/vad#hasNext') {\n03782| processInfo.hasNext.push(objectValue);\n03783| hasNextEdges.push({ from: subjectUri, to: objectValue });\n03784| }\n03785| \n03786| // Собираем hasParent\n03787| if (predicateLabel === 'vad:hasParentTrig' || predicateUri === 'http://example.org/vad#hasParentTrig') {\n03788| processInfo.hasParent = objectValue;\n03789| }\n03790| }\n03791| \n03792| // Собираем информацию о группах исполнителей\n03793| const isExecutorGroup = subjectTypes.some(t =>\n03794| t === 'vad:ExecutorGroup' || t === 'http://example.org/vad#ExecutorGroup'\n03795| );\n03796| \n03797| if (isExecutorGroup) {\n03798| if (!executorGroups.has(subjectUri)) {\n03799| executorGroups.set(subjectUri, {\n03800| uri: subjectUri,\n03801| label: null,\n03802| executors: []\n03803| });\n03804| }\n03805| \n03806| const groupInfo = executorGroups.get(subjectUri);\n03807| \n03808| if (predicateLabel === 'rdfs:label' || predicateUri === 'http://www.w3.org/2000/01/rdf-schema#label') {\n03809| groupInfo.label = objectValue;\n03810| }\n03811| \n03812| if (predicateLabel === 'vad:includes' || predicateUri === 'http://example.org/vad#includes') {\n03813| groupInfo.executors.push(objectValue);\n03814| }\n03815| }\n03816| });\n03817| \n03818| // Дополняем метаданные процессов из vad:ptree (если не найдены в текущем TriG)\n03819| processes.forEach((processInfo, processUri) => {\n03820| if (!processInfo.name) {\n03821| const ptreeMetadata = getProcessMetadataFromPtree(processUri, prefixes);\n03822| if (ptreeMetadata.label) {\n03823| processInfo.name = ptreeMetadata.label;\n03824| }\n03825| }\n03826| });\n03827| \n03828| // Собираем имена исполнителей\n03829| // Сначала из текущих quads (если rdfs:label есть в этом графе)\n03830| const executorNames = new Map();\n03831| quads.forEach(quad => {\n03832| const subjectUri = quad.subject.value;\n03833| const predicateUri = quad.predicate.value;\n03834| const predicateLabel = getPrefixedName(predicateUri, prefixes);\n03835| const objectValue = quad.object.value;\n03836| \n03837| const subjectTypes = nodeTypesCache[subjectUri] || [];\n03838| const isExecutor = subjectTypes.some(t =>\n03839| t === 'vad:TypeExecutor' || t === 'http://example.org/vad#TypeExecutor'\n03840| );\n03841| \n03842| if (isExecutor && (predicateLabel === 'rdfs:label' || predicateUri === 'http://www.w3.org/2000/01/rdf-schema#label')) {\n03843| executorNames.set(subjectUri, objectValue);\n03844| }\n03845| });\n03846| \n03847| // Дополняем имена исполнителей из vad:rtree (если не найдены в текущем TriG)\n03848| executorGroups.forEach((groupInfo, groupUri) => {\n03849| groupInfo.executors.forEach(executorUri => {\n03850| if (!executorNames.has(executorUri)) {\n03851| const nameFromRtree = getExecutorNameFromRtree(executorUri, prefixes);\n03852| if (nameFromRtree) {\n03853| executorNames.set(executorUri, nameFromRtree);\n03854| }\n03855| }\n03856| });\n03857| });\n03858| \n03859| // Генерация DOT-кода\n03860| // Используем rankdir=TB чтобы rank=same группировал узлы горизонтально\n03861| // А процессы идут в одной строке благодаря rank=same\n03862| let dot = 'digraph VADGraph {\\n';\n03863| dot += ' // VAD (Value Added Chain Diagram)\\n';\n03864| dot += ' rankdir=TB;\\n'; // Top-to-bottom позволяет горизонтальное выравнивание через rank=same\n03865| dot += ' node [fontname=\"Arial\"];\\n';\n03866| dot += ' edge [fontname=\"Arial\", fontsize=10];\\n';\n03867| dot += ' splines=spline;\\n'; // spline для лучшей маршрутизации skip-ребер\n03868| dot += ' nodesep=0.8;\\n'; // Расстояние между узлами\n03869| dot += ' ranksep=0.3;\\n'; // Минимальное расстояние между рангами (CDS и ExecutorGroup)\n03870| \n03871| // ver5t: Добавляем заголовок схемы из именованного графа (если есть)\n03872| // Сначала проверяем graphLabels (для обычного VAD режима)\n03873| // Затем проверяем trigHierarchy (для VAD TriG режима)\n03874| let schemaTitle = null;\n03875| if (namedGraphUri && graphLabels.has(namedGraphUri)) {\n03876| schemaTitle = graphLabels.get(namedGraphUri);\n03877| } else if (namedGraphUri && trigHierarchy && trigHierarchy[namedGraphUri] && trigHierarchy[namedGraphUri].label) {\n03878| schemaTitle = trigHierarchy[namedGraphUri].label;\n03879| }\n03880| if (schemaTitle) {\n03881| dot += ` label=\"${escapeDotString(schemaTitle)}\";\\n`;\n03882| dot += ' labelloc=\"t\";\\n'; // Заголовок вверху\n03883| dot += ' fontname=\"Arial\";\\n';\n03884| dot += ' fontsize=16;\\n';\n03885| }\n03886| dot += '\\n';\n03887| \n03888| // Фильтруем процессы - показываем только те, которые не являются материнскими\n03889| // (Process0 скрыт, потому что он не имеет hasNext и является родителем)\n03890| const visibleProcesses = new Map();\n03891| processes.forEach((processInfo, uri) => {\n03892| // Показываем процесс, если он имеет hasNext или на него ссылается hasNext\n03893| const hasOutgoingNext = processInfo.hasNext.length > 0;\n03894| const hasIncomingNext = [...processes.values()].some(p => p.hasNext.includes(uri));\n03895| \n03896| if (hasOutgoingNext || hasIncomingNext) {\n03897| visibleProcesses.set(uri, processInfo);\n03898| }\n03899| });\n03900| \n03901| // Строим порядок процессов для определения skip-ребер (ребер, которые пропускают промежуточные узлы)\n03902| // Топологическая сортировка процессов по hasNext для определения их порядка\n03903| const processOrder = [];\n03904| const visited = new Set();\n03905| const processUris = [...visibleProcesses.keys()];\n03906| \n03907| // Найти начальный процесс (процесс без входящих hasNext)\n03908| const incomingCount = new Map();\n03909| processUris.forEach(uri => incomingCount.set(uri, 0));\n03910| hasNextEdges.forEach(edge => {\n03911| if (visibleProcesses.has(edge.to)) {\n03912| incomingCount.set(edge.to, (incomingCount.get(edge.to) || 0) + 1);\n03913| }\n03914| });\n03915| \n03916| // Топологическая сортировка (BFS)\n03917| const queue = [];\n03918| processUris.forEach(uri => {\n03919| if (incomingCount.get(uri) === 0) {\n03920| queue.push(uri);\n03921| }\n03922| });\n03923| \n03924| while (queue.length > 0) {\n03925| const uri = queue.shift();\n03926| if (!visited.has(uri)) {\n03927| visited.add(uri);\n03928| processOrder.push(uri);\n03929| const processInfo = visibleProcesses.get(uri);\n03930| if (processInfo) {\n03931| processInfo.hasNext.forEach(nextUri => {\n03932| if (visibleProcesses.has(nextUri)) {\n03933| const count = incomingCount.get(nextUri) - 1;\n03934| incomingCount.set(nextUri, count);\n03935| if (count === 0 && !visited.has(nextUri)) {\n03936| queue.push(nextUri);\n03937| }\n03938| }\n03939| });\n03940| }\n03941| }\n03942| }\n03943| \n03944| // Добавляем оставшиеся процессы (на случай циклов или изолированных)\n03945| processUris.forEach(uri => {\n03946| if (!visited.has(uri)) {\n03947| processOrder.push(uri);\n03948| }\n03949| });\n03950| \n03951| // Создаем индекс позиции процесса для определения skip-ребер\n03952| const processPositionIndex = new Map();\n03953| processOrder.forEach((uri, index) => {\n03954| processPositionIndex.set(uri, index);\n03955| });\n03956| \n03957| // Добавляем узлы процессов (CDS) и ExecutorGroup как отдельные узлы\n03958| dot += ' // Процессы VAD (cds shape) и ExecutorGroup (ellipse желтый)\\n';\n03959| \n03960| // Сначала добавляем все узлы CDS\n03961| visibleProcesses.forEach((processInfo, uri) => {\n03962| const nodeId = generateVadNodeId(uri, prefixes);\n03963| const processName = processInfo.name || processInfo.label;\n03964| \n03965| // Формируем HTML label с именем процесса (БЕЗ жирного шрифта)\n03966| const wrappedProcessName = wrapTextByWords(processName, currentMaxLabelLength);\n03967| \n03968| let htmlLabel = '<';\n03969| for (let i = 0; i < wrappedProcessName.length; i++) {\n03970| if (i > 0) htmlLabel += '<BR/>';\n03971| htmlLabel += escapeHtmlLabel(wrappedProcessName[i]);\n03972| }\n03973| htmlLabel += '>';\n03974| \n03975| // Определяем стиль узла на основе vad:processSubtype\n03976| // Проверяем nodeSubtypesCache для определения типа процесса\n03977| // Поддерживаемые подтипы:\n03978| // - DetailedChild (голубой) - детализированный подпроцесс текущей схемы\n03979| // - DetailedExternal (оранжевый) - детализированный внешний процесс\n03980| // - notDetailedChild (зеленый) - недетализированный подпроцесс\n03981| // - notDetailedExternal (серый) - недетализированный внешний процесс\n03982| // - NotDefinedType (красноватый) - тип не определен\n03983| const nodeSubtypes = nodeSubtypesCache[uri] || [];\n03984| \n03985| // Получаем стиль на основе подтипа процесса (используем новую функцию)\n03986| const nodeStyle = getProcessStyleForSubtype(nodeSubtypes);\n03987| dot += ` ${nodeId} [label=${htmlLabel} ${nodeStyle}];\\n`;\n03988| \n03989| nodeLabelToUri[processInfo.label] = { uri: uri, dotId: nodeId };\n03990| });\n03991| \n03992| dot += '\\n';\n03993| \n03994| // Добавляем ExecutorGroup узлы (желтые эллипсы)\n03995| // ExecutorGroup объекты теперь кликабельны и показывают свойства\n03996| dot += ' // ExecutorGroup узлы (эллипсы с желтоватой заливкой)\\n';\n03997| visibleProcesses.forEach((processInfo, uri) => {\n03998| const nodeId = generateVadNodeId(uri, prefixes);\n03999| \n04000| // Получаем список исполнителей\n\n(File has more lines. Use 'offset' parameter to read beyond line 4000)\n</file>",
[2026-01-26T19:13:27.640Z] [INFO] "title": "ver8a/index.html",
[2026-01-26T19:13:27.641Z] [INFO] "metadata": {
[2026-01-26T19:13:27.641Z] [INFO] "preview": " if (list && toggle) {\n if (list.style.display === 'none') {\n list.style.display = 'block';\n toggle.textContent = '▼';\n } else {\n list.style.display = 'none';\n toggle.textContent = '▶';\n }\n }\n }\n\n /**\n * Обработчик выбора процесса в дереве\n * @param {string} processUri - URI выбранного процесса\n * @param {string} trigUri - URI TriG, содержащего процесс\n */\n function selectProcess(processUri, trigUri) {\n // Если выбранный процесс из другого TriG, сначала переключаемся на этот TriG\n if (selectedTrigUri !== trigUri) {\n selectTriG(trigUri);"
[2026-01-26T19:13:27.642Z] [INFO] },
[2026-01-26T19:13:27.642Z] [INFO] "time": {
[2026-01-26T19:13:27.642Z] [INFO] "start": 1769454807626,
[2026-01-26T19:13:27.642Z] [INFO] "end": 1769454807629
[2026-01-26T19:13:27.643Z] [INFO] }
[2026-01-26T19:13:27.643Z] [INFO] }
[2026-01-26T19:13:27.643Z] [INFO] }
[2026-01-26T19:13:27.643Z] [INFO] }
[2026-01-26T19:13:27.686Z] [INFO] {
[2026-01-26T19:13:27.686Z] [INFO] "type": "text",
[2026-01-26T19:13:27.686Z] [INFO] "timestamp": 1769454807685,
[2026-01-26T19:13:27.687Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:27.687Z] [INFO] "part": {
[2026-01-26T19:13:27.687Z] [INFO] "id": "prt_bfbb9a6b0001gxu74G9pU5kTSq",
[2026-01-26T19:13:27.687Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:27.687Z] [INFO] "messageID": "msg_bfbb992b3001iiBGzWoEtam0zJ",
[2026-01-26T19:13:27.688Z] [INFO] "type": "text",
[2026-01-26T19:13:27.688Z] [INFO] "text": "\nLet me continue reading the file to understand the full structure:",
[2026-01-26T19:13:27.688Z] [INFO] "time": {
[2026-01-26T19:13:27.688Z] [INFO] "start": 1769454807684,
[2026-01-26T19:13:27.688Z] [INFO] "end": 1769454807684
[2026-01-26T19:13:27.688Z] [INFO] }
[2026-01-26T19:13:27.689Z] [INFO] }
[2026-01-26T19:13:27.689Z] [INFO] }
[2026-01-26T19:13:27.693Z] [INFO] {
[2026-01-26T19:13:27.693Z] [INFO] "type": "step_finish",
[2026-01-26T19:13:27.694Z] [INFO] "timestamp": 1769454807692,
[2026-01-26T19:13:27.694Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:27.694Z] [INFO] "part": {
[2026-01-26T19:13:27.694Z] [INFO] "id": "prt_bfbb9aa86001iXsdjO20JSiS79",
[2026-01-26T19:13:27.694Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:27.695Z] [INFO] "messageID": "msg_bfbb992b3001iiBGzWoEtam0zJ",
[2026-01-26T19:13:27.695Z] [INFO] "type": "step-finish",
[2026-01-26T19:13:27.695Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:13:27.695Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08",
[2026-01-26T19:13:27.695Z] [INFO] "cost": 0,
[2026-01-26T19:13:27.695Z] [INFO] "tokens": {
[2026-01-26T19:13:27.696Z] [INFO] "input": 45153,
[2026-01-26T19:13:27.696Z] [INFO] "output": 67,
[2026-01-26T19:13:27.696Z] [INFO] "reasoning": 1,
[2026-01-26T19:13:27.696Z] [INFO] "cache": {
[2026-01-26T19:13:27.696Z] [INFO] "read": 67,
[2026-01-26T19:13:27.696Z] [INFO] "write": 0
[2026-01-26T19:13:27.697Z] [INFO] }
[2026-01-26T19:13:27.697Z] [INFO] }
[2026-01-26T19:13:27.697Z] [INFO] }
[2026-01-26T19:13:27.697Z] [INFO] }
[2026-01-26T19:13:33.255Z] [INFO] {
[2026-01-26T19:13:33.255Z] [INFO] "type": "step_start",
[2026-01-26T19:13:33.256Z] [INFO] "timestamp": 1769454813254,
[2026-01-26T19:13:33.256Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:33.256Z] [INFO] "part": {
[2026-01-26T19:13:33.256Z] [INFO] "id": "prt_bfbb9c0450012IDIosfIu2BMHW",
[2026-01-26T19:13:33.257Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:33.257Z] [INFO] "messageID": "msg_bfbb9aa9c001ts6uiKsgIGcUnW",
[2026-01-26T19:13:33.258Z] [INFO] "type": "step-start",
[2026-01-26T19:13:33.258Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08"
[2026-01-26T19:13:33.258Z] [INFO] }
[2026-01-26T19:13:33.258Z] [INFO] }
[2026-01-26T19:13:42.365Z] [INFO] {
[2026-01-26T19:13:42.366Z] [INFO] "type": "tool_use",
[2026-01-26T19:13:42.367Z] [INFO] "timestamp": 1769454822364,
[2026-01-26T19:13:42.367Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:42.367Z] [INFO] "part": {
[2026-01-26T19:13:42.367Z] [INFO] "id": "prt_bfbb9e3d8001lxSJI0t7dsqSVJ",
[2026-01-26T19:13:42.368Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:42.368Z] [INFO] "messageID": "msg_bfbb9aa9c001ts6uiKsgIGcUnW",
[2026-01-26T19:13:42.368Z] [INFO] "type": "tool",
[2026-01-26T19:13:42.368Z] [INFO] "callID": "call_007f7713d9d9404dbb2e0acb",
[2026-01-26T19:13:42.368Z] [INFO] "tool": "todowrite",
[2026-01-26T19:13:42.368Z] [INFO] "state": {
[2026-01-26T19:13:42.369Z] [INFO] "status": "completed",
[2026-01-26T19:13:42.369Z] [INFO] "input": {
[2026-01-26T19:13:42.369Z] [INFO] "todos": [
[2026-01-26T19:13:42.369Z] [INFO] {
[2026-01-26T19:13:42.370Z] [INFO] "content": "Анализ проблемы: Изучить issue #169 и понять что не работает в ver8a",
[2026-01-26T19:13:42.370Z] [INFO] "id": "1",
[2026-01-26T19:13:42.370Z] [INFO] "priority": "high",
[2026-01-26T19:13:42.370Z] [INFO] "status": "in_progress"
[2026-01-26T19:13:42.370Z] [INFO] },
[2026-01-26T19:13:42.371Z] [INFO] {
[2026-01-26T19:13:42.371Z] [INFO] "content": "Проверить текущую структуру файлов ver8a и наличие необходимых файлов",
[2026-01-26T19:13:42.371Z] [INFO] "id": "2",
[2026-01-26T19:13:42.371Z] [INFO] "priority": "high",
[2026-01-26T19:13:42.371Z] [INFO] "status": "pending"
[2026-01-26T19:13:42.371Z] [INFO] },
[2026-01-26T19:13:42.371Z] [INFO] {
[2026-01-26T19:13:42.372Z] [INFO] "content": "Исследовать ошибки загрузки файлов RDF данных или URI",
[2026-01-26T19:13:42.372Z] [INFO] "id": "3",
[2026-01-26T19:13:42.372Z] [INFO] "priority": "high",
[2026-01-26T19:13:42.372Z] [INFO] "status": "pending"
[2026-01-26T19:13:42.372Z] [INFO] },
[2026-01-26T19:13:42.372Z] [INFO] {
[2026-01-26T19:13:42.372Z] [INFO] "content": "Проверить работу кнопок: Показать, Очистить, Тест, Сохранить как",
[2026-01-26T19:13:42.373Z] [INFO] "id": "4",
[2026-01-26T19:13:42.373Z] [INFO] "priority": "high",
[2026-01-26T19:13:42.373Z] [INFO] "status": "pending"
[2026-01-26T19:13:42.373Z] [INFO] },
[2026-01-26T19:13:42.374Z] [INFO] {
[2026-01-26T19:13:42.374Z] [INFO] "content": "Исправить отображение полей: Макс. длина VAD, Smart Design, Result in SPARQL",
[2026-01-26T19:13:42.374Z] [INFO] "id": "5",
[2026-01-26T19:13:42.374Z] [INFO] "priority": "high",
[2026-01-26T19:13:42.375Z] [INFO] "status": "pending"
[2026-01-26T19:13:42.375Z] [INFO] },
[2026-01-26T19:13:42.375Z] [INFO] {
[2026-01-26T19:13:42.376Z] [INFO] "content": "Протестировать все исправления и убедиться что работает",
[2026-01-26T19:13:42.376Z] [INFO] "id": "6",
[2026-01-26T19:13:42.376Z] [INFO] "priority": "medium",
[2026-01-26T19:13:42.376Z] [INFO] "status": "pending"
[2026-01-26T19:13:42.377Z] [INFO] },
[2026-01-26T19:13:42.377Z] [INFO] {
[2026-01-26T19:13:42.377Z] [INFO] "content": "Создать pull request с исправлениями",
[2026-01-26T19:13:42.377Z] [INFO] "id": "7",
[2026-01-26T19:13:42.377Z] [INFO] "priority": "medium",
[2026-01-26T19:13:42.378Z] [INFO] "status": "pending"
[2026-01-26T19:13:42.378Z] [INFO] }
[2026-01-26T19:13:42.378Z] [INFO] ]
[2026-01-26T19:13:42.378Z] [INFO] },
[2026-01-26T19:13:42.378Z] [INFO] "output": "[\n {\n \"content\": \"Анализ проблемы: Изучить issue #169 и понять что не работает в ver8a\",\n \"id\": \"1\",\n \"priority\": \"high\",\n \"status\": \"in_progress\"\n },\n {\n \"content\": \"Проверить текущую структуру файлов ver8a и наличие необходимых файлов\",\n \"id\": \"2\",\n \"priority\": \"high\",\n \"status\": \"pending\"\n },\n {\n \"content\": \"Исследовать ошибки загрузки файлов RDF данных или URI\",\n \"id\": \"3\",\n \"priority\": \"high\",\n \"status\": \"pending\"\n },\n {\n \"content\": \"Проверить работу кнопок: Показать, Очистить, Тест, Сохранить как\",\n \"id\": \"4\",\n \"priority\": \"high\",\n \"status\": \"pending\"\n },\n {\n \"content\": \"Исправить отображение полей: Макс. длина VAD, Smart Design, Result in SPARQL\",\n \"id\": \"5\",\n \"priority\": \"high\",\n \"status\": \"pending\"\n },\n {\n \"content\": \"Протестировать все исправления и убедиться что работает\",\n \"id\": \"6\",\n \"priority\": \"medium\",\n \"status\": \"pending\"\n },\n {\n \"content\": \"Создать pull request с исправлениями\",\n \"id\": \"7\",\n \"priority\": \"medium\",\n \"status\": \"pending\"\n }\n]",
[2026-01-26T19:13:42.378Z] [INFO] "title": "7 todos",
[2026-01-26T19:13:42.379Z] [INFO] "metadata": {
[2026-01-26T19:13:42.379Z] [INFO] "todos": [
[2026-01-26T19:13:42.379Z] [INFO] {
[2026-01-26T19:13:42.379Z] [INFO] "content": "Анализ проблемы: Изучить issue #169 и понять что не работает в ver8a",
[2026-01-26T19:13:42.379Z] [INFO] "id": "1",
[2026-01-26T19:13:42.379Z] [INFO] "priority": "high",
[2026-01-26T19:13:42.379Z] [INFO] "status": "in_progress"
[2026-01-26T19:13:42.380Z] [INFO] },
[2026-01-26T19:13:42.380Z] [INFO] {
[2026-01-26T19:13:42.380Z] [INFO] "content": "Проверить текущую структуру файлов ver8a и наличие необходимых файлов",
[2026-01-26T19:13:42.380Z] [INFO] "id": "2",
[2026-01-26T19:13:42.380Z] [INFO] "priority": "high",
[2026-01-26T19:13:42.380Z] [INFO] "status": "pending"
[2026-01-26T19:13:42.380Z] [INFO] },
[2026-01-26T19:13:42.381Z] [INFO] {
[2026-01-26T19:13:42.381Z] [INFO] "content": "Исследовать ошибки загрузки файлов RDF данных или URI",
[2026-01-26T19:13:42.381Z] [INFO] "id": "3",
[2026-01-26T19:13:42.381Z] [INFO] "priority": "high",
[2026-01-26T19:13:42.381Z] [INFO] "status": "pending"
[2026-01-26T19:13:42.381Z] [INFO] },
[2026-01-26T19:13:42.382Z] [INFO] {
[2026-01-26T19:13:42.382Z] [INFO] "content": "Проверить работу кнопок: Показать, Очистить, Тест, Сохранить как",
[2026-01-26T19:13:42.382Z] [INFO] "id": "4",
[2026-01-26T19:13:42.383Z] [INFO] "priority": "high",
[2026-01-26T19:13:42.383Z] [INFO] "status": "pending"
[2026-01-26T19:13:42.383Z] [INFO] },
[2026-01-26T19:13:42.383Z] [INFO] {
[2026-01-26T19:13:42.383Z] [INFO] "content": "Исправить отображение полей: Макс. длина VAD, Smart Design, Result in SPARQL",
[2026-01-26T19:13:42.383Z] [INFO] "id": "5",
[2026-01-26T19:13:42.383Z] [INFO] "priority": "high",
[2026-01-26T19:13:42.384Z] [INFO] "status": "pending"
[2026-01-26T19:13:42.384Z] [INFO] },
[2026-01-26T19:13:42.384Z] [INFO] {
[2026-01-26T19:13:42.384Z] [INFO] "content": "Протестировать все исправления и убедиться что работает",
[2026-01-26T19:13:42.384Z] [INFO] "id": "6",
[2026-01-26T19:13:42.384Z] [INFO] "priority": "medium",
[2026-01-26T19:13:42.384Z] [INFO] "status": "pending"
[2026-01-26T19:13:42.385Z] [INFO] },
[2026-01-26T19:13:42.385Z] [INFO] {
[2026-01-26T19:13:42.385Z] [INFO] "content": "Создать pull request с исправлениями",
[2026-01-26T19:13:42.385Z] [INFO] "id": "7",
[2026-01-26T19:13:42.386Z] [INFO] "priority": "medium",
[2026-01-26T19:13:42.386Z] [INFO] "status": "pending"
[2026-01-26T19:13:42.386Z] [INFO] }
[2026-01-26T19:13:42.386Z] [INFO] ]
[2026-01-26T19:13:42.386Z] [INFO] },
[2026-01-26T19:13:42.386Z] [INFO] "time": {
[2026-01-26T19:13:42.387Z] [INFO] "start": 1769454822363,
[2026-01-26T19:13:42.387Z] [INFO] "end": 1769454822364
[2026-01-26T19:13:42.387Z] [INFO] }
[2026-01-26T19:13:42.387Z] [INFO] }
[2026-01-26T19:13:42.387Z] [INFO] }
[2026-01-26T19:13:42.387Z] [INFO] }
[2026-01-26T19:13:42.395Z] [INFO] {
[2026-01-26T19:13:42.396Z] [INFO] "type": "text",
[2026-01-26T19:13:42.396Z] [INFO] "timestamp": 1769454822395,
[2026-01-26T19:13:42.396Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:42.396Z] [INFO] "part": {
[2026-01-26T19:13:42.396Z] [INFO] "id": "prt_bfbb9c0470023CKWQr4qJpsS3y",
[2026-01-26T19:13:42.397Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:42.397Z] [INFO] "messageID": "msg_bfbb9aa9c001ts6uiKsgIGcUnW",
[2026-01-26T19:13:42.398Z] [INFO] "type": "text",
[2026-01-26T19:13:42.398Z] [INFO] "text": "\nNow let me create a detailed plan to solve this issue based on what I've learned:",
[2026-01-26T19:13:42.398Z] [INFO] "time": {
[2026-01-26T19:13:42.398Z] [INFO] "start": 1769454822394,
[2026-01-26T19:13:42.398Z] [INFO] "end": 1769454822394
[2026-01-26T19:13:42.398Z] [INFO] }
[2026-01-26T19:13:42.398Z] [INFO] }
[2026-01-26T19:13:42.399Z] [INFO] }
[2026-01-26T19:13:42.404Z] [INFO] {
[2026-01-26T19:13:42.405Z] [INFO] "type": "step_finish",
[2026-01-26T19:13:42.405Z] [INFO] "timestamp": 1769454822403,
[2026-01-26T19:13:42.405Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:42.405Z] [INFO] "part": {
[2026-01-26T19:13:42.405Z] [INFO] "id": "prt_bfbb9e3fb001xii9wllmnqikyL",
[2026-01-26T19:13:42.406Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:42.406Z] [INFO] "messageID": "msg_bfbb9aa9c001ts6uiKsgIGcUnW",
[2026-01-26T19:13:42.406Z] [INFO] "type": "step-finish",
[2026-01-26T19:13:42.406Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:13:42.406Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08",
[2026-01-26T19:13:42.406Z] [INFO] "cost": 0,
[2026-01-26T19:13:42.406Z] [INFO] "tokens": {
[2026-01-26T19:13:42.407Z] [INFO] "input": 26646,
[2026-01-26T19:13:42.407Z] [INFO] "output": 313,
[2026-01-26T19:13:42.407Z] [INFO] "reasoning": 1,
[2026-01-26T19:13:42.407Z] [INFO] "cache": {
[2026-01-26T19:13:42.408Z] [INFO] "read": 45286,
[2026-01-26T19:13:42.408Z] [INFO] "write": 0
[2026-01-26T19:13:42.408Z] [INFO] }
[2026-01-26T19:13:42.408Z] [INFO] }
[2026-01-26T19:13:42.409Z] [INFO] }
[2026-01-26T19:13:42.409Z] [INFO] }
[2026-01-26T19:13:44.681Z] [INFO] {
[2026-01-26T19:13:44.682Z] [INFO] "type": "step_start",
[2026-01-26T19:13:44.682Z] [INFO] "timestamp": 1769454824675,
[2026-01-26T19:13:44.682Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:44.682Z] [INFO] "part": {
[2026-01-26T19:13:44.682Z] [INFO] "id": "prt_bfbb9ece2001QPnHa75LUQrbTN",
[2026-01-26T19:13:44.683Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:44.683Z] [INFO] "messageID": "msg_bfbb9e4160017YNWIZpksUJalV",
[2026-01-26T19:13:44.684Z] [INFO] "type": "step-start",
[2026-01-26T19:13:44.684Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08"
[2026-01-26T19:13:44.684Z] [INFO] }
[2026-01-26T19:13:44.685Z] [INFO] }
[2026-01-26T19:13:47.459Z] [INFO] {
[2026-01-26T19:13:47.459Z] [INFO] "type": "tool_use",
[2026-01-26T19:13:47.460Z] [INFO] "timestamp": 1769454827458,
[2026-01-26T19:13:47.460Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:47.460Z] [INFO] "part": {
[2026-01-26T19:13:47.460Z] [INFO] "id": "prt_bfbb9f7b9001m3p3psjFN2Cvlq",
[2026-01-26T19:13:47.460Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:47.460Z] [INFO] "messageID": "msg_bfbb9e4160017YNWIZpksUJalV",
[2026-01-26T19:13:47.460Z] [INFO] "type": "tool",
[2026-01-26T19:13:47.461Z] [INFO] "callID": "call_e55bb38e728844838115187c",
[2026-01-26T19:13:47.461Z] [INFO] "tool": "list",
[2026-01-26T19:13:47.461Z] [INFO] "state": {
[2026-01-26T19:13:47.461Z] [INFO] "status": "completed",
[2026-01-26T19:13:47.461Z] [INFO] "input": {
[2026-01-26T19:13:47.461Z] [INFO] "path": "/tmp/gh-issue-solver-1769454766276/ver8a"
[2026-01-26T19:13:47.462Z] [INFO] },
[2026-01-26T19:13:47.462Z] [INFO] "output": "/tmp/gh-issue-solver-1769454766276/ver8a/\n doc/\n problem.md\n old/\n readme.md\n Trig_VADv4.ttl\n filelist.md\n index.html\n readme.md\n sparql-queries-choice.js\n sparql-queries-code.js\n styles.css\n vad-basic-ontology.ttl\n vad-basic-ontology_tech_Appendix.ttl\n vad-validation-rules.js\n virtualRDFdata_VADv4.ttl\n",
[2026-01-26T19:13:47.462Z] [INFO] "title": "ver8a",
[2026-01-26T19:13:47.462Z] [INFO] "metadata": {
[2026-01-26T19:13:47.462Z] [INFO] "count": 13,
[2026-01-26T19:13:47.462Z] [INFO] "truncated": false
[2026-01-26T19:13:47.463Z] [INFO] },
[2026-01-26T19:13:47.463Z] [INFO] "time": {
[2026-01-26T19:13:47.464Z] [INFO] "start": 1769454827451,
[2026-01-26T19:13:47.465Z] [INFO] "end": 1769454827458
[2026-01-26T19:13:47.465Z] [INFO] }
[2026-01-26T19:13:47.465Z] [INFO] }
[2026-01-26T19:13:47.465Z] [INFO] }
[2026-01-26T19:13:47.466Z] [INFO] }
[2026-01-26T19:13:47.492Z] [INFO] {
[2026-01-26T19:13:47.493Z] [INFO] "type": "text",
[2026-01-26T19:13:47.493Z] [INFO] "timestamp": 1769454827492,
[2026-01-26T19:13:47.493Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:47.494Z] [INFO] "part": {
[2026-01-26T19:13:47.494Z] [INFO] "id": "prt_bfbb9ed67001BRFhI8pRPrtLpE",
[2026-01-26T19:13:47.495Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:47.495Z] [INFO] "messageID": "msg_bfbb9e4160017YNWIZpksUJalV",
[2026-01-26T19:13:47.495Z] [INFO] "type": "text",
[2026-01-26T19:13:47.495Z] [INFO] "text": "\nАнализ проблемы показывает:\n\n1. **Проблема**: В ver8a не работает загрузка RDF данных, кнопки \"Показать\", \"Очистить\", \"Тест\", \"Сохранить как\"\n2. **Проблема**: Не отображаются поля \"Макс. длина VAD:\", окна Smart Design, Result in SPARQL\n3. **Проблема**: Даже копирование через буфер обмена не работает\n\nПроверю структуру файлов:",
[2026-01-26T19:13:47.495Z] [INFO] "time": {
[2026-01-26T19:13:47.495Z] [INFO] "start": 1769454827491,
[2026-01-26T19:13:47.495Z] [INFO] "end": 1769454827491
[2026-01-26T19:13:47.496Z] [INFO] }
[2026-01-26T19:13:47.496Z] [INFO] }
[2026-01-26T19:13:47.496Z] [INFO] }
[2026-01-26T19:13:47.502Z] [INFO] {
[2026-01-26T19:13:47.502Z] [INFO] "type": "step_finish",
[2026-01-26T19:13:47.502Z] [INFO] "timestamp": 1769454827501,
[2026-01-26T19:13:47.502Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:47.503Z] [INFO] "part": {
[2026-01-26T19:13:47.503Z] [INFO] "id": "prt_bfbb9f7e4001UJJ5H7HWu6Qc4A",
[2026-01-26T19:13:47.503Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:47.503Z] [INFO] "messageID": "msg_bfbb9e4160017YNWIZpksUJalV",
[2026-01-26T19:13:47.503Z] [INFO] "type": "step-finish",
[2026-01-26T19:13:47.504Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:13:47.504Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08",
[2026-01-26T19:13:47.504Z] [INFO] "cost": 0,
[2026-01-26T19:13:47.504Z] [INFO] "tokens": {
[2026-01-26T19:13:47.504Z] [INFO] "input": 339,
[2026-01-26T19:13:47.505Z] [INFO] "output": 139,
[2026-01-26T19:13:47.505Z] [INFO] "reasoning": 1,
[2026-01-26T19:13:47.505Z] [INFO] "cache": {
[2026-01-26T19:13:47.505Z] [INFO] "read": 72244,
[2026-01-26T19:13:47.505Z] [INFO] "write": 0
[2026-01-26T19:13:47.505Z] [INFO] }
[2026-01-26T19:13:47.505Z] [INFO] }
[2026-01-26T19:13:47.506Z] [INFO] }
[2026-01-26T19:13:47.506Z] [INFO] }
[2026-01-26T19:13:49.779Z] [INFO] {
[2026-01-26T19:13:49.779Z] [INFO] "type": "step_start",
[2026-01-26T19:13:49.779Z] [INFO] "timestamp": 1769454829778,
[2026-01-26T19:13:49.780Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:49.780Z] [INFO] "part": {
[2026-01-26T19:13:49.780Z] [INFO] "id": "prt_bfbba00d10011D3yUwB8jaDSt7",
[2026-01-26T19:13:49.780Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:49.781Z] [INFO] "messageID": "msg_bfbb9f7fc001L3FUTNorx33Muv",
[2026-01-26T19:13:49.781Z] [INFO] "type": "step-start",
[2026-01-26T19:13:49.781Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08"
[2026-01-26T19:13:49.781Z] [INFO] }
[2026-01-26T19:13:49.781Z] [INFO] }
[2026-01-26T19:13:50.687Z] [INFO] {
[2026-01-26T19:13:50.687Z] [INFO] "type": "tool_use",
[2026-01-26T19:13:50.688Z] [INFO] "timestamp": 1769454830686,
[2026-01-26T19:13:50.688Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:50.688Z] [INFO] "part": {
[2026-01-26T19:13:50.688Z] [INFO] "id": "prt_bfbba045c0018AJtinfJ7SyJfX",
[2026-01-26T19:13:50.689Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:50.689Z] [INFO] "messageID": "msg_bfbb9f7fc001L3FUTNorx33Muv",
[2026-01-26T19:13:50.689Z] [INFO] "type": "tool",
[2026-01-26T19:13:50.689Z] [INFO] "callID": "call_010c22e92d9b4f949fc69f65",
[2026-01-26T19:13:50.689Z] [INFO] "tool": "read",
[2026-01-26T19:13:50.689Z] [INFO] "state": {
[2026-01-26T19:13:50.689Z] [INFO] "status": "completed",
[2026-01-26T19:13:50.690Z] [INFO] "input": {
[2026-01-26T19:13:50.690Z] [INFO] "filePath": "/tmp/gh-issue-solver-1769454766276/ver8a/styles.css",
[2026-01-26T19:13:50.690Z] [INFO] "limit": 100
[2026-01-26T19:13:50.690Z] [INFO] },
[2026-01-26T19:13:50.690Z] [INFO] "output": "<file>\n00001| /* Основные стили страницы */\n00002| * {\n00003| box-sizing: border-box;\n00004| }\n00005| \n00006| body {\n00007| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n00008| max-width: 1400px;\n00009| margin: 0 auto;\n00010| padding: 20px;\n00011| background-color: #f5f5f5;\n00012| }\n00013| \n00014| h1 {\n00015| color: #333;\n00016| border-bottom: 2px solid #4CAF50;\n00017| padding-bottom: 10px;\n00018| }\n00019| \n00020| .description {\n00021| background-color: #e8f5e9;\n00022| padding: 15px;\n00023| border-radius: 5px;\n00024| margin-bottom: 20px;\n00025| }\n00026| \n00027| .example-status {\n00028| margin-top: 10px;\n00029| padding: 8px;\n00030| background-color: #fff3cd;\n00031| border: 1px solid #ffeaa7;\n00032| border-radius: 4px;\n00033| color: #856404;\n00034| font-size: 14px;\n00035| }\n00036| \n00037| .container {\n00038| background-color: white;\n00039| padding: 20px;\n00040| border-radius: 8px;\n00041| box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n00042| }\n00043| \n00044| .rdf-input-header {\n00045| display: flex;\n00046| align-items: center;\n00047| gap: 10px;\n00048| margin-bottom: 10px;\n00049| flex-wrap: wrap;\n00050| }\n00051| \n00052| .rdf-input-header label {\n00053| margin-bottom: 0;\n00054| flex: 1;\n00055| }\n00056| \n00057| .rdf-input-header button {\n00058| margin-top: 0;\n00059| padding: 8px 16px;\n00060| font-size: 14px;\n00061| }\n00062| \n00063| .clear-btn {\n00064| background-color: #dc3545;\n00065| }\n00066| \n00067| .clear-btn:hover {\n00068| background-color: #c82333;\n00069| }\n00070| \n00071| .save-as-btn {\n00072| background-color: #007bff;\n00073| }\n00074| \n00075| .save-as-btn:hover {\n00076| background-color: #0056b3;\n00077| }\n00078| \n00079| .load-btn {\n00080| background-color: #28a745;\n00081| }\n00082| \n00083| .load-btn:hover {\n00084| background-color: #1e7e34;\n00085| }\n00086| \n00087| label {\n00088| display: block;\n00089| margin-bottom: 5px;\n00090| font-weight: bold;\n00091| color: #555;\n00092| }\n00093| \n00094| textarea {\n00095| width: 100%;\n00096| height: 200px;\n00097| padding: 10px;\n00098| border: 1px solid #ddd;\n00099| border-radius: 4px;\n00100| font-family: 'Consolas', 'Monaco', monospace;\n\n(File has more lines. Use 'offset' parameter to read beyond line 100)\n</file>",
[2026-01-26T19:13:50.690Z] [INFO] "title": "ver8a/styles.css",
[2026-01-26T19:13:50.691Z] [INFO] "metadata": {
[2026-01-26T19:13:50.691Z] [INFO] "preview": " /* Основные стили страницы */\n * {\n box-sizing: border-box;\n }\n\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n max-width: 1400px;\n margin: 0 auto;\n padding: 20px;\n background-color: #f5f5f5;\n }\n\n h1 {\n color: #333;\n border-bottom: 2px solid #4CAF50;\n padding-bottom: 10px;\n }\n\n .description {"
[2026-01-26T19:13:50.691Z] [INFO] },
[2026-01-26T19:13:50.691Z] [INFO] "time": {
[2026-01-26T19:13:50.691Z] [INFO] "start": 1769454830685,
[2026-01-26T19:13:50.692Z] [INFO] "end": 1769454830686
[2026-01-26T19:13:50.692Z] [INFO] }
[2026-01-26T19:13:50.692Z] [INFO] }
[2026-01-26T19:13:50.692Z] [INFO] }
[2026-01-26T19:13:50.692Z] [INFO] }
[2026-01-26T19:13:50.720Z] [INFO] {
[2026-01-26T19:13:50.720Z] [INFO] "type": "text",
[2026-01-26T19:13:50.721Z] [INFO] "timestamp": 1769454830719,
[2026-01-26T19:13:50.721Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:50.721Z] [INFO] "part": {
[2026-01-26T19:13:50.722Z] [INFO] "id": "prt_bfbba0275001eiArVtNgv69HmG",
[2026-01-26T19:13:50.723Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:50.723Z] [INFO] "messageID": "msg_bfbb9f7fc001L3FUTNorx33Muv",
[2026-01-26T19:13:50.724Z] [INFO] "type": "text",
[2026-01-26T19:13:50.724Z] [INFO] "text": "",
[2026-01-26T19:13:50.724Z] [INFO] "time": {
[2026-01-26T19:13:50.724Z] [INFO] "start": 1769454830719,
[2026-01-26T19:13:50.724Z] [INFO] "end": 1769454830719
[2026-01-26T19:13:50.724Z] [INFO] }
[2026-01-26T19:13:50.724Z] [INFO] }
[2026-01-26T19:13:50.725Z] [INFO] }
[2026-01-26T19:13:50.727Z] [INFO] {
[2026-01-26T19:13:50.728Z] [INFO] "type": "step_finish",
[2026-01-26T19:13:50.728Z] [INFO] "timestamp": 1769454830727,
[2026-01-26T19:13:50.728Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:50.728Z] [INFO] "part": {
[2026-01-26T19:13:50.728Z] [INFO] "id": "prt_bfbba0480001snkF4WIIyx8mv1",
[2026-01-26T19:13:50.728Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:50.728Z] [INFO] "messageID": "msg_bfbb9f7fc001L3FUTNorx33Muv",
[2026-01-26T19:13:50.728Z] [INFO] "type": "step-finish",
[2026-01-26T19:13:50.728Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:13:50.729Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08",
[2026-01-26T19:13:50.729Z] [INFO] "cost": 0,
[2026-01-26T19:13:50.729Z] [INFO] "tokens": {
[2026-01-26T19:13:50.729Z] [INFO] "input": 123,
[2026-01-26T19:13:50.730Z] [INFO] "output": 45,
[2026-01-26T19:13:50.730Z] [INFO] "reasoning": 1,
[2026-01-26T19:13:50.730Z] [INFO] "cache": {
[2026-01-26T19:13:50.730Z] [INFO] "read": 72721,
[2026-01-26T19:13:50.730Z] [INFO] "write": 0
[2026-01-26T19:13:50.731Z] [INFO] }
[2026-01-26T19:13:50.731Z] [INFO] }
[2026-01-26T19:13:50.731Z] [INFO] }
[2026-01-26T19:13:50.731Z] [INFO] }
[2026-01-26T19:13:53.563Z] [INFO] {
[2026-01-26T19:13:53.564Z] [INFO] "type": "step_start",
[2026-01-26T19:13:53.564Z] [INFO] "timestamp": 1769454833562,
[2026-01-26T19:13:53.564Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:53.564Z] [INFO] "part": {
[2026-01-26T19:13:53.565Z] [INFO] "id": "prt_bfbba0f9a0019xauSSES6FpZ8I",
[2026-01-26T19:13:53.565Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:53.566Z] [INFO] "messageID": "msg_bfbba0499001ttLMbtqVnSTFyp",
[2026-01-26T19:13:53.566Z] [INFO] "type": "step-start",
[2026-01-26T19:13:53.566Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08"
[2026-01-26T19:13:53.566Z] [INFO] }
[2026-01-26T19:13:53.566Z] [INFO] }
[2026-01-26T19:13:54.842Z] [INFO] {
[2026-01-26T19:13:54.842Z] [INFO] "type": "tool_use",
[2026-01-26T19:13:54.843Z] [INFO] "timestamp": 1769454834841,
[2026-01-26T19:13:54.843Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:54.843Z] [INFO] "part": {
[2026-01-26T19:13:54.843Z] [INFO] "id": "prt_bfbba148f001SBiLuCwg9ygKZR",
[2026-01-26T19:13:54.843Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:54.844Z] [INFO] "messageID": "msg_bfbba0499001ttLMbtqVnSTFyp",
[2026-01-26T19:13:54.844Z] [INFO] "type": "tool",
[2026-01-26T19:13:54.844Z] [INFO] "callID": "call_8efbadd8f9734bfbab99a908",
[2026-01-26T19:13:54.844Z] [INFO] "tool": "grep",
[2026-01-26T19:13:54.844Z] [INFO] "state": {
[2026-01-26T19:13:54.844Z] [INFO] "status": "completed",
[2026-01-26T19:13:54.845Z] [INFO] "input": {
[2026-01-26T19:13:54.845Z] [INFO] "path": "/tmp/gh-issue-solver-1769454766276/ver8a",
[2026-01-26T19:13:54.845Z] [INFO] "pattern": "function visualize\\(\\)"
[2026-01-26T19:13:54.845Z] [INFO] },
[2026-01-26T19:13:54.845Z] [INFO] "output": "Found 1 matches\n/tmp/gh-issue-solver-1769454766276/ver8a/index.html:\n Line 7216: async function visualize() {",
[2026-01-26T19:13:54.845Z] [INFO] "title": "function visualize\\(\\)",
[2026-01-26T19:13:54.845Z] [INFO] "metadata": {
[2026-01-26T19:13:54.845Z] [INFO] "matches": 1,
[2026-01-26T19:13:54.846Z] [INFO] "truncated": false
[2026-01-26T19:13:54.846Z] [INFO] },
[2026-01-26T19:13:54.846Z] [INFO] "time": {
[2026-01-26T19:13:54.846Z] [INFO] "start": 1769454834834,
[2026-01-26T19:13:54.846Z] [INFO] "end": 1769454834841
[2026-01-26T19:13:54.846Z] [INFO] }
[2026-01-26T19:13:54.846Z] [INFO] }
[2026-01-26T19:13:54.846Z] [INFO] }
[2026-01-26T19:13:54.846Z] [INFO] }
[2026-01-26T19:13:54.907Z] [INFO] {
[2026-01-26T19:13:54.907Z] [INFO] "type": "text",
[2026-01-26T19:13:54.908Z] [INFO] "timestamp": 1769454834906,
[2026-01-26T19:13:54.909Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:54.909Z] [INFO] "part": {
[2026-01-26T19:13:54.909Z] [INFO] "id": "prt_bfbba1022001mpBTR3G8JOSF3M",
[2026-01-26T19:13:54.909Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:54.909Z] [INFO] "messageID": "msg_bfbba0499001ttLMbtqVnSTFyp",
[2026-01-26T19:13:54.910Z] [INFO] "type": "text",
[2026-01-26T19:13:54.910Z] [INFO] "text": "\nТеперь проверю, работают ли основные функции. Сначала посмотрим на функцию visualize():",
[2026-01-26T19:13:54.910Z] [INFO] "time": {
[2026-01-26T19:13:54.910Z] [INFO] "start": 1769454834905,
[2026-01-26T19:13:54.910Z] [INFO] "end": 1769454834905
[2026-01-26T19:13:54.910Z] [INFO] }
[2026-01-26T19:13:54.911Z] [INFO] }
[2026-01-26T19:13:54.911Z] [INFO] }
[2026-01-26T19:13:54.914Z] [INFO] {
[2026-01-26T19:13:54.914Z] [INFO] "type": "step_finish",
[2026-01-26T19:13:54.915Z] [INFO] "timestamp": 1769454834914,
[2026-01-26T19:13:54.915Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:54.915Z] [INFO] "part": {
[2026-01-26T19:13:54.915Z] [INFO] "id": "prt_bfbba14db0012V8x3pjV6QxMes",
[2026-01-26T19:13:54.915Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:13:54.915Z] [INFO] "messageID": "msg_bfbba0499001ttLMbtqVnSTFyp",
[2026-01-26T19:13:54.916Z] [INFO] "type": "step-finish",
[2026-01-26T19:13:54.916Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:13:54.916Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08",
[2026-01-26T19:13:54.916Z] [INFO] "cost": 0,
[2026-01-26T19:13:54.916Z] [INFO] "tokens": {
[2026-01-26T19:13:54.916Z] [INFO] "input": 915,
[2026-01-26T19:13:54.916Z] [INFO] "output": 65,
[2026-01-26T19:13:54.917Z] [INFO] "reasoning": 1,
[2026-01-26T19:13:54.917Z] [INFO] "cache": {
[2026-01-26T19:13:54.917Z] [INFO] "read": 72888,
[2026-01-26T19:13:54.917Z] [INFO] "write": 0
[2026-01-26T19:13:54.917Z] [INFO] }
[2026-01-26T19:13:54.917Z] [INFO] }
[2026-01-26T19:13:54.918Z] [INFO] }
[2026-01-26T19:13:54.918Z] [INFO] }
[2026-01-26T19:16:09.638Z] [INFO] {
[2026-01-26T19:16:09.638Z] [INFO] "type": "step_start",
[2026-01-26T19:16:09.638Z] [INFO] "timestamp": 1769454969637,
[2026-01-26T19:16:09.639Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:16:09.639Z] [INFO] "part": {
[2026-01-26T19:16:09.639Z] [INFO] "id": "prt_bfbbc2324001xyWYdF0SjQtuMk",
[2026-01-26T19:16:09.639Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:16:09.639Z] [INFO] "messageID": "msg_bfbba14f20013lIF8g9VTpFgyo",
[2026-01-26T19:16:09.639Z] [INFO] "type": "step-start",
[2026-01-26T19:16:09.640Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08"
[2026-01-26T19:16:09.640Z] [INFO] }
[2026-01-26T19:16:09.640Z] [INFO] }
[2026-01-26T19:16:10.894Z] [INFO] {
[2026-01-26T19:16:10.894Z] [INFO] "type": "tool_use",
[2026-01-26T19:16:10.894Z] [INFO] "timestamp": 1769454970893,
[2026-01-26T19:16:10.895Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:16:10.895Z] [INFO] "part": {
[2026-01-26T19:16:10.895Z] [INFO] "id": "prt_bfbbc2808001pmtSfeS0okFYpR",
[2026-01-26T19:16:10.895Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:16:10.895Z] [INFO] "messageID": "msg_bfbba14f20013lIF8g9VTpFgyo",
[2026-01-26T19:16:10.895Z] [INFO] "type": "tool",
[2026-01-26T19:16:10.895Z] [INFO] "callID": "call_d32d8011350c47a5891a5fe0",
[2026-01-26T19:16:10.896Z] [INFO] "tool": "read",
[2026-01-26T19:16:10.896Z] [INFO] "state": {
[2026-01-26T19:16:10.896Z] [INFO] "status": "completed",
[2026-01-26T19:16:10.896Z] [INFO] "input": {
[2026-01-26T19:16:10.896Z] [INFO] "filePath": "/tmp/gh-issue-solver-1769454766276/ver8a/index.html",
[2026-01-26T19:16:10.896Z] [INFO] "limit": 100,
[2026-01-26T19:16:10.897Z] [INFO] "offset": 7216
[2026-01-26T19:16:10.897Z] [INFO] },
[2026-01-26T19:16:10.897Z] [INFO] "output": "<file>\n07217| const rdfInput = document.getElementById('rdf-input').value.trim();\n07218| const inputFormat = document.getElementById('input-format').value;\n07219| const outputFormat = document.getElementById('output-format').value;\n07220| const layoutEngine = document.getElementById('layout-engine').value;\n07221| const visualizationMode = document.getElementById('visualization-mode').value;\n07222| \n07223| const maxLabelLengthInput = document.getElementById('max-label-length');\n07224| const maxLabelLengthValue = parseInt(maxLabelLengthInput.value, 10);\n07225| if (!isNaN(maxLabelLengthValue) && maxLabelLengthValue >= 5 && maxLabelLengthValue <= 200) {\n07226| currentMaxLabelLength = maxLabelLengthValue;\n07227| } else {\n07228| currentMaxLabelLength = DEFAULT_MAX_LABEL_LENGTH;\n07229| maxLabelLengthInput.value = DEFAULT_MAX_LABEL_LENGTH;\n07230| }\n07231| \n07232| // Чтение параметра \"Макс. длина VAD\" для режима VAD\n07233| const maxVadRowLengthInput = document.getElementById('max-vad-row-length');\n07234| const maxVadRowLengthValue = parseInt(maxVadRowLengthInput.value, 10);\n07235| if (!isNaN(maxVadRowLengthValue) && maxVadRowLengthValue >= 2 && maxVadRowLengthValue <= 20) {\n07236| currentMaxVadRowLength = maxVadRowLengthValue;\n07237| } else {\n07238| currentMaxVadRowLength = DEFAULT_MAX_VAD_ROW_LENGTH;\n07239| maxVadRowLengthInput.value = DEFAULT_MAX_VAD_ROW_LENGTH;\n07240| }\n07241| \n07242| currentMode = visualizationMode;\n07243| \n07244| if (!rdfInput) {\n07245| showError('Пожалуйста, введите RDF данные');\n07246| return;\n07247| }\n07248| \n07249| showLoading();\n07250| \n07251| const button = document.getElementById('visualize-btn');\n07252| button.disabled = true;\n07253| button.textContent = 'Обработка...';\n07254| \n07255| try {\n07256| const parser = new N3.Parser({ format: inputFormat });\n07257| const quads = [];\n07258| let prefixes = {};\n07259| \n07260| await new Promise((resolve, reject) => {\n07261| parser.parse(rdfInput, (error, quad, parsedPrefixes) => {\n07262| if (error) {\n07263| reject(error);\n07264| return;\n07265| }\n07266| if (quad) {\n07267| quads.push(quad);\n07268| } else {\n07269| if (parsedPrefixes) {\n07270| prefixes = parsedPrefixes;\n07271| }\n07272| resolve();\n07273| }\n07274| });\n07275| });\n07276| \n07277| currentPrefixes = prefixes;\n07278| currentQuads = quads;\n07279| currentStore = null;\n07280| \n07281| if (quads.length === 0) {\n07282| showError('Не найдено RDF триплетов в данных');\n07283| return;\n07284| }\n07285| \n07286| // Валидация для режима VAD\n07287| if (currentMode === 'vad') {\n07288| const validation = validateVAD(quads, prefixes);\n07289| if (!validation.valid) {\n07290| showValidationError(formatVADErrors(validation.errors));\n07291| button.disabled = false;\n07292| button.textContent = 'Показать';\n07293| return;\n07294| }\n07295| }\n07296| \n07297| // Обработка режима VAD TriG\n07298| if (currentMode === 'vad-trig') {\n07299| // Парсим иерархию TriG графов\n07300| const hierarchyResult = parseTriGHierarchy(quads, prefixes);\n07301| \n07302| if (!hierarchyResult.valid) {\n07303| showValidationError(formatVADTriGErrors(hierarchyResult.errors));\n07304| toggleVADTriGPanels(false);\n07305| button.disabled = false;\n07306| button.textContent = 'Показать';\n07307| return;\n07308| }\n07309| \n07310| // Сохраняем иерархию\n07311| trigHierarchy = hierarchyResult.hierarchy;\n07312| \n07313| // Выбираем корневой TriG для начального отображения\n07314| // Используем первый из корневых TriG, если есть\n07315| selectedTrigUri = hierarchyResult.rootTrigUris.length > 0 ? hierarchyResult.rootTrigUris[0] : null;\n07316| \n\n(File has more lines. Use 'offset' parameter to read beyond line 7316)\n</file>",
[2026-01-26T19:16:10.897Z] [INFO] "title": "ver8a/index.html",
[2026-01-26T19:16:10.897Z] [INFO] "metadata": {
[2026-01-26T19:16:10.897Z] [INFO] "preview": " const rdfInput = document.getElementById('rdf-input').value.trim();\n const inputFormat = document.getElementById('input-format').value;\n const outputFormat = document.getElementById('output-format').value;\n const layoutEngine = document.getElementById('layout-engine').value;\n const visualizationMode = document.getElementById('visualization-mode').value;\n\n const maxLabelLengthInput = document.getElementById('max-label-length');\n const maxLabelLengthValue = parseInt(maxLabelLengthInput.value, 10);\n if (!isNaN(maxLabelLengthValue) && maxLabelLengthValue >= 5 && maxLabelLengthValue <= 200) {\n currentMaxLabelLength = maxLabelLengthValue;\n } else {\n currentMaxLabelLength = DEFAULT_MAX_LABEL_LENGTH;\n maxLabelLengthInput.value = DEFAULT_MAX_LABEL_LENGTH;\n }\n\n // Чтение параметра \"Макс. длина VAD\" для режима VAD\n const maxVadRowLengthInput = document.getElementById('max-vad-row-length');\n const maxVadRowLengthValue = parseInt(maxVadRowLengthInput.value, 10);\n if (!isNaN(maxVadRowLengthValue) && maxVadRowLengthValue >= 2 && maxVadRowLengthValue <= 20) {\n currentMaxVadRowLength = maxVadRowLengthValue;"
[2026-01-26T19:16:10.897Z] [INFO] },
[2026-01-26T19:16:10.898Z] [INFO] "time": {
[2026-01-26T19:16:10.898Z] [INFO] "start": 1769454970890,
[2026-01-26T19:16:10.898Z] [INFO] "end": 1769454970893
[2026-01-26T19:16:10.898Z] [INFO] }
[2026-01-26T19:16:10.898Z] [INFO] }
[2026-01-26T19:16:10.898Z] [INFO] }
[2026-01-26T19:16:10.898Z] [INFO] }
[2026-01-26T19:16:10.927Z] [INFO] {
[2026-01-26T19:16:10.929Z] [INFO] "type": "text",
[2026-01-26T19:16:10.930Z] [INFO] "timestamp": 1769454970927,
[2026-01-26T19:16:10.930Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:16:10.931Z] [INFO] "part": {
[2026-01-26T19:16:10.931Z] [INFO] "id": "prt_bfbbc2500001iVHcFmtvQLjLLd",
[2026-01-26T19:16:10.931Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:16:10.931Z] [INFO] "messageID": "msg_bfbba14f20013lIF8g9VTpFgyo",
[2026-01-26T19:16:10.931Z] [INFO] "type": "text",
[2026-01-26T19:16:10.931Z] [INFO] "text": "",
[2026-01-26T19:16:10.931Z] [INFO] "time": {
[2026-01-26T19:16:10.932Z] [INFO] "start": 1769454970926,
[2026-01-26T19:16:10.932Z] [INFO] "end": 1769454970926
[2026-01-26T19:16:10.932Z] [INFO] }
[2026-01-26T19:16:10.932Z] [INFO] }
[2026-01-26T19:16:10.933Z] [INFO] }
[2026-01-26T19:16:10.942Z] [INFO] {
[2026-01-26T19:16:10.943Z] [INFO] "type": "step_finish",
[2026-01-26T19:16:10.943Z] [INFO] "timestamp": 1769454970942,
[2026-01-26T19:16:10.943Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:16:10.943Z] [INFO] "part": {
[2026-01-26T19:16:10.943Z] [INFO] "id": "prt_bfbbc2830001S6Fqjpjf03UtqI",
[2026-01-26T19:16:10.943Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:16:10.943Z] [INFO] "messageID": "msg_bfbba14f20013lIF8g9VTpFgyo",
[2026-01-26T19:16:10.943Z] [INFO] "type": "step-finish",
[2026-01-26T19:16:10.944Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:16:10.944Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08",
[2026-01-26T19:16:10.944Z] [INFO] "cost": 0,
[2026-01-26T19:16:10.944Z] [INFO] "tokens": {
[2026-01-26T19:16:10.944Z] [INFO] "input": 58350,
[2026-01-26T19:16:10.944Z] [INFO] "output": 55,
[2026-01-26T19:16:10.944Z] [INFO] "reasoning": 1,
[2026-01-26T19:16:10.944Z] [INFO] "cache": {
[2026-01-26T19:16:10.944Z] [INFO] "read": 15563,
[2026-01-26T19:16:10.945Z] [INFO] "write": 0
[2026-01-26T19:16:10.945Z] [INFO] }
[2026-01-26T19:16:10.945Z] [INFO] }
[2026-01-26T19:16:10.945Z] [INFO] }
[2026-01-26T19:16:10.945Z] [INFO] }
[2026-01-26T19:18:07.828Z] [INFO] {
[2026-01-26T19:18:07.829Z] [INFO] "type": "error",
[2026-01-26T19:18:07.829Z] [INFO] "timestamp": 1769455087827,
[2026-01-26T19:18:07.830Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:18:07.830Z] [INFO] "error": "The operation timed out."
[2026-01-26T19:18:07.830Z] [INFO] }
[2026-01-26T19:18:21.341Z] [INFO] {
[2026-01-26T19:18:21.341Z] [INFO] "type": "step_start",
[2026-01-26T19:18:21.341Z] [INFO] "timestamp": 1769455101340,
[2026-01-26T19:18:21.342Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:18:21.342Z] [INFO] "part": {
[2026-01-26T19:18:21.342Z] [INFO] "id": "prt_bfbbe259b001I0KrJM3yAhnrtA",
[2026-01-26T19:18:21.342Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:18:21.342Z] [INFO] "messageID": "msg_bfbbc2853001T368yg4t3lHCTl",
[2026-01-26T19:18:21.343Z] [INFO] "type": "step-start",
[2026-01-26T19:18:21.343Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08"
[2026-01-26T19:18:21.343Z] [INFO] }
[2026-01-26T19:18:21.343Z] [INFO] }
[2026-01-26T19:18:53.901Z] [INFO] {
[2026-01-26T19:18:53.902Z] [INFO] "type": "tool_use",
[2026-01-26T19:18:53.902Z] [INFO] "timestamp": 1769455133901,
[2026-01-26T19:18:53.902Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:18:53.903Z] [INFO] "part": {
[2026-01-26T19:18:53.903Z] [INFO] "id": "prt_bfbbea4c10013ZuwNgjS8hfx44",
[2026-01-26T19:18:53.903Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:18:53.903Z] [INFO] "messageID": "msg_bfbbc2853001T368yg4t3lHCTl",
[2026-01-26T19:18:53.903Z] [INFO] "type": "tool",
[2026-01-26T19:18:53.903Z] [INFO] "callID": "call_581b410adee04bf98ca4a2df",
[2026-01-26T19:18:53.903Z] [INFO] "tool": "grep",
[2026-01-26T19:18:53.903Z] [INFO] "state": {
[2026-01-26T19:18:53.903Z] [INFO] "status": "completed",
[2026-01-26T19:18:53.903Z] [INFO] "input": {
[2026-01-26T19:18:53.904Z] [INFO] "path": "/tmp/gh-issue-solver-1769454766276/ver8a",
[2026-01-26T19:18:53.904Z] [INFO] "pattern": "function showError\\(|function showLoading\\(|function showValidationError\\("
[2026-01-26T19:18:53.904Z] [INFO] },
[2026-01-26T19:18:53.904Z] [INFO] "output": "Found 3 matches\n/tmp/gh-issue-solver-1769454766276/ver8a/index.html:\n Line 4365: function showLoading() {\n Line 4404: function showError(message) {\n Line 4434: function showValidationError(message) {",
[2026-01-26T19:18:53.904Z] [INFO] "title": "function showError\\(|function showLoading\\(|function showValidationError\\(",
[2026-01-26T19:18:53.904Z] [INFO] "metadata": {
[2026-01-26T19:18:53.904Z] [INFO] "matches": 3,
[2026-01-26T19:18:53.904Z] [INFO] "truncated": false
[2026-01-26T19:18:53.905Z] [INFO] },
[2026-01-26T19:18:53.905Z] [INFO] "time": {
[2026-01-26T19:18:53.905Z] [INFO] "start": 1769455133891,
[2026-01-26T19:18:53.905Z] [INFO] "end": 1769455133901
[2026-01-26T19:18:53.905Z] [INFO] }
[2026-01-26T19:18:53.905Z] [INFO] }
[2026-01-26T19:18:53.905Z] [INFO] }
[2026-01-26T19:18:53.905Z] [INFO] }
[2026-01-26T19:18:53.929Z] [INFO] {
[2026-01-26T19:18:53.930Z] [INFO] "type": "text",
[2026-01-26T19:18:53.931Z] [INFO] "timestamp": 1769455133929,
[2026-01-26T19:18:53.931Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:18:53.931Z] [INFO] "part": {
[2026-01-26T19:18:53.932Z] [INFO] "id": "prt_bfbbe2a06001bBRpmYv921CT5b",
[2026-01-26T19:18:53.932Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:18:53.932Z] [INFO] "messageID": "msg_bfbbc2853001T368yg4t3lHCTl",
[2026-01-26T19:18:53.932Z] [INFO] "type": "text",
[2026-01-26T19:18:53.932Z] [INFO] "text": "\nТеперь проверим, есть ли необходимые функции для отображения ошибок и загрузки:",
[2026-01-26T19:18:53.932Z] [INFO] "time": {
[2026-01-26T19:18:53.932Z] [INFO] "start": 1769455133928,
[2026-01-26T19:18:53.933Z] [INFO] "end": 1769455133928
[2026-01-26T19:18:53.933Z] [INFO] }
[2026-01-26T19:18:53.933Z] [INFO] }
[2026-01-26T19:18:53.933Z] [INFO] }
[2026-01-26T19:18:53.936Z] [INFO] {
[2026-01-26T19:18:53.937Z] [INFO] "type": "step_finish",
[2026-01-26T19:18:53.937Z] [INFO] "timestamp": 1769455133936,
[2026-01-26T19:18:53.937Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:18:53.937Z] [INFO] "part": {
[2026-01-26T19:18:53.937Z] [INFO] "id": "prt_bfbbea4e9001id1DubMIUT4nob",
[2026-01-26T19:18:53.937Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:18:53.938Z] [INFO] "messageID": "msg_bfbbc2853001T368yg4t3lHCTl",
[2026-01-26T19:18:53.938Z] [INFO] "type": "step-finish",
[2026-01-26T19:18:53.938Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:18:53.938Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08",
[2026-01-26T19:18:53.938Z] [INFO] "cost": 0,
[2026-01-26T19:18:53.938Z] [INFO] "tokens": {
[2026-01-26T19:18:53.938Z] [INFO] "input": 1243,
[2026-01-26T19:18:53.938Z] [INFO] "output": 72,
[2026-01-26T19:18:53.938Z] [INFO] "reasoning": 1,
[2026-01-26T19:18:53.939Z] [INFO] "cache": {
[2026-01-26T19:18:53.939Z] [INFO] "read": 73967,
[2026-01-26T19:18:53.939Z] [INFO] "write": 0
[2026-01-26T19:18:53.939Z] [INFO] }
[2026-01-26T19:18:53.939Z] [INFO] }
[2026-01-26T19:18:53.939Z] [INFO] }
[2026-01-26T19:18:53.939Z] [INFO] }
[2026-01-26T19:19:10.492Z] [INFO] {
[2026-01-26T19:19:10.492Z] [INFO] "type": "step_start",
[2026-01-26T19:19:10.493Z] [INFO] "timestamp": 1769455150491,
[2026-01-26T19:19:10.493Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:10.493Z] [INFO] "part": {
[2026-01-26T19:19:10.493Z] [INFO] "id": "prt_bfbbee59a001N0lW70tGwrnvsm",
[2026-01-26T19:19:10.493Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:10.493Z] [INFO] "messageID": "msg_bfbbea504001yJcFq47G0FG686",
[2026-01-26T19:19:10.493Z] [INFO] "type": "step-start",
[2026-01-26T19:19:10.494Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08"
[2026-01-26T19:19:10.494Z] [INFO] }
[2026-01-26T19:19:10.494Z] [INFO] }
[2026-01-26T19:19:13.688Z] [INFO] {
[2026-01-26T19:19:13.688Z] [INFO] "type": "tool_use",
[2026-01-26T19:19:13.688Z] [INFO] "timestamp": 1769455153687,
[2026-01-26T19:19:13.688Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:13.688Z] [INFO] "part": {
[2026-01-26T19:19:13.688Z] [INFO] "id": "prt_bfbbef214001czjdA9EPEfF130",
[2026-01-26T19:19:13.689Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:13.689Z] [INFO] "messageID": "msg_bfbbea504001yJcFq47G0FG686",
[2026-01-26T19:19:13.689Z] [INFO] "type": "tool",
[2026-01-26T19:19:13.689Z] [INFO] "callID": "call_6a269874c5e54e3d9f9623c8",
[2026-01-26T19:19:13.689Z] [INFO] "tool": "read",
[2026-01-26T19:19:13.689Z] [INFO] "state": {
[2026-01-26T19:19:13.689Z] [INFO] "status": "completed",
[2026-01-26T19:19:13.689Z] [INFO] "input": {
[2026-01-26T19:19:13.690Z] [INFO] "filePath": "/tmp/gh-issue-solver-1769454766276/ver8a/Trig_VADv4.ttl",
[2026-01-26T19:19:13.690Z] [INFO] "limit": 50
[2026-01-26T19:19:13.690Z] [INFO] },
[2026-01-26T19:19:13.690Z] [INFO] "output": "<file>\n00001| # Пример TriG VADv4 (Value Added Chain Diagram с иерархией TriG)\n00002| # Демонстрация иерархии TriG графов через hasParentTrig\n00003| # Исполнители определены в vad:rtree (Дерево Исполнителей)\n00004| #\n00005| # vad:processSubtype вычисляется автоматически в virtualRDFdata:\n00006| # - vad:DetailedChild: процесс с vad:hasTrig, где дочерний TriG указывает на текущий\n00007| # - vad:notDetailedChild: процесс с vad:hasParentProcess, совпадающим с родителем текущей схемы\n00008| # - vad:NotDefinedType: процесс с vad:hasParentProcess = vad:NotDefined\n00009| #\n00010| # ВАЖНЫЕ ПРАВИЛА:\n00011| # 1. vad:hasTrig и vad:hasParentProcess - взаимоисключающие предикаты\n00012| # 2. vad:processSubtype вычисляется автоматически в virtualRDFdata\n00013| # 3. vad:isSubprocessTrig указывается первым для явной связи индивида с TriG\n00014| # 4. ID группы исполнителей формируется как: ExecutorGroup_ + ID процесса\n00015| \n00016| @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n00017| @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n00018| @prefix dcterms: <http://purl.org/dc/terms/> .\n00019| @prefix vad: <http://example.org/vad#> .\n00020| \n00021| # ============================================================================\n00022| # Дерево Процессов (ptree) - общий граф с метаданными всех процессов\n00023| # ============================================================================\n00024| \n00025| vad:ptree {\n00026| vad:ptree rdf:type vad:ProcessTree ;\n00027| rdfs:label \"Дерево Процессов (TriG)\" ;\n00028| vad:hasParentTrig vad:root .\n00029| \n00030| vad:p1 rdf:type vad:TypeProcess ;\n00031| rdfs:label \"p1 Процесс 1\" ;\n00032| dcterms:description \"p1 Процесс 1\" ;\n00033| vad:hasTrig vad:t_p1 .\n00034| \n00035| vad:p1.1 rdf:type vad:TypeProcess ;\n00036| rdfs:label \"p1.1 Процесс 1.1\" ;\n00037| dcterms:description \"Первый процесс в цепочке добавленной стоимости\" ;\n00038| vad:hasTrig vad:t_p1.1 .\n00039| \n00040| vad:p1.2 rdf:type vad:TypeProcess ;\n00041| rdfs:label \"Процесс 1.2\" ;\n00042| dcterms:description \"Второй процесс, выполняется после Процесса 1\" ;\n00043| vad:hasParentProcess vad:p1 .\n00044| \n00045| # p1.1.1\n00046| vad:p1.1.1 rdf:type vad:TypeProcess ;\n00047| rdfs:label \"p1.1.1 Процесс 1.1.1\" ;\n00048| dcterms:description \"Первый процесс в цепочке добавленной стоимости\" ;\n00049| vad:hasParentProcess vad:p1.1 .\n00050| \n\n(File has more lines. Use 'offset' parameter to read beyond line 50)\n</file>",
[2026-01-26T19:19:13.690Z] [INFO] "title": "ver8a/Trig_VADv4.ttl",
[2026-01-26T19:19:13.690Z] [INFO] "metadata": {
[2026-01-26T19:19:13.690Z] [INFO] "preview": "# Пример TriG VADv4 (Value Added Chain Diagram с иерархией TriG)\n# Демонстрация иерархии TriG графов через hasParentTrig\n# Исполнители определены в vad:rtree (Дерево Исполнителей)\n#\n# vad:processSubtype вычисляется автоматически в virtualRDFdata:\n# - vad:DetailedChild: процесс с vad:hasTrig, где дочерний TriG указывает на текущий\n# - vad:notDetailedChild: процесс с vad:hasParentProcess, совпадающим с родителем текущей схемы\n# - vad:NotDefinedType: процесс с vad:hasParentProcess = vad:NotDefined\n#\n# ВАЖНЫЕ ПРАВИЛА:\n# 1. vad:hasTrig и vad:hasParentProcess - взаимоисключающие предикаты\n# 2. vad:processSubtype вычисляется автоматически в virtualRDFdata\n# 3. vad:isSubprocessTrig указывается первым для явной связи индивида с TriG\n# 4. ID группы исполнителей формируется как: ExecutorGroup_ + ID процесса\n\n@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n@prefix dcterms: <http://purl.org/dc/terms/> .\n@prefix vad: <http://example.org/vad#> .\n"
[2026-01-26T19:19:13.690Z] [INFO] },
[2026-01-26T19:19:13.691Z] [INFO] "time": {
[2026-01-26T19:19:13.691Z] [INFO] "start": 1769455153686,
[2026-01-26T19:19:13.691Z] [INFO] "end": 1769455153687
[2026-01-26T19:19:13.691Z] [INFO] }
[2026-01-26T19:19:13.691Z] [INFO] }
[2026-01-26T19:19:13.691Z] [INFO] }
[2026-01-26T19:19:13.691Z] [INFO] }
[2026-01-26T19:19:13.738Z] [INFO] {
[2026-01-26T19:19:13.739Z] [INFO] "type": "text",
[2026-01-26T19:19:13.739Z] [INFO] "timestamp": 1769455153738,
[2026-01-26T19:19:13.739Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:13.739Z] [INFO] "part": {
[2026-01-26T19:19:13.739Z] [INFO] "id": "prt_bfbbee843001cDSxdu8dDC3E0C",
[2026-01-26T19:19:13.740Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:13.740Z] [INFO] "messageID": "msg_bfbbea504001yJcFq47G0FG686",
[2026-01-26T19:19:13.740Z] [INFO] "type": "text",
[2026-01-26T19:19:13.740Z] [INFO] "text": "\nОтлично, функции есть. Теперь проверю основную проблему - это может быть связано с отсутствием или неправильной работой JavaScript. Давайте проверим, загрузка файла Trig_VADv4.ttl:",
[2026-01-26T19:19:13.740Z] [INFO] "time": {
[2026-01-26T19:19:13.740Z] [INFO] "start": 1769455153737,
[2026-01-26T19:19:13.740Z] [INFO] "end": 1769455153737
[2026-01-26T19:19:13.741Z] [INFO] }
[2026-01-26T19:19:13.741Z] [INFO] }
[2026-01-26T19:19:13.741Z] [INFO] }
[2026-01-26T19:19:13.744Z] [INFO] {
[2026-01-26T19:19:13.745Z] [INFO] "type": "step_finish",
[2026-01-26T19:19:13.745Z] [INFO] "timestamp": 1769455153744,
[2026-01-26T19:19:13.745Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:13.745Z] [INFO] "part": {
[2026-01-26T19:19:13.745Z] [INFO] "id": "prt_bfbbef24a001wIGc2az5fnpR5u",
[2026-01-26T19:19:13.745Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:13.745Z] [INFO] "messageID": "msg_bfbbea504001yJcFq47G0FG686",
[2026-01-26T19:19:13.745Z] [INFO] "type": "step-finish",
[2026-01-26T19:19:13.746Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:19:13.746Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08",
[2026-01-26T19:19:13.746Z] [INFO] "cost": 0,
[2026-01-26T19:19:13.746Z] [INFO] "tokens": {
[2026-01-26T19:19:13.746Z] [INFO] "input": 1487,
[2026-01-26T19:19:13.746Z] [INFO] "output": 97,
[2026-01-26T19:19:13.746Z] [INFO] "reasoning": 1,
[2026-01-26T19:19:13.746Z] [INFO] "cache": {
[2026-01-26T19:19:13.746Z] [INFO] "read": 73867,
[2026-01-26T19:19:13.746Z] [INFO] "write": 0
[2026-01-26T19:19:13.747Z] [INFO] }
[2026-01-26T19:19:13.747Z] [INFO] }
[2026-01-26T19:19:13.747Z] [INFO] }
[2026-01-26T19:19:13.747Z] [INFO] }
[2026-01-26T19:19:24.294Z] [INFO] {
[2026-01-26T19:19:24.295Z] [INFO] "type": "step_start",
[2026-01-26T19:19:24.295Z] [INFO] "timestamp": 1769455164294,
[2026-01-26T19:19:24.295Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:24.295Z] [INFO] "part": {
[2026-01-26T19:19:24.296Z] [INFO] "id": "prt_bfbbf1b85001kgz0yAGFG155Ds",
[2026-01-26T19:19:24.296Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:24.296Z] [INFO] "messageID": "msg_bfbbef2600018eK8KwNhePRefX",
[2026-01-26T19:19:24.296Z] [INFO] "type": "step-start",
[2026-01-26T19:19:24.296Z] [INFO] "snapshot": "a7b6540b45d9e43b73d9d460bd0daccb9a625a08"
[2026-01-26T19:19:24.296Z] [INFO] }
[2026-01-26T19:19:24.296Z] [INFO] }
[2026-01-26T19:19:32.899Z] [INFO] {
[2026-01-26T19:19:32.900Z] [INFO] "type": "tool_use",
[2026-01-26T19:19:32.900Z] [INFO] "timestamp": 1769455172899,
[2026-01-26T19:19:32.900Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:32.900Z] [INFO] "part": {
[2026-01-26T19:19:32.900Z] [INFO] "id": "prt_bfbbf3d1e0012gZfgosyAxYD22",
[2026-01-26T19:19:32.901Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:32.901Z] [INFO] "messageID": "msg_bfbbef2600018eK8KwNhePRefX",
[2026-01-26T19:19:32.901Z] [INFO] "type": "tool",
[2026-01-26T19:19:32.902Z] [INFO] "callID": "call_0cc74ec741ce4e819d4dd644",
[2026-01-26T19:19:32.902Z] [INFO] "tool": "write",
[2026-01-26T19:19:32.902Z] [INFO] "state": {
[2026-01-26T19:19:32.902Z] [INFO] "status": "completed",
[2026-01-26T19:19:32.902Z] [INFO] "input": {
[2026-01-26T19:19:32.903Z] [INFO] "content": "<!DOCTYPE html>\n<html lang=\"ru\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Test Debug</title>\n</head>\n<body>\n <h1>Debug Test</h1>\n <div id=\"test-results\"></div>\n \n <script>\n console.log('Debug test started');\n \n // Проверим основные функции\n const results = document.getElementById('test-results');\n \n function testBasicFunctionality() {\n let html = '<h2>Test Results:</h2>';\n \n // Test 1: Check if elements exist\n const testElements = [\n 'rdf-input',\n 'input-format', \n 'visualize-btn',\n 'max-label-length',\n 'max-vad-row-length'\n ];\n \n html += '<h3>Element Tests:</h3>';\n testElements.forEach(id => {\n const element = document.getElementById(id);\n html += `<p>${id}: ${element ? 'EXISTS' : 'MISSING'}</p>`;\n });\n \n results.innerHTML = html;\n }\n \n // Run tests\n testBasicFunctionality();\n </script>\n</body>\n</html>",
[2026-01-26T19:19:32.903Z] [INFO] "filePath": "/tmp/gh-issue-solver-1769454766276/debug_test.html"
[2026-01-26T19:19:32.903Z] [INFO] },
[2026-01-26T19:19:32.903Z] [INFO] "output": "",
[2026-01-26T19:19:32.903Z] [INFO] "title": "debug_test.html",
[2026-01-26T19:19:32.903Z] [INFO] "metadata": {
[2026-01-26T19:19:32.903Z] [INFO] "diagnostics": {},
[2026-01-26T19:19:32.904Z] [INFO] "filepath": "/tmp/gh-issue-solver-1769454766276/debug_test.html",
[2026-01-26T19:19:32.904Z] [INFO] "exists": false
[2026-01-26T19:19:32.904Z] [INFO] },
[2026-01-26T19:19:32.904Z] [INFO] "time": {
[2026-01-26T19:19:32.904Z] [INFO] "start": 1769455172898,
[2026-01-26T19:19:32.904Z] [INFO] "end": 1769455172899
[2026-01-26T19:19:32.905Z] [INFO] }
[2026-01-26T19:19:32.905Z] [INFO] }
[2026-01-26T19:19:32.905Z] [INFO] }
[2026-01-26T19:19:32.905Z] [INFO] }
[2026-01-26T19:19:32.949Z] [INFO] {
[2026-01-26T19:19:32.950Z] [INFO] "type": "text",
[2026-01-26T19:19:32.950Z] [INFO] "timestamp": 1769455172948,
[2026-01-26T19:19:32.950Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:32.950Z] [INFO] "part": {
[2026-01-26T19:19:32.950Z] [INFO] "id": "prt_bfbbf1d18001rTjuR8X3is55cJ",
[2026-01-26T19:19:32.951Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:32.951Z] [INFO] "messageID": "msg_bfbbef2600018eK8KwNhePRefX",
[2026-01-26T19:19:32.951Z] [INFO] "type": "text",
[2026-01-26T19:19:32.951Z] [INFO] "text": "\nФайл с данными существует. Теперь нужно проверить основную проблему. Судя по описанию, проблема может быть в обработке RDF данных или в JavaScript. Давайте проверим, есть ли ошибки в консоли. Для начала создам тестовый HTML файл для проверки:",
[2026-01-26T19:19:32.952Z] [INFO] "time": {
[2026-01-26T19:19:32.952Z] [INFO] "start": 1769455172948,
[2026-01-26T19:19:32.952Z] [INFO] "end": 1769455172948
[2026-01-26T19:19:32.953Z] [INFO] }
[2026-01-26T19:19:32.953Z] [INFO] }
[2026-01-26T19:19:32.953Z] [INFO] }
[2026-01-26T19:19:32.961Z] [INFO] {
[2026-01-26T19:19:32.962Z] [INFO] "type": "step_finish",
[2026-01-26T19:19:32.962Z] [INFO] "timestamp": 1769455172961,
[2026-01-26T19:19:32.962Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:32.962Z] [INFO] "part": {
[2026-01-26T19:19:32.963Z] [INFO] "id": "prt_bfbbf3d55001ZLjH418fxLEC2p",
[2026-01-26T19:19:32.963Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:32.963Z] [INFO] "messageID": "msg_bfbbef2600018eK8KwNhePRefX",
[2026-01-26T19:19:32.964Z] [INFO] "type": "step-finish",
[2026-01-26T19:19:32.964Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:19:32.964Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a",
[2026-01-26T19:19:32.964Z] [INFO] "cost": 0,
[2026-01-26T19:19:32.964Z] [INFO] "tokens": {
[2026-01-26T19:19:32.964Z] [INFO] "input": 65342,
[2026-01-26T19:19:32.965Z] [INFO] "output": 363,
[2026-01-26T19:19:32.965Z] [INFO] "reasoning": 1,
[2026-01-26T19:19:32.965Z] [INFO] "cache": {
[2026-01-26T19:19:32.965Z] [INFO] "read": 10939,
[2026-01-26T19:19:32.965Z] [INFO] "write": 0
[2026-01-26T19:19:32.965Z] [INFO] }
[2026-01-26T19:19:32.965Z] [INFO] }
[2026-01-26T19:19:32.966Z] [INFO] }
[2026-01-26T19:19:32.966Z] [INFO] }
[2026-01-26T19:19:41.949Z] [INFO] {
[2026-01-26T19:19:41.949Z] [INFO] "type": "step_start",
[2026-01-26T19:19:41.949Z] [INFO] "timestamp": 1769455181948,
[2026-01-26T19:19:41.949Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:41.950Z] [INFO] "part": {
[2026-01-26T19:19:41.950Z] [INFO] "id": "prt_bfbbf607b001Ql8UDrbKEsk354",
[2026-01-26T19:19:41.950Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:41.950Z] [INFO] "messageID": "msg_bfbbf3d75001BJ4pLrwigaac0l",
[2026-01-26T19:19:41.950Z] [INFO] "type": "step-start",
[2026-01-26T19:19:41.950Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a"
[2026-01-26T19:19:41.950Z] [INFO] }
[2026-01-26T19:19:41.950Z] [INFO] }
[2026-01-26T19:19:46.365Z] [INFO] {
[2026-01-26T19:19:46.366Z] [INFO] "type": "tool_use",
[2026-01-26T19:19:46.366Z] [INFO] "timestamp": 1769455186365,
[2026-01-26T19:19:46.366Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:46.366Z] [INFO] "part": {
[2026-01-26T19:19:46.366Z] [INFO] "id": "prt_bfbbf71b4001FAwXoxwyfkenI8",
[2026-01-26T19:19:46.367Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:46.367Z] [INFO] "messageID": "msg_bfbbf3d75001BJ4pLrwigaac0l",
[2026-01-26T19:19:46.368Z] [INFO] "type": "tool",
[2026-01-26T19:19:46.368Z] [INFO] "callID": "call_095d1a39baa14c3191f8e593",
[2026-01-26T19:19:46.368Z] [INFO] "tool": "grep",
[2026-01-26T19:19:46.368Z] [INFO] "state": {
[2026-01-26T19:19:46.368Z] [INFO] "status": "completed",
[2026-01-26T19:19:46.368Z] [INFO] "input": {
[2026-01-26T19:19:46.368Z] [INFO] "path": "/tmp/gh-issue-solver-1769454766276/ver8a",
[2026-01-26T19:19:46.368Z] [INFO] "pattern": "function showRdfInSeparateWindow\\(|function updateModeDescription\\(|function toggleSparqlPanel\\("
[2026-01-26T19:19:46.369Z] [INFO] },
[2026-01-26T19:19:46.369Z] [INFO] "output": "Found 3 matches\n/tmp/gh-issue-solver-1769454766276/ver8a/index.html:\n Line 7076: function showRdfInSeparateWindow() {\n Line 7143: function updateModeDescription() {\n Line 7184: function toggleSparqlPanel() {",
[2026-01-26T19:19:46.369Z] [INFO] "title": "function showRdfInSeparateWindow\\(|function updateModeDescription\\(|function toggleSparqlPanel\\(",
[2026-01-26T19:19:46.369Z] [INFO] "metadata": {
[2026-01-26T19:19:46.369Z] [INFO] "matches": 3,
[2026-01-26T19:19:46.369Z] [INFO] "truncated": false
[2026-01-26T19:19:46.369Z] [INFO] },
[2026-01-26T19:19:46.369Z] [INFO] "time": {
[2026-01-26T19:19:46.369Z] [INFO] "start": 1769455186358,
[2026-01-26T19:19:46.370Z] [INFO] "end": 1769455186364
[2026-01-26T19:19:46.370Z] [INFO] }
[2026-01-26T19:19:46.370Z] [INFO] }
[2026-01-26T19:19:46.370Z] [INFO] }
[2026-01-26T19:19:46.370Z] [INFO] }
[2026-01-26T19:19:46.407Z] [INFO] {
[2026-01-26T19:19:46.407Z] [INFO] "type": "text",
[2026-01-26T19:19:46.408Z] [INFO] "timestamp": 1769455186407,
[2026-01-26T19:19:46.408Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:46.409Z] [INFO] "part": {
[2026-01-26T19:19:46.409Z] [INFO] "id": "prt_bfbbf69ae001S49f5JjtU2LzMi",
[2026-01-26T19:19:46.409Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:46.409Z] [INFO] "messageID": "msg_bfbbf3d75001BJ4pLrwigaac0l",
[2026-01-26T19:19:46.409Z] [INFO] "type": "text",
[2026-01-26T19:19:46.410Z] [INFO] "text": "\nТеперь я проверю основную проблему. Судя по анализу кода, все основные функции на месте. Проблема может быть в том, что некоторые функции отсутствуют или не вызываются правильно. Проверим наличие всех необходимых функций:",
[2026-01-26T19:19:46.410Z] [INFO] "time": {
[2026-01-26T19:19:46.410Z] [INFO] "start": 1769455186406,
[2026-01-26T19:19:46.410Z] [INFO] "end": 1769455186406
[2026-01-26T19:19:46.410Z] [INFO] }
[2026-01-26T19:19:46.410Z] [INFO] }
[2026-01-26T19:19:46.411Z] [INFO] }
[2026-01-26T19:19:46.414Z] [INFO] {
[2026-01-26T19:19:46.415Z] [INFO] "type": "step_finish",
[2026-01-26T19:19:46.415Z] [INFO] "timestamp": 1769455186414,
[2026-01-26T19:19:46.416Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:46.416Z] [INFO] "part": {
[2026-01-26T19:19:46.416Z] [INFO] "id": "prt_bfbbf71e7001rLi620mx7Nm0MR",
[2026-01-26T19:19:46.416Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:19:46.416Z] [INFO] "messageID": "msg_bfbbf3d75001BJ4pLrwigaac0l",
[2026-01-26T19:19:46.417Z] [INFO] "type": "step-finish",
[2026-01-26T19:19:46.417Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:19:46.417Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a",
[2026-01-26T19:19:46.417Z] [INFO] "cost": 0,
[2026-01-26T19:19:46.417Z] [INFO] "tokens": {
[2026-01-26T19:19:46.417Z] [INFO] "input": 76586,
[2026-01-26T19:19:46.417Z] [INFO] "output": 117,
[2026-01-26T19:19:46.417Z] [INFO] "reasoning": 1,
[2026-01-26T19:19:46.417Z] [INFO] "cache": {
[2026-01-26T19:19:46.418Z] [INFO] "read": 67,
[2026-01-26T19:19:46.418Z] [INFO] "write": 0
[2026-01-26T19:19:46.418Z] [INFO] }
[2026-01-26T19:19:46.418Z] [INFO] }
[2026-01-26T19:19:46.418Z] [INFO] }
[2026-01-26T19:19:46.418Z] [INFO] }
[2026-01-26T19:21:53.962Z] [INFO] {
[2026-01-26T19:21:53.962Z] [INFO] "type": "step_start",
[2026-01-26T19:21:53.963Z] [INFO] "timestamp": 1769455313961,
[2026-01-26T19:21:53.963Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:21:53.963Z] [INFO] "part": {
[2026-01-26T19:21:53.963Z] [INFO] "id": "prt_bfbc16428001Gc3GdNPH73b3x5",
[2026-01-26T19:21:53.964Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:21:53.964Z] [INFO] "messageID": "msg_bfbbf7203001h7D4E21wrcyvaL",
[2026-01-26T19:21:53.964Z] [INFO] "type": "step-start",
[2026-01-26T19:21:53.964Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a"
[2026-01-26T19:21:53.964Z] [INFO] }
[2026-01-26T19:21:53.964Z] [INFO] }
[2026-01-26T19:21:56.852Z] [INFO] {
[2026-01-26T19:21:56.852Z] [INFO] "type": "tool_use",
[2026-01-26T19:21:56.853Z] [INFO] "timestamp": 1769455316851,
[2026-01-26T19:21:56.853Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:21:56.853Z] [INFO] "part": {
[2026-01-26T19:21:56.853Z] [INFO] "id": "prt_bfbc16f6a0017xa12Ov8zG3kIE",
[2026-01-26T19:21:56.854Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:21:56.854Z] [INFO] "messageID": "msg_bfbbf7203001h7D4E21wrcyvaL",
[2026-01-26T19:21:56.854Z] [INFO] "type": "tool",
[2026-01-26T19:21:56.855Z] [INFO] "callID": "call_821ade1acca44bf0b670c24d",
[2026-01-26T19:21:56.855Z] [INFO] "tool": "grep",
[2026-01-26T19:21:56.855Z] [INFO] "state": {
[2026-01-26T19:21:56.855Z] [INFO] "status": "completed",
[2026-01-26T19:21:56.856Z] [INFO] "input": {
[2026-01-26T19:21:56.856Z] [INFO] "path": "/tmp/gh-issue-solver-1769454766276/ver8a",
[2026-01-26T19:21:56.856Z] [INFO] "pattern": "max-vad-row-length-group|smart-design-container|result-sparql-panel"
[2026-01-26T19:21:56.856Z] [INFO] },
[2026-01-26T19:21:56.857Z] [INFO] "output": "Found 9 matches\n/tmp/gh-issue-solver-1769454766276/ver8a/styles.css:\n Line 1089: .smart-design-container {\n Line 1094: .smart-design-container.visible {\n Line 1194: .result-sparql-panel {\n Line 1201: .result-sparql-panel h3 {\n\n/tmp/gh-issue-solver-1769454766276/ver8a/index.html:\n Line 102: <div class=\"form-group\" id=\"max-vad-row-length-group\" style=\"display: none;\">\n Line 115: <div class=\"smart-design-container\" id=\"smart-design-container\">\n Line 178: <div class=\"result-sparql-panel\">\n Line 7146: const maxVadRowLengthGroup = document.getElementById('max-vad-row-length-group');\n Line 7188: const smartDesignContainer = document.getElementById('smart-design-container');",
[2026-01-26T19:21:56.857Z] [INFO] "title": "max-vad-row-length-group|smart-design-container|result-sparql-panel",
[2026-01-26T19:21:56.857Z] [INFO] "metadata": {
[2026-01-26T19:21:56.857Z] [INFO] "matches": 9,
[2026-01-26T19:21:56.857Z] [INFO] "truncated": false
[2026-01-26T19:21:56.858Z] [INFO] },
[2026-01-26T19:21:56.858Z] [INFO] "time": {
[2026-01-26T19:21:56.858Z] [INFO] "start": 1769455316844,
[2026-01-26T19:21:56.858Z] [INFO] "end": 1769455316851
[2026-01-26T19:21:56.858Z] [INFO] }
[2026-01-26T19:21:56.858Z] [INFO] }
[2026-01-26T19:21:56.859Z] [INFO] }
[2026-01-26T19:21:56.859Z] [INFO] }
[2026-01-26T19:21:56.911Z] [INFO] {
[2026-01-26T19:21:56.912Z] [INFO] "type": "text",
[2026-01-26T19:21:56.912Z] [INFO] "timestamp": 1769455316911,
[2026-01-26T19:21:56.912Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:21:56.913Z] [INFO] "part": {
[2026-01-26T19:21:56.913Z] [INFO] "id": "prt_bfbc164ad001IO9ZG0tXr1pko7",
[2026-01-26T19:21:56.913Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:21:56.913Z] [INFO] "messageID": "msg_bfbbf7203001h7D4E21wrcyvaL",
[2026-01-26T19:21:56.913Z] [INFO] "type": "text",
[2026-01-26T19:21:56.913Z] [INFO] "text": "\nФункции существуют. Теперь проверим основные проблемы, упомянутые в issue:\n\n1. Кнопка \"Показать\" не работает\n2. Кнопки \"Очистить\", \"Тест\", \"Сохранить как\" не работают \n3. Поля \"Макс. длина VAD:\", \"Smart Design\", \"Result in SPARQL\" не отображаются\n\nДавайте проверим CSS для отображения этих полей:",
[2026-01-26T19:21:56.914Z] [INFO] "time": {
[2026-01-26T19:21:56.914Z] [INFO] "start": 1769455316910,
[2026-01-26T19:21:56.914Z] [INFO] "end": 1769455316910
[2026-01-26T19:21:56.914Z] [INFO] }
[2026-01-26T19:21:56.914Z] [INFO] }
[2026-01-26T19:21:56.915Z] [INFO] }
[2026-01-26T19:21:56.919Z] [INFO] {
[2026-01-26T19:21:56.920Z] [INFO] "type": "step_finish",
[2026-01-26T19:21:56.920Z] [INFO] "timestamp": 1769455316919,
[2026-01-26T19:21:56.920Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:21:56.920Z] [INFO] "part": {
[2026-01-26T19:21:56.921Z] [INFO] "id": "prt_bfbc16faf001nd2BwFcZqmkqVo",
[2026-01-26T19:21:56.921Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:21:56.921Z] [INFO] "messageID": "msg_bfbbf7203001h7D4E21wrcyvaL",
[2026-01-26T19:21:56.921Z] [INFO] "type": "step-finish",
[2026-01-26T19:21:56.921Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:21:56.921Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a",
[2026-01-26T19:21:56.921Z] [INFO] "cost": 0,
[2026-01-26T19:21:56.922Z] [INFO] "tokens": {
[2026-01-26T19:21:56.922Z] [INFO] "input": 81,
[2026-01-26T19:21:56.922Z] [INFO] "output": 152,
[2026-01-26T19:21:56.923Z] [INFO] "reasoning": 1,
[2026-01-26T19:21:56.923Z] [INFO] "cache": {
[2026-01-26T19:21:56.923Z] [INFO] "read": 76769,
[2026-01-26T19:21:56.923Z] [INFO] "write": 0
[2026-01-26T19:21:56.923Z] [INFO] }
[2026-01-26T19:21:56.923Z] [INFO] }
[2026-01-26T19:21:56.923Z] [INFO] }
[2026-01-26T19:21:56.923Z] [INFO] }
[2026-01-26T19:22:01.698Z] [INFO] {
[2026-01-26T19:22:01.699Z] [INFO] "type": "step_start",
[2026-01-26T19:22:01.699Z] [INFO] "timestamp": 1769455321698,
[2026-01-26T19:22:01.700Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:01.700Z] [INFO] "part": {
[2026-01-26T19:22:01.700Z] [INFO] "id": "prt_bfbc182610012VA33a3CvWVrJH",
[2026-01-26T19:22:01.700Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:01.700Z] [INFO] "messageID": "msg_bfbc16fcd001Ow43tnUu5ACs3x",
[2026-01-26T19:22:01.700Z] [INFO] "type": "step-start",
[2026-01-26T19:22:01.700Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a"
[2026-01-26T19:22:01.701Z] [INFO] }
[2026-01-26T19:22:01.701Z] [INFO] }
[2026-01-26T19:22:04.302Z] [INFO] {
[2026-01-26T19:22:04.302Z] [INFO] "type": "tool_use",
[2026-01-26T19:22:04.303Z] [INFO] "timestamp": 1769455324301,
[2026-01-26T19:22:04.303Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:04.303Z] [INFO] "part": {
[2026-01-26T19:22:04.303Z] [INFO] "id": "prt_bfbc18c89001L7LpM2uA7xLOhd",
[2026-01-26T19:22:04.304Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:04.304Z] [INFO] "messageID": "msg_bfbc16fcd001Ow43tnUu5ACs3x",
[2026-01-26T19:22:04.304Z] [INFO] "type": "tool",
[2026-01-26T19:22:04.305Z] [INFO] "callID": "call_9a595e09f03646368204227d",
[2026-01-26T19:22:04.305Z] [INFO] "tool": "read",
[2026-01-26T19:22:04.305Z] [INFO] "state": {
[2026-01-26T19:22:04.305Z] [INFO] "status": "completed",
[2026-01-26T19:22:04.305Z] [INFO] "input": {
[2026-01-26T19:22:04.306Z] [INFO] "filePath": "/tmp/gh-issue-solver-1769454766276/ver8a/index.html",
[2026-01-26T19:22:04.306Z] [INFO] "limit": 50,
[2026-01-26T19:22:04.306Z] [INFO] "offset": 7143
[2026-01-26T19:22:04.306Z] [INFO] },
[2026-01-26T19:22:04.306Z] [INFO] "output": "<file>\n07144| const mode = document.getElementById('visualization-mode').value;\n07145| const description = document.getElementById('mode-description');\n07146| const maxVadRowLengthGroup = document.getElementById('max-vad-row-length-group');\n07147| const sparqlModeSelect = document.getElementById('sparql-mode');\n07148| const smartDesignOption = sparqlModeSelect ? sparqlModeSelect.querySelector('option[value=\"smart-design\"]') : null;\n07149| \n07150| const descriptions = {\n07151| 'notation': 'С выделением типов объектов и предикатов цветом и формами',\n07152| 'base': 'Базовый режим без специальных стилей',\n07153| 'aggregation': 'Литералы агрегируются в узел субъекта',\n07154| 'vad': 'VAD: процессы как cds-фигуры с исполнителями',\n07155| 'vad-trig': 'VAD TriG: иерархия TriG с деревом и свойствами'\n07156| };\n07157| \n07158| description.textContent = descriptions[mode] || '';\n07159| \n07160| // Показываем/скрываем параметр \"Макс. длина VAD\" для режимов VAD и VAD TriG\n07161| if (mode === 'vad' || mode === 'vad-trig') {\n07162| maxVadRowLengthGroup.style.display = 'block';\n07163| } else {\n07164| maxVadRowLengthGroup.style.display = 'none';\n07165| }\n07166| \n07167| // Показываем/скрываем опцию \"SPARQL Smart Design\" в зависимости от режима визуализации\n07168| if (smartDesignOption) {\n07169| if (mode === 'vad-trig') {\n07170| smartDesignOption.style.display = '';\n07171| } else {\n07172| smartDesignOption.style.display = 'none';\n07173| // Если был выбран smart-design, сбрасываем на 'no'\n07174| if (sparqlModeSelect.value === 'smart-design') {\n07175| sparqlModeSelect.value = 'no';\n07176| }\n07177| }\n07178| }\n07179| \n07180| // Обновляем состояние SPARQL панели\n07181| toggleSparqlPanel();\n07182| }\n07183| \n07184| function toggleSparqlPanel() {\n07185| const sparqlMode = document.getElementById('sparql-mode').value;\n07186| const visualizationMode = document.getElementById('visualization-mode').value;\n07187| const sparqlPanel = document.getElementById('sparql-panel');\n07188| const smartDesignContainer = document.getElementById('smart-design-container');\n07189| \n07190| // Скрываем оба панели по умолчанию\n07191| sparqlPanel.classList.remove('visible');\n07192| smartDesignContainer.classList.remove('visible');\n07193| \n\n(File has more lines. Use 'offset' parameter to read beyond line 7193)\n</file>",
[2026-01-26T19:22:04.307Z] [INFO] "title": "ver8a/index.html",
[2026-01-26T19:22:04.307Z] [INFO] "metadata": {
[2026-01-26T19:22:04.307Z] [INFO] "preview": " const mode = document.getElementById('visualization-mode').value;\n const description = document.getElementById('mode-description');\n const maxVadRowLengthGroup = document.getElementById('max-vad-row-length-group');\n const sparqlModeSelect = document.getElementById('sparql-mode');\n const smartDesignOption = sparqlModeSelect ? sparqlModeSelect.querySelector('option[value=\"smart-design\"]') : null;\n\n const descriptions = {\n 'notation': 'С выделением типов объектов и предикатов цветом и формами',\n 'base': 'Базовый режим без специальных стилей',\n 'aggregation': 'Литералы агрегируются в узел субъекта',\n 'vad': 'VAD: процессы как cds-фигуры с исполнителями',\n 'vad-trig': 'VAD TriG: иерархия TriG с деревом и свойствами'\n };\n\n description.textContent = descriptions[mode] || '';\n\n // Показываем/скрываем параметр \"Макс. длина VAD\" для режимов VAD и VAD TriG\n if (mode === 'vad' || mode === 'vad-trig') {\n maxVadRowLengthGroup.style.display = 'block';\n } else {"
[2026-01-26T19:22:04.307Z] [INFO] },
[2026-01-26T19:22:04.307Z] [INFO] "time": {
[2026-01-26T19:22:04.307Z] [INFO] "start": 1769455324299,
[2026-01-26T19:22:04.308Z] [INFO] "end": 1769455324301
[2026-01-26T19:22:04.308Z] [INFO] }
[2026-01-26T19:22:04.308Z] [INFO] }
[2026-01-26T19:22:04.308Z] [INFO] }
[2026-01-26T19:22:04.308Z] [INFO] }
[2026-01-26T19:22:04.349Z] [INFO] {
[2026-01-26T19:22:04.350Z] [INFO] "type": "text",
[2026-01-26T19:22:04.350Z] [INFO] "timestamp": 1769455324349,
[2026-01-26T19:22:04.351Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:04.351Z] [INFO] "part": {
[2026-01-26T19:22:04.352Z] [INFO] "id": "prt_bfbc182e8001AQRwKXzoUCNWZ5",
[2026-01-26T19:22:04.352Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:04.352Z] [INFO] "messageID": "msg_bfbc16fcd001Ow43tnUu5ACs3x",
[2026-01-26T19:22:04.352Z] [INFO] "type": "text",
[2026-01-26T19:22:04.352Z] [INFO] "text": "\nВижу проблему! Поле `max-vad-row-length-group` по умолчанию скрыто (`style=\"display: none;\"`). Это может быть причиной, почему поле \"Макс. длина VAD:\" не отображается.\n\nПроверим функцию `updateModeDescription()`:",
[2026-01-26T19:22:04.353Z] [INFO] "time": {
[2026-01-26T19:22:04.353Z] [INFO] "start": 1769455324348,
[2026-01-26T19:22:04.353Z] [INFO] "end": 1769455324348
[2026-01-26T19:22:04.353Z] [INFO] }
[2026-01-26T19:22:04.353Z] [INFO] }
[2026-01-26T19:22:04.353Z] [INFO] }
[2026-01-26T19:22:04.357Z] [INFO] {
[2026-01-26T19:22:04.357Z] [INFO] "type": "step_finish",
[2026-01-26T19:22:04.357Z] [INFO] "timestamp": 1769455324357,
[2026-01-26T19:22:04.358Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:04.358Z] [INFO] "part": {
[2026-01-26T19:22:04.358Z] [INFO] "id": "prt_bfbc18cbd001UkcqJXus77IC3E",
[2026-01-26T19:22:04.358Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:04.359Z] [INFO] "messageID": "msg_bfbc16fcd001Ow43tnUu5ACs3x",
[2026-01-26T19:22:04.359Z] [INFO] "type": "step-finish",
[2026-01-26T19:22:04.359Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:22:04.360Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a",
[2026-01-26T19:22:04.360Z] [INFO] "cost": 0,
[2026-01-26T19:22:04.360Z] [INFO] "tokens": {
[2026-01-26T19:22:04.360Z] [INFO] "input": 220,
[2026-01-26T19:22:04.360Z] [INFO] "output": 116,
[2026-01-26T19:22:04.360Z] [INFO] "reasoning": 1,
[2026-01-26T19:22:04.360Z] [INFO] "cache": {
[2026-01-26T19:22:04.361Z] [INFO] "read": 77001,
[2026-01-26T19:22:04.361Z] [INFO] "write": 0
[2026-01-26T19:22:04.361Z] [INFO] }
[2026-01-26T19:22:04.361Z] [INFO] }
[2026-01-26T19:22:04.361Z] [INFO] }
[2026-01-26T19:22:04.361Z] [INFO] }
[2026-01-26T19:22:06.001Z] [INFO] {
[2026-01-26T19:22:06.001Z] [INFO] "type": "step_start",
[2026-01-26T19:22:06.002Z] [INFO] "timestamp": 1769455326000,
[2026-01-26T19:22:06.002Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:06.002Z] [INFO] "part": {
[2026-01-26T19:22:06.003Z] [INFO] "id": "prt_bfbc1932f0013DN8A2NhvEfAUC",
[2026-01-26T19:22:06.003Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:06.004Z] [INFO] "messageID": "msg_bfbc18cd9001N4v8IV449Qo1ug",
[2026-01-26T19:22:06.004Z] [INFO] "type": "step-start",
[2026-01-26T19:22:06.005Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a"
[2026-01-26T19:22:06.005Z] [INFO] }
[2026-01-26T19:22:06.005Z] [INFO] }
[2026-01-26T19:22:08.135Z] [INFO] {
[2026-01-26T19:22:08.136Z] [INFO] "type": "tool_use",
[2026-01-26T19:22:08.136Z] [INFO] "timestamp": 1769455328135,
[2026-01-26T19:22:08.136Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:08.136Z] [INFO] "part": {
[2026-01-26T19:22:08.136Z] [INFO] "id": "prt_bfbc19b84001Zk4LAedyFvwLgm",
[2026-01-26T19:22:08.137Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:08.137Z] [INFO] "messageID": "msg_bfbc18cd9001N4v8IV449Qo1ug",
[2026-01-26T19:22:08.137Z] [INFO] "type": "tool",
[2026-01-26T19:22:08.137Z] [INFO] "callID": "call_f22f23d4ef924b40873cad81",
[2026-01-26T19:22:08.137Z] [INFO] "tool": "read",
[2026-01-26T19:22:08.137Z] [INFO] "state": {
[2026-01-26T19:22:08.137Z] [INFO] "status": "completed",
[2026-01-26T19:22:08.137Z] [INFO] "input": {
[2026-01-26T19:22:08.137Z] [INFO] "filePath": "/tmp/gh-issue-solver-1769454766276/ver8a/index.html",
[2026-01-26T19:22:08.137Z] [INFO] "limit": 30,
[2026-01-26T19:22:08.138Z] [INFO] "offset": 7190
[2026-01-26T19:22:08.138Z] [INFO] },
[2026-01-26T19:22:08.138Z] [INFO] "output": "<file>\n07191| sparqlPanel.classList.remove('visible');\n07192| smartDesignContainer.classList.remove('visible');\n07193| \n07194| if (sparqlMode === 'smart-design') {\n07195| // SPARQL Smart Design отображается только в режиме VAD TriG\n07196| if (visualizationMode === 'vad-trig') {\n07197| smartDesignContainer.classList.add('visible');\n07198| // В режиме SPARQL Smart Design всегда также отображается окно SPARQL запрос\n07199| sparqlPanel.classList.add('visible');\n07200| // Заполняем выпадающие списки при активации Smart Design\n07201| populateSmartDesignDropdowns();\n07202| // Показываем справочные сообщения при открытии панели\n07203| showSmartDesignMessage('Выберите TriG, затем Subject, Predicate и Object. Нажмите \"Создать SPARQL\" для генерации запроса.', 'info');\n07204| showResultSparqlMessage('После генерации SPARQL запроса нажмите \"Применить как Simple Triple\" или \"Применить как Shorthand Triple\" для добавления в RDF данные.', 'info');\n07205| } else {\n07206| // Показываем сообщение, что Smart Design доступен только в VAD TriG\n07207| showSmartDesignMessage('SPARQL Smart Design доступен только в режиме \"Режим VAD TriG\"', 'info');\n07208| }\n07209| }\n07210| }\n07211| \n07212| // ============================================================================\n07213| // ОСНОВНАЯ ФУНКЦИЯ ВИЗУАЛИЗАЦИИ\n07214| // ============================================================================\n07215| \n07216| async function visualize() {\n07217| const rdfInput = document.getElementById('rdf-input').value.trim();\n07218| const inputFormat = document.getElementById('input-format').value;\n07219| const outputFormat = document.getElementById('output-format').value;\n07220| const layoutEngine = document.getElementById('layout-engine').value;\n\n(File has more lines. Use 'offset' parameter to read beyond line 7220)\n</file>",
[2026-01-26T19:22:08.138Z] [INFO] "title": "ver8a/index.html",
[2026-01-26T19:22:08.139Z] [INFO] "metadata": {
[2026-01-26T19:22:08.139Z] [INFO] "preview": " sparqlPanel.classList.remove('visible');\n smartDesignContainer.classList.remove('visible');\n\n if (sparqlMode === 'smart-design') {\n // SPARQL Smart Design отображается только в режиме VAD TriG\n if (visualizationMode === 'vad-trig') {\n smartDesignContainer.classList.add('visible');\n // В режиме SPARQL Smart Design всегда также отображается окно SPARQL запрос\n sparqlPanel.classList.add('visible');\n // Заполняем выпадающие списки при активации Smart Design\n populateSmartDesignDropdowns();\n // Показываем справочные сообщения при открытии панели\n showSmartDesignMessage('Выберите TriG, затем Subject, Predicate и Object. Нажмите \"Создать SPARQL\" для генерации запроса.', 'info');\n showResultSparqlMessage('После генерации SPARQL запроса нажмите \"Применить как Simple Triple\" или \"Применить как Shorthand Triple\" для добавления в RDF данные.', 'info');\n } else {\n // Показываем сообщение, что Smart Design доступен только в VAD TriG\n showSmartDesignMessage('SPARQL Smart Design доступен только в режиме \"Режим VAD TriG\"', 'info');\n }\n }\n }"
[2026-01-26T19:22:08.139Z] [INFO] },
[2026-01-26T19:22:08.139Z] [INFO] "time": {
[2026-01-26T19:22:08.139Z] [INFO] "start": 1769455328133,
[2026-01-26T19:22:08.139Z] [INFO] "end": 1769455328135
[2026-01-26T19:22:08.139Z] [INFO] }
[2026-01-26T19:22:08.139Z] [INFO] }
[2026-01-26T19:22:08.139Z] [INFO] }
[2026-01-26T19:22:08.140Z] [INFO] }
[2026-01-26T19:22:08.232Z] [INFO] {
[2026-01-26T19:22:08.232Z] [INFO] "type": "text",
[2026-01-26T19:22:08.232Z] [INFO] "timestamp": 1769455328231,
[2026-01-26T19:22:08.232Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:08.233Z] [INFO] "part": {
[2026-01-26T19:22:08.233Z] [INFO] "id": "prt_bfbc193580011Xx6FAzvqrIdbf",
[2026-01-26T19:22:08.233Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:08.233Z] [INFO] "messageID": "msg_bfbc18cd9001N4v8IV449Qo1ug",
[2026-01-26T19:22:08.233Z] [INFO] "type": "text",
[2026-01-26T19:22:08.233Z] [INFO] "text": "\nПроблема становится ясной! Функция `toggleSparqlPanel()` скрывает панели по умолчанию, и только показывает их при определенных условиях. Проверим полную логику:",
[2026-01-26T19:22:08.233Z] [INFO] "time": {
[2026-01-26T19:22:08.233Z] [INFO] "start": 1769455328230,
[2026-01-26T19:22:08.234Z] [INFO] "end": 1769455328230
[2026-01-26T19:22:08.234Z] [INFO] }
[2026-01-26T19:22:08.234Z] [INFO] }
[2026-01-26T19:22:08.234Z] [INFO] }
[2026-01-26T19:22:08.239Z] [INFO] {
[2026-01-26T19:22:08.240Z] [INFO] "type": "step_finish",
[2026-01-26T19:22:08.240Z] [INFO] "timestamp": 1769455328239,
[2026-01-26T19:22:08.240Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:08.240Z] [INFO] "part": {
[2026-01-26T19:22:08.240Z] [INFO] "id": "prt_bfbc19be7001YYk6wjjJYsjeVu",
[2026-01-26T19:22:08.241Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:08.241Z] [INFO] "messageID": "msg_bfbc18cd9001N4v8IV449Qo1ug",
[2026-01-26T19:22:08.241Z] [INFO] "type": "step-finish",
[2026-01-26T19:22:08.241Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:22:08.242Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a",
[2026-01-26T19:22:08.242Z] [INFO] "cost": 0,
[2026-01-26T19:22:08.242Z] [INFO] "tokens": {
[2026-01-26T19:22:08.242Z] [INFO] "input": 743,
[2026-01-26T19:22:08.243Z] [INFO] "output": 98,
[2026-01-26T19:22:08.243Z] [INFO] "reasoning": 1,
[2026-01-26T19:22:08.243Z] [INFO] "cache": {
[2026-01-26T19:22:08.243Z] [INFO] "read": 77336,
[2026-01-26T19:22:08.243Z] [INFO] "write": 0
[2026-01-26T19:22:08.243Z] [INFO] }
[2026-01-26T19:22:08.243Z] [INFO] }
[2026-01-26T19:22:08.243Z] [INFO] }
[2026-01-26T19:22:08.243Z] [INFO] }
[2026-01-26T19:22:10.777Z] [INFO] {
[2026-01-26T19:22:10.777Z] [INFO] "type": "step_start",
[2026-01-26T19:22:10.777Z] [INFO] "timestamp": 1769455330776,
[2026-01-26T19:22:10.778Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:10.778Z] [INFO] "part": {
[2026-01-26T19:22:10.778Z] [INFO] "id": "prt_bfbc1a5d7001BakdWx3Ehmp76v",
[2026-01-26T19:22:10.778Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:10.778Z] [INFO] "messageID": "msg_bfbc19c03001BGKG4BP6fkbaQc",
[2026-01-26T19:22:10.778Z] [INFO] "type": "step-start",
[2026-01-26T19:22:10.778Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a"
[2026-01-26T19:22:10.779Z] [INFO] }
[2026-01-26T19:22:10.779Z] [INFO] }
[2026-01-26T19:22:15.285Z] [INFO] {
[2026-01-26T19:22:15.286Z] [INFO] "type": "tool_use",
[2026-01-26T19:22:15.286Z] [INFO] "timestamp": 1769455335284,
[2026-01-26T19:22:15.286Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:15.286Z] [INFO] "part": {
[2026-01-26T19:22:15.286Z] [INFO] "id": "prt_bfbc1b76f00153ZGkB7snpZSfL",
[2026-01-26T19:22:15.287Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:15.287Z] [INFO] "messageID": "msg_bfbc19c03001BGKG4BP6fkbaQc",
[2026-01-26T19:22:15.287Z] [INFO] "type": "tool",
[2026-01-26T19:22:15.287Z] [INFO] "callID": "call_b063d5c524a64cf6bef688d8",
[2026-01-26T19:22:15.287Z] [INFO] "tool": "read",
[2026-01-26T19:22:15.287Z] [INFO] "state": {
[2026-01-26T19:22:15.287Z] [INFO] "status": "completed",
[2026-01-26T19:22:15.288Z] [INFO] "input": {
[2026-01-26T19:22:15.288Z] [INFO] "filePath": "/tmp/gh-issue-solver-1769454766276/ver8a/index.html",
[2026-01-26T19:22:15.288Z] [INFO] "limit": 50,
[2026-01-26T19:22:15.289Z] [INFO] "offset": 3047
[2026-01-26T19:22:15.289Z] [INFO] },
[2026-01-26T19:22:15.289Z] [INFO] "output": "<file>\n03048| document.getElementById('rdf-input').value = '';\n03049| }\n03050| \n03051| /**\n03052| * Сохраняет содержимое RDF поля как файл\n03053| */\n03054| function saveAsFile() {\n03055| const content = document.getElementById('rdf-input').value;\n03056| if (!content.trim()) {\n03057| alert('Нет данных для сохранения');\n03058| return;\n03059| }\n03060| \n03061| const format = document.getElementById('input-format').value;\n03062| let extension = 'ttl';\n03063| if (format === 'n-triples') extension = 'nt';\n03064| else if (format === 'n-quads') extension = 'nq';\n03065| else if (format === 'trig') extension = 'trig';\n03066| \n03067| const blob = new Blob([content], { type: 'text/plain' });\n03068| const url = URL.createObjectURL(blob);\n03069| const a = document.createElement('a');\n03070| a.href = url;\n03071| a.download = `rdf-data.${extension}`;\n03072| document.body.appendChild(a);\n03073| a.click();\n03074| document.body.removeChild(a);\n03075| URL.revokeObjectURL(url);\n03076| }\n03077| \n03078| /**\n03079| * Загружает файл, выбранный пользователем\n03080| */\n03081| function loadFile(event) {\n03082| const file = event.target.files[0];\n03083| if (!file) return;\n03084| \n03085| const reader = new FileReader();\n03086| reader.onload = function(e) {\n03087| document.getElementById('rdf-input').value = e.target.result;\n03088| \n03089| // Определяем формат по расширению\n03090| const extension = file.name.split('.').pop().toLowerCase();\n03091| let format = 'turtle';\n03092| if (extension === 'nt') format = 'n-triples';\n03093| else if (extension === 'nq') format = 'n-quads';\n03094| else if (extension === 'trig') format = 'trig';\n03095| \n03096| document.getElementById('input-format').value = format;\n03097| \n\n(File has more lines. Use 'offset' parameter to read beyond line 3097)\n</file>",
[2026-01-26T19:22:15.289Z] [INFO] "title": "ver8a/index.html",
[2026-01-26T19:22:15.289Z] [INFO] "metadata": {
[2026-01-26T19:22:15.289Z] [INFO] "preview": " document.getElementById('rdf-input').value = '';\n }\n\n /**\n * Сохраняет содержимое RDF поля как файл\n */\n function saveAsFile() {\n const content = document.getElementById('rdf-input').value;\n if (!content.trim()) {\n alert('Нет данных для сохранения');\n return;\n }\n\n const format = document.getElementById('input-format').value;\n let extension = 'ttl';\n if (format === 'n-triples') extension = 'nt';\n else if (format === 'n-quads') extension = 'nq';\n else if (format === 'trig') extension = 'trig';\n\n const blob = new Blob([content], { type: 'text/plain' });"
[2026-01-26T19:22:15.290Z] [INFO] },
[2026-01-26T19:22:15.290Z] [INFO] "time": {
[2026-01-26T19:22:15.290Z] [INFO] "start": 1769455335281,
[2026-01-26T19:22:15.290Z] [INFO] "end": 1769455335284
[2026-01-26T19:22:15.290Z] [INFO] }
[2026-01-26T19:22:15.290Z] [INFO] }
[2026-01-26T19:22:15.290Z] [INFO] }
[2026-01-26T19:22:15.291Z] [INFO] }
[2026-01-26T19:22:15.383Z] [INFO] {
[2026-01-26T19:22:15.383Z] [INFO] "type": "text",
[2026-01-26T19:22:15.384Z] [INFO] "timestamp": 1769455335382,
[2026-01-26T19:22:15.384Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:15.384Z] [INFO] "part": {
[2026-01-26T19:22:15.384Z] [INFO] "id": "prt_bfbc1a5d9001uu42MfJjkH2xPF",
[2026-01-26T19:22:15.384Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:15.385Z] [INFO] "messageID": "msg_bfbc19c03001BGKG4BP6fkbaQc",
[2026-01-26T19:22:15.385Z] [INFO] "type": "text",
[2026-01-26T19:22:15.385Z] [INFO] "text": "\nТеперь понимаю! Проблема в том, что:\n\n1. **Поле \"Макс. длина VAD:\"** показывается только когда `visualization-mode` = 'vad' или 'vad-trig'\n2. **Панели Smart Design и Result in SPARQL** показываются только когда:\n - `sparql-mode` = 'smart-design' И \n - `visualization-mode` = 'vad-trig'\n\nПо умолчанию в HTML установлен режим 'vad-trig', но возможно при загрузке страницы не вызывается `updateModeDescription()`.\n\nТеперь проверим, почему кнопки не работают. Проверим основные функции кнопок:",
[2026-01-26T19:22:15.385Z] [INFO] "time": {
[2026-01-26T19:22:15.385Z] [INFO] "start": 1769455335381,
[2026-01-26T19:22:15.385Z] [INFO] "end": 1769455335381
[2026-01-26T19:22:15.385Z] [INFO] }
[2026-01-26T19:22:15.385Z] [INFO] }
[2026-01-26T19:22:15.386Z] [INFO] }
[2026-01-26T19:22:15.390Z] [INFO] {
[2026-01-26T19:22:15.390Z] [INFO] "type": "step_finish",
[2026-01-26T19:22:15.391Z] [INFO] "timestamp": 1769455335390,
[2026-01-26T19:22:15.391Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:15.391Z] [INFO] "part": {
[2026-01-26T19:22:15.391Z] [INFO] "id": "prt_bfbc1b7d7001DOJgaj0e2TbW9s",
[2026-01-26T19:22:15.391Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:15.392Z] [INFO] "messageID": "msg_bfbc19c03001BGKG4BP6fkbaQc",
[2026-01-26T19:22:15.392Z] [INFO] "type": "step-finish",
[2026-01-26T19:22:15.392Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:22:15.393Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a",
[2026-01-26T19:22:15.393Z] [INFO] "cost": 0,
[2026-01-26T19:22:15.393Z] [INFO] "tokens": {
[2026-01-26T19:22:15.393Z] [INFO] "input": 517,
[2026-01-26T19:22:15.393Z] [INFO] "output": 196,
[2026-01-26T19:22:15.394Z] [INFO] "reasoning": 1,
[2026-01-26T19:22:15.394Z] [INFO] "cache": {
[2026-01-26T19:22:15.394Z] [INFO] "read": 78176,
[2026-01-26T19:22:15.394Z] [INFO] "write": 0
[2026-01-26T19:22:15.394Z] [INFO] }
[2026-01-26T19:22:15.394Z] [INFO] }
[2026-01-26T19:22:15.395Z] [INFO] }
[2026-01-26T19:22:15.395Z] [INFO] }
[2026-01-26T19:22:17.792Z] [INFO] {
[2026-01-26T19:22:17.792Z] [INFO] "type": "step_start",
[2026-01-26T19:22:17.793Z] [INFO] "timestamp": 1769455337791,
[2026-01-26T19:22:17.793Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:17.793Z] [INFO] "part": {
[2026-01-26T19:22:17.793Z] [INFO] "id": "prt_bfbc1c13e001e1kGsvOWZmzkKw",
[2026-01-26T19:22:17.793Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:17.793Z] [INFO] "messageID": "msg_bfbc1b7f2001HcXH7RpEJrLCaE",
[2026-01-26T19:22:17.794Z] [INFO] "type": "step-start",
[2026-01-26T19:22:17.794Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a"
[2026-01-26T19:22:17.794Z] [INFO] }
[2026-01-26T19:22:17.794Z] [INFO] }
[2026-01-26T19:22:20.247Z] [INFO] {
[2026-01-26T19:22:20.248Z] [INFO] "type": "tool_use",
[2026-01-26T19:22:20.248Z] [INFO] "timestamp": 1769455340247,
[2026-01-26T19:22:20.248Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:20.248Z] [INFO] "part": {
[2026-01-26T19:22:20.248Z] [INFO] "id": "prt_bfbc1cac9001dfUUGfKjj9o6J9",
[2026-01-26T19:22:20.249Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:20.249Z] [INFO] "messageID": "msg_bfbc1b7f2001HcXH7RpEJrLCaE",
[2026-01-26T19:22:20.249Z] [INFO] "type": "tool",
[2026-01-26T19:22:20.249Z] [INFO] "callID": "call_1a4645624cba41cf92cf9c1b",
[2026-01-26T19:22:20.249Z] [INFO] "tool": "grep",
[2026-01-26T19:22:20.249Z] [INFO] "state": {
[2026-01-26T19:22:20.249Z] [INFO] "status": "completed",
[2026-01-26T19:22:20.249Z] [INFO] "input": {
[2026-01-26T19:22:20.250Z] [INFO] "path": "/tmp/gh-issue-solver-1769454766276/ver8a",
[2026-01-26T19:22:20.250Z] [INFO] "pattern": "window\\.onload|document\\.addEventListener.*DOMContentLoaded|updateModeDescription\\(\\)"
[2026-01-26T19:22:20.250Z] [INFO] },
[2026-01-26T19:22:20.250Z] [INFO] "output": "Found 9 matches\n/tmp/gh-issue-solver-1769454766276/ver8a/index.html:\n Line 76: <select id=\"visualization-mode\" onchange=\"updateModeDescription()\">\n Line 2972: updateModeDescription();\n Line 3005: updateModeDescription();\n Line 5490: document.addEventListener('DOMContentLoaded', function() {\n Line 7143: function updateModeDescription() {\n Line 7948: document.addEventListener('DOMContentLoaded', function() {\n Line 7949: updateModeDescription();\n Line 7974: updateModeDescription();\n Line 8872: document.addEventListener('DOMContentLoaded', function() {",
[2026-01-26T19:22:20.250Z] [INFO] "title": "window\\.onload|document\\.addEventListener.*DOMContentLoaded|updateModeDescription\\(\\)",
[2026-01-26T19:22:20.251Z] [INFO] "metadata": {
[2026-01-26T19:22:20.251Z] [INFO] "matches": 9,
[2026-01-26T19:22:20.251Z] [INFO] "truncated": false
[2026-01-26T19:22:20.251Z] [INFO] },
[2026-01-26T19:22:20.251Z] [INFO] "time": {
[2026-01-26T19:22:20.251Z] [INFO] "start": 1769455340235,
[2026-01-26T19:22:20.251Z] [INFO] "end": 1769455340246
[2026-01-26T19:22:20.252Z] [INFO] }
[2026-01-26T19:22:20.252Z] [INFO] }
[2026-01-26T19:22:20.252Z] [INFO] }
[2026-01-26T19:22:20.252Z] [INFO] }
[2026-01-26T19:22:20.286Z] [INFO] {
[2026-01-26T19:22:20.287Z] [INFO] "type": "text",
[2026-01-26T19:22:20.287Z] [INFO] "timestamp": 1769455340286,
[2026-01-26T19:22:20.287Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:20.287Z] [INFO] "part": {
[2026-01-26T19:22:20.288Z] [INFO] "id": "prt_bfbc1c1c9001e7OTD9cxQRKC30",
[2026-01-26T19:22:20.288Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:20.288Z] [INFO] "messageID": "msg_bfbc1b7f2001HcXH7RpEJrLCaE",
[2026-01-26T19:22:20.288Z] [INFO] "type": "text",
[2026-01-26T19:22:20.288Z] [INFO] "text": "\nВсе функции на месте. Теперь проверим самую главную проблему - почему кнопки вообще не работают. Это может быть связано с отсутствием инициализации при загрузке страницы.\n\nПроверим, есть ли вызов `updateModeDescription()` при загрузке:",
[2026-01-26T19:22:20.288Z] [INFO] "time": {
[2026-01-26T19:22:20.288Z] [INFO] "start": 1769455340285,
[2026-01-26T19:22:20.288Z] [INFO] "end": 1769455340285
[2026-01-26T19:22:20.288Z] [INFO] }
[2026-01-26T19:22:20.289Z] [INFO] }
[2026-01-26T19:22:20.289Z] [INFO] }
[2026-01-26T19:22:20.294Z] [INFO] {
[2026-01-26T19:22:20.295Z] [INFO] "type": "step_finish",
[2026-01-26T19:22:20.295Z] [INFO] "timestamp": 1769455340294,
[2026-01-26T19:22:20.295Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:20.295Z] [INFO] "part": {
[2026-01-26T19:22:20.295Z] [INFO] "id": "prt_bfbc1cafe001OT6BEJNiyA4cwT",
[2026-01-26T19:22:20.296Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:20.296Z] [INFO] "messageID": "msg_bfbc1b7f2001HcXH7RpEJrLCaE",
[2026-01-26T19:22:20.296Z] [INFO] "type": "step-finish",
[2026-01-26T19:22:20.296Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:22:20.296Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a",
[2026-01-26T19:22:20.296Z] [INFO] "cost": 0,
[2026-01-26T19:22:20.297Z] [INFO] "tokens": {
[2026-01-26T19:22:20.297Z] [INFO] "input": 610,
[2026-01-26T19:22:20.297Z] [INFO] "output": 111,
[2026-01-26T19:22:20.297Z] [INFO] "reasoning": 1,
[2026-01-26T19:22:20.297Z] [INFO] "cache": {
[2026-01-26T19:22:20.298Z] [INFO] "read": 78888,
[2026-01-26T19:22:20.298Z] [INFO] "write": 0
[2026-01-26T19:22:20.298Z] [INFO] }
[2026-01-26T19:22:20.299Z] [INFO] }
[2026-01-26T19:22:20.299Z] [INFO] }
[2026-01-26T19:22:20.299Z] [INFO] }
[2026-01-26T19:22:22.868Z] [INFO] {
[2026-01-26T19:22:22.869Z] [INFO] "type": "step_start",
[2026-01-26T19:22:22.869Z] [INFO] "timestamp": 1769455342868,
[2026-01-26T19:22:22.869Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:22.869Z] [INFO] "part": {
[2026-01-26T19:22:22.870Z] [INFO] "id": "prt_bfbc1d512001vXU95xobY24asg",
[2026-01-26T19:22:22.870Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:22.870Z] [INFO] "messageID": "msg_bfbc1cb18001hDPpPoqKPuUMzH",
[2026-01-26T19:22:22.870Z] [INFO] "type": "step-start",
[2026-01-26T19:22:22.870Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a"
[2026-01-26T19:22:22.870Z] [INFO] }
[2026-01-26T19:22:22.870Z] [INFO] }
[2026-01-26T19:22:25.215Z] [INFO] {
[2026-01-26T19:22:25.215Z] [INFO] "type": "tool_use",
[2026-01-26T19:22:25.216Z] [INFO] "timestamp": 1769455345214,
[2026-01-26T19:22:25.216Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:25.216Z] [INFO] "part": {
[2026-01-26T19:22:25.216Z] [INFO] "id": "prt_bfbc1de3a001DDKllC94e5rba4",
[2026-01-26T19:22:25.216Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:25.216Z] [INFO] "messageID": "msg_bfbc1cb18001hDPpPoqKPuUMzH",
[2026-01-26T19:22:25.216Z] [INFO] "type": "tool",
[2026-01-26T19:22:25.216Z] [INFO] "callID": "call_92f9b33811d44e0d870becb7",
[2026-01-26T19:22:25.216Z] [INFO] "tool": "read",
[2026-01-26T19:22:25.217Z] [INFO] "state": {
[2026-01-26T19:22:25.217Z] [INFO] "status": "completed",
[2026-01-26T19:22:25.217Z] [INFO] "input": {
[2026-01-26T19:22:25.217Z] [INFO] "filePath": "/tmp/gh-issue-solver-1769454766276/ver8a/index.html",
[2026-01-26T19:22:25.217Z] [INFO] "limit": 20,
[2026-01-26T19:22:25.217Z] [INFO] "offset": 7948
[2026-01-26T19:22:25.217Z] [INFO] },
[2026-01-26T19:22:25.217Z] [INFO] "output": "<file>\n07949| updateModeDescription();\n07950| \n07951| // Парсим URL параметры\n07952| const params = parseUrlParams();\n07953| \n07954| // Если есть RDF данные в URL, заполняем форму и визуализируем\n07955| if (params.rdf) {\n07956| console.log('Обнаружены RDF данные в URL, загружаем...');\n07957| \n07958| // Заполняем текстовое поле RDF данными\n07959| document.getElementById('rdf-input').value = params.rdf;\n07960| \n07961| // Устанавливаем входной формат, если указан\n07962| if (params.from) {\n07963| document.getElementById('input-format').value = params.from;\n07964| }\n07965| \n07966| // Устанавливаем выходной формат, если указан\n07967| if (params.to) {\n07968| document.getElementById('output-format').value = params.to;\n\n(File has more lines. Use 'offset' parameter to read beyond line 7968)\n</file>",
[2026-01-26T19:22:25.218Z] [INFO] "title": "ver8a/index.html",
[2026-01-26T19:22:25.218Z] [INFO] "metadata": {
[2026-01-26T19:22:25.218Z] [INFO] "preview": " updateModeDescription();\n\n // Парсим URL параметры\n const params = parseUrlParams();\n\n // Если есть RDF данные в URL, заполняем форму и визуализируем\n if (params.rdf) {\n console.log('Обнаружены RDF данные в URL, загружаем...');\n\n // Заполняем текстовое поле RDF данными\n document.getElementById('rdf-input').value = params.rdf;\n\n // Устанавливаем входной формат, если указан\n if (params.from) {\n document.getElementById('input-format').value = params.from;\n }\n\n // Устанавливаем выходной формат, если указан\n if (params.to) {\n document.getElementById('output-format').value = params.to;"
[2026-01-26T19:22:25.218Z] [INFO] },
[2026-01-26T19:22:25.218Z] [INFO] "time": {
[2026-01-26T19:22:25.218Z] [INFO] "start": 1769455345211,
[2026-01-26T19:22:25.218Z] [INFO] "end": 1769455345214
[2026-01-26T19:22:25.218Z] [INFO] }
[2026-01-26T19:22:25.218Z] [INFO] }
[2026-01-26T19:22:25.218Z] [INFO] }
[2026-01-26T19:22:25.218Z] [INFO] }
[2026-01-26T19:22:25.261Z] [INFO] {
[2026-01-26T19:22:25.262Z] [INFO] "type": "text",
[2026-01-26T19:22:25.263Z] [INFO] "timestamp": 1769455345261,
[2026-01-26T19:22:25.263Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:25.263Z] [INFO] "part": {
[2026-01-26T19:22:25.263Z] [INFO] "id": "prt_bfbc1d5990010M0l0VS6I5UzSH",
[2026-01-26T19:22:25.263Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:25.263Z] [INFO] "messageID": "msg_bfbc1cb18001hDPpPoqKPuUMzH",
[2026-01-26T19:22:25.263Z] [INFO] "type": "text",
[2026-01-26T19:22:25.263Z] [INFO] "text": "\nОтлично! Есть вызов `updateModeDescription()` в `DOMContentLoaded` на строке 7949. Но я заметил множественные обработчики `DOMContentLoaded`, что может вызывать конфликты.\n\nПроверим все обработчики:",
[2026-01-26T19:22:25.263Z] [INFO] "time": {
[2026-01-26T19:22:25.264Z] [INFO] "start": 1769455345260,
[2026-01-26T19:22:25.264Z] [INFO] "end": 1769455345260
[2026-01-26T19:22:25.264Z] [INFO] }
[2026-01-26T19:22:25.264Z] [INFO] }
[2026-01-26T19:22:25.264Z] [INFO] }
[2026-01-26T19:22:25.269Z] [INFO] {
[2026-01-26T19:22:25.269Z] [INFO] "type": "step_finish",
[2026-01-26T19:22:25.270Z] [INFO] "timestamp": 1769455345269,
[2026-01-26T19:22:25.270Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:25.270Z] [INFO] "part": {
[2026-01-26T19:22:25.270Z] [INFO] "id": "prt_bfbc1de6d001Oh5sLYrybh793Y",
[2026-01-26T19:22:25.270Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:25.270Z] [INFO] "messageID": "msg_bfbc1cb18001hDPpPoqKPuUMzH",
[2026-01-26T19:22:25.270Z] [INFO] "type": "step-finish",
[2026-01-26T19:22:25.270Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:22:25.270Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a",
[2026-01-26T19:22:25.270Z] [INFO] "cost": 0,
[2026-01-26T19:22:25.271Z] [INFO] "tokens": {
[2026-01-26T19:22:25.271Z] [INFO] "input": 161,
[2026-01-26T19:22:25.271Z] [INFO] "output": 107,
[2026-01-26T19:22:25.271Z] [INFO] "reasoning": 1,
[2026-01-26T19:22:25.271Z] [INFO] "cache": {
[2026-01-26T19:22:25.271Z] [INFO] "read": 79608,
[2026-01-26T19:22:25.271Z] [INFO] "write": 0
[2026-01-26T19:22:25.271Z] [INFO] }
[2026-01-26T19:22:25.271Z] [INFO] }
[2026-01-26T19:22:25.271Z] [INFO] }
[2026-01-26T19:22:25.271Z] [INFO] }
[2026-01-26T19:22:28.889Z] [INFO] {
[2026-01-26T19:22:28.890Z] [INFO] "type": "step_start",
[2026-01-26T19:22:28.890Z] [INFO] "timestamp": 1769455348888,
[2026-01-26T19:22:28.891Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:28.891Z] [INFO] "part": {
[2026-01-26T19:22:28.891Z] [INFO] "id": "prt_bfbc1ec970013d5IOhgmdHJgHh",
[2026-01-26T19:22:28.891Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:28.891Z] [INFO] "messageID": "msg_bfbc1de8a001rIoqSR1a59MI25",
[2026-01-26T19:22:28.892Z] [INFO] "type": "step-start",
[2026-01-26T19:22:28.892Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a"
[2026-01-26T19:22:28.892Z] [INFO] }
[2026-01-26T19:22:28.892Z] [INFO] }
[2026-01-26T19:22:29.745Z] [INFO] {
[2026-01-26T19:22:29.745Z] [INFO] "type": "tool_use",
[2026-01-26T19:22:29.746Z] [INFO] "timestamp": 1769455349744,
[2026-01-26T19:22:29.746Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:29.746Z] [INFO] "part": {
[2026-01-26T19:22:29.746Z] [INFO] "id": "prt_bfbc1efec001bIZefJHENZaSiq",
[2026-01-26T19:22:29.746Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:29.746Z] [INFO] "messageID": "msg_bfbc1de8a001rIoqSR1a59MI25",
[2026-01-26T19:22:29.746Z] [INFO] "type": "tool",
[2026-01-26T19:22:29.747Z] [INFO] "callID": "call_58224678f83a48a8b67bd894",
[2026-01-26T19:22:29.747Z] [INFO] "tool": "read",
[2026-01-26T19:22:29.747Z] [INFO] "state": {
[2026-01-26T19:22:29.747Z] [INFO] "status": "completed",
[2026-01-26T19:22:29.747Z] [INFO] "input": {
[2026-01-26T19:22:29.747Z] [INFO] "filePath": "/tmp/gh-issue-solver-1769454766276/ver8a/index.html",
[2026-01-26T19:22:29.747Z] [INFO] "limit": 20,
[2026-01-26T19:22:29.748Z] [INFO] "offset": 8872
[2026-01-26T19:22:29.748Z] [INFO] },
[2026-01-26T19:22:29.748Z] [INFO] "output": "<file>\n08873| loadTechAppendix();\n08874| });\n08875| </script>\n08876| </body>\n08877| </html>\n08878| \n\n(End of file - total 8878 lines)\n</file>",
[2026-01-26T19:22:29.748Z] [INFO] "title": "ver8a/index.html",
[2026-01-26T19:22:29.748Z] [INFO] "metadata": {
[2026-01-26T19:22:29.748Z] [INFO] "preview": " loadTechAppendix();\n });\n </script>\n</body>\n</html>\n"
[2026-01-26T19:22:29.748Z] [INFO] },
[2026-01-26T19:22:29.748Z] [INFO] "time": {
[2026-01-26T19:22:29.749Z] [INFO] "start": 1769455349741,
[2026-01-26T19:22:29.749Z] [INFO] "end": 1769455349744
[2026-01-26T19:22:29.749Z] [INFO] }
[2026-01-26T19:22:29.749Z] [INFO] }
[2026-01-26T19:22:29.749Z] [INFO] }
[2026-01-26T19:22:29.749Z] [INFO] }
[2026-01-26T19:22:29.788Z] [INFO] {
[2026-01-26T19:22:29.788Z] [INFO] "type": "text",
[2026-01-26T19:22:29.789Z] [INFO] "timestamp": 1769455349787,
[2026-01-26T19:22:29.789Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:29.789Z] [INFO] "part": {
[2026-01-26T19:22:29.789Z] [INFO] "id": "prt_bfbc1ed1b001YxGxqMeXQhYtrf",
[2026-01-26T19:22:29.789Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:29.789Z] [INFO] "messageID": "msg_bfbc1de8a001rIoqSR1a59MI25",
[2026-01-26T19:22:29.790Z] [INFO] "type": "text",
[2026-01-26T19:22:29.790Z] [INFO] "text": "",
[2026-01-26T19:22:29.791Z] [INFO] "time": {
[2026-01-26T19:22:29.791Z] [INFO] "start": 1769455349787,
[2026-01-26T19:22:29.791Z] [INFO] "end": 1769455349787
[2026-01-26T19:22:29.791Z] [INFO] }
[2026-01-26T19:22:29.792Z] [INFO] }
[2026-01-26T19:22:29.792Z] [INFO] }
[2026-01-26T19:22:29.796Z] [INFO] {
[2026-01-26T19:22:29.797Z] [INFO] "type": "step_finish",
[2026-01-26T19:22:29.797Z] [INFO] "timestamp": 1769455349796,
[2026-01-26T19:22:29.797Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:29.797Z] [INFO] "part": {
[2026-01-26T19:22:29.797Z] [INFO] "id": "prt_bfbc1f01c001X5H37Hr0ZHrGif",
[2026-01-26T19:22:29.797Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:29.797Z] [INFO] "messageID": "msg_bfbc1de8a001rIoqSR1a59MI25",
[2026-01-26T19:22:29.797Z] [INFO] "type": "step-finish",
[2026-01-26T19:22:29.798Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:22:29.798Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a",
[2026-01-26T19:22:29.798Z] [INFO] "cost": 0,
[2026-01-26T19:22:29.798Z] [INFO] "tokens": {
[2026-01-26T19:22:29.798Z] [INFO] "input": 274,
[2026-01-26T19:22:29.798Z] [INFO] "output": 55,
[2026-01-26T19:22:29.798Z] [INFO] "reasoning": 1,
[2026-01-26T19:22:29.798Z] [INFO] "cache": {
[2026-01-26T19:22:29.798Z] [INFO] "read": 79875,
[2026-01-26T19:22:29.798Z] [INFO] "write": 0
[2026-01-26T19:22:29.799Z] [INFO] }
[2026-01-26T19:22:29.799Z] [INFO] }
[2026-01-26T19:22:29.799Z] [INFO] }
[2026-01-26T19:22:29.799Z] [INFO] }
[2026-01-26T19:22:31.951Z] [INFO] {
[2026-01-26T19:22:31.952Z] [INFO] "type": "step_start",
[2026-01-26T19:22:31.952Z] [INFO] "timestamp": 1769455351951,
[2026-01-26T19:22:31.952Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:31.952Z] [INFO] "part": {
[2026-01-26T19:22:31.952Z] [INFO] "id": "prt_bfbc1f88d001HSZd58PkI9dtwS",
[2026-01-26T19:22:31.952Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:31.953Z] [INFO] "messageID": "msg_bfbc1f03a001zWnRyEedrzWSrd",
[2026-01-26T19:22:31.953Z] [INFO] "type": "step-start",
[2026-01-26T19:22:31.954Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a"
[2026-01-26T19:22:31.954Z] [INFO] }
[2026-01-26T19:22:31.954Z] [INFO] }
[2026-01-26T19:22:33.172Z] [INFO] {
[2026-01-26T19:22:33.172Z] [INFO] "type": "tool_use",
[2026-01-26T19:22:33.172Z] [INFO] "timestamp": 1769455353171,
[2026-01-26T19:22:33.172Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:33.172Z] [INFO] "part": {
[2026-01-26T19:22:33.172Z] [INFO] "id": "prt_bfbc1fd4f001mq5cbmoWZRcB50",
[2026-01-26T19:22:33.173Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:33.173Z] [INFO] "messageID": "msg_bfbc1f03a001zWnRyEedrzWSrd",
[2026-01-26T19:22:33.173Z] [INFO] "type": "tool",
[2026-01-26T19:22:33.173Z] [INFO] "callID": "call_94cbfa3aed1e4de5bf44d2a6",
[2026-01-26T19:22:33.173Z] [INFO] "tool": "read",
[2026-01-26T19:22:33.173Z] [INFO] "state": {
[2026-01-26T19:22:33.173Z] [INFO] "status": "completed",
[2026-01-26T19:22:33.173Z] [INFO] "input": {
[2026-01-26T19:22:33.173Z] [INFO] "filePath": "/tmp/gh-issue-solver-1769454766276/ver8a/index.html",
[2026-01-26T19:22:33.173Z] [INFO] "limit": 30,
[2026-01-26T19:22:33.174Z] [INFO] "offset": 5490
[2026-01-26T19:22:33.174Z] [INFO] },
[2026-01-26T19:22:33.174Z] [INFO] "output": "<file>\n05491| const trigSelect = document.getElementById('smart-design-trig');\n05492| const subjectSelect = document.getElementById('smart-design-subject');\n05493| const subjectTypeSelect = document.getElementById('smart-design-subject-type');\n05494| const predicateSelect = document.getElementById('smart-design-predicate');\n05495| const objectSelect = document.getElementById('smart-design-object');\n05496| \n05497| if (trigSelect) {\n05498| trigSelect.addEventListener('change', function() {\n05499| // TriG теперь не имеет опции __NEW__, просто обновляем состояние полей\n05500| updateSmartDesignFieldsState();\n05501| // Перезаполняем Subject Type в зависимости от выбранного TriG\n05502| populateSubjectTypeDropdown();\n05503| // Перезаполняем Subject и Predicate в зависимости от выбранного TriG (vad:ptree или нет)\n05504| updateSubjectPredicateDropdowns();\n05505| });\n05506| }\n05507| \n05508| if (subjectSelect) {\n05509| subjectSelect.addEventListener('change', function() {\n05510| // Используем специализированный обработчик для Subject с логикой концепт/индивид\n05511| handleSubjectSelectChange(this);\n05512| // При выборе существующего Subject, автоматически определяем его тип\n05513| autoDetectSubjectType();\n05514| });\n05515| }\n05516| \n05517| if (subjectTypeSelect) {\n05518| subjectTypeSelect.addEventListener('change', function() {\n05519| // При изменении Subject Type, обновляем список допустимых Subject и Predicate\n05520| updateSubjectsBySubjectType();\n\n(File has more lines. Use 'offset' parameter to read beyond line 5520)\n</file>",
[2026-01-26T19:22:33.174Z] [INFO] "title": "ver8a/index.html",
[2026-01-26T19:22:33.174Z] [INFO] "metadata": {
[2026-01-26T19:22:33.174Z] [INFO] "preview": " const trigSelect = document.getElementById('smart-design-trig');\n const subjectSelect = document.getElementById('smart-design-subject');\n const subjectTypeSelect = document.getElementById('smart-design-subject-type');\n const predicateSelect = document.getElementById('smart-design-predicate');\n const objectSelect = document.getElementById('smart-design-object');\n\n if (trigSelect) {\n trigSelect.addEventListener('change', function() {\n // TriG теперь не имеет опции __NEW__, просто обновляем состояние полей\n updateSmartDesignFieldsState();\n // Перезаполняем Subject Type в зависимости от выбранного TriG\n populateSubjectTypeDropdown();\n // Перезаполняем Subject и Predicate в зависимости от выбранного TriG (vad:ptree или нет)\n updateSubjectPredicateDropdowns();\n });\n }\n\n if (subjectSelect) {\n subjectSelect.addEventListener('change', function() {\n // Используем специализированный обработчик для Subject с логикой концепт/индивид"
[2026-01-26T19:22:33.174Z] [INFO] },
[2026-01-26T19:22:33.174Z] [INFO] "time": {
[2026-01-26T19:22:33.174Z] [INFO] "start": 1769455353169,
[2026-01-26T19:22:33.174Z] [INFO] "end": 1769455353171
[2026-01-26T19:22:33.175Z] [INFO] }
[2026-01-26T19:22:33.175Z] [INFO] }
[2026-01-26T19:22:33.175Z] [INFO] }
[2026-01-26T19:22:33.175Z] [INFO] }
[2026-01-26T19:22:33.215Z] [INFO] {
[2026-01-26T19:22:33.215Z] [INFO] "type": "text",
[2026-01-26T19:22:33.216Z] [INFO] "timestamp": 1769455353214,
[2026-01-26T19:22:33.216Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:33.216Z] [INFO] "part": {
[2026-01-26T19:22:33.217Z] [INFO] "id": "prt_bfbc1f913001cRC7MGDEmtxrC4",
[2026-01-26T19:22:33.217Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:33.217Z] [INFO] "messageID": "msg_bfbc1f03a001zWnRyEedrzWSrd",
[2026-01-26T19:22:33.217Z] [INFO] "type": "text",
[2026-01-26T19:22:33.217Z] [INFO] "text": "\nТеперь нужно посмотреть на обработчик на строке 5490:",
[2026-01-26T19:22:33.218Z] [INFO] "time": {
[2026-01-26T19:22:33.218Z] [INFO] "start": 1769455353214,
[2026-01-26T19:22:33.218Z] [INFO] "end": 1769455353214
[2026-01-26T19:22:33.218Z] [INFO] }
[2026-01-26T19:22:33.218Z] [INFO] }
[2026-01-26T19:22:33.218Z] [INFO] }
[2026-01-26T19:22:33.224Z] [INFO] {
[2026-01-26T19:22:33.224Z] [INFO] "type": "step_finish",
[2026-01-26T19:22:33.225Z] [INFO] "timestamp": 1769455353223,
[2026-01-26T19:22:33.225Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:33.225Z] [INFO] "part": {
[2026-01-26T19:22:33.225Z] [INFO] "id": "prt_bfbc1fd7f001lHTiJhJboAGc6c",
[2026-01-26T19:22:33.225Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:33.225Z] [INFO] "messageID": "msg_bfbc1f03a001zWnRyEedrzWSrd",
[2026-01-26T19:22:33.226Z] [INFO] "type": "step-finish",
[2026-01-26T19:22:33.226Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:22:33.226Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a",
[2026-01-26T19:22:33.226Z] [INFO] "cost": 0,
[2026-01-26T19:22:33.226Z] [INFO] "tokens": {
[2026-01-26T19:22:33.226Z] [INFO] "input": 67,
[2026-01-26T19:22:33.227Z] [INFO] "output": 69,
[2026-01-26T19:22:33.227Z] [INFO] "reasoning": 1,
[2026-01-26T19:22:33.227Z] [INFO] "cache": {
[2026-01-26T19:22:33.227Z] [INFO] "read": 80203,
[2026-01-26T19:22:33.227Z] [INFO] "write": 0
[2026-01-26T19:22:33.227Z] [INFO] }
[2026-01-26T19:22:33.227Z] [INFO] }
[2026-01-26T19:22:33.228Z] [INFO] }
[2026-01-26T19:22:33.228Z] [INFO] }
[2026-01-26T19:22:35.846Z] [INFO] {
[2026-01-26T19:22:35.846Z] [INFO] "type": "step_start",
[2026-01-26T19:22:35.847Z] [INFO] "timestamp": 1769455355845,
[2026-01-26T19:22:35.847Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:35.847Z] [INFO] "part": {
[2026-01-26T19:22:35.847Z] [INFO] "id": "prt_bfbc207c4001769Jg0BX3k2Q5r",
[2026-01-26T19:22:35.848Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:35.848Z] [INFO] "messageID": "msg_bfbc1fd9c001UVfzcX3EbogPyr",
[2026-01-26T19:22:35.848Z] [INFO] "type": "step-start",
[2026-01-26T19:22:35.848Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a"
[2026-01-26T19:22:35.848Z] [INFO] }
[2026-01-26T19:22:35.848Z] [INFO] }
[2026-01-26T19:22:37.063Z] [INFO] {
[2026-01-26T19:22:37.063Z] [INFO] "type": "tool_use",
[2026-01-26T19:22:37.063Z] [INFO] "timestamp": 1769455357062,
[2026-01-26T19:22:37.064Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:37.064Z] [INFO] "part": {
[2026-01-26T19:22:37.064Z] [INFO] "id": "prt_bfbc20c83001JbZpWZ34tPo9ux",
[2026-01-26T19:22:37.064Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:37.064Z] [INFO] "messageID": "msg_bfbc1fd9c001UVfzcX3EbogPyr",
[2026-01-26T19:22:37.064Z] [INFO] "type": "tool",
[2026-01-26T19:22:37.064Z] [INFO] "callID": "call_b44e77a9596841a4ac4788f6",
[2026-01-26T19:22:37.064Z] [INFO] "tool": "read",
[2026-01-26T19:22:37.064Z] [INFO] "state": {
[2026-01-26T19:22:37.065Z] [INFO] "status": "completed",
[2026-01-26T19:22:37.065Z] [INFO] "input": {
[2026-01-26T19:22:37.065Z] [INFO] "filePath": "/tmp/gh-issue-solver-1769454766276/ver8a/index.html",
[2026-01-26T19:22:37.065Z] [INFO] "limit": 10,
[2026-01-26T19:22:37.065Z] [INFO] "offset": 5488
[2026-01-26T19:22:37.065Z] [INFO] },
[2026-01-26T19:22:37.065Z] [INFO] "output": "<file>\n05489| // Добавляем обработчики событий для выпадающих списков Smart Design\n05490| document.addEventListener('DOMContentLoaded', function() {\n05491| const trigSelect = document.getElementById('smart-design-trig');\n05492| const subjectSelect = document.getElementById('smart-design-subject');\n05493| const subjectTypeSelect = document.getElementById('smart-design-subject-type');\n05494| const predicateSelect = document.getElementById('smart-design-predicate');\n05495| const objectSelect = document.getElementById('smart-design-object');\n05496| \n05497| if (trigSelect) {\n05498| trigSelect.addEventListener('change', function() {\n\n(File has more lines. Use 'offset' parameter to read beyond line 5498)\n</file>",
[2026-01-26T19:22:37.065Z] [INFO] "title": "ver8a/index.html",
[2026-01-26T19:22:37.066Z] [INFO] "metadata": {
[2026-01-26T19:22:37.066Z] [INFO] "preview": " // Добавляем обработчики событий для выпадающих списков Smart Design\n document.addEventListener('DOMContentLoaded', function() {\n const trigSelect = document.getElementById('smart-design-trig');\n const subjectSelect = document.getElementById('smart-design-subject');\n const subjectTypeSelect = document.getElementById('smart-design-subject-type');\n const predicateSelect = document.getElementById('smart-design-predicate');\n const objectSelect = document.getElementById('smart-design-object');\n\n if (trigSelect) {\n trigSelect.addEventListener('change', function() {"
[2026-01-26T19:22:37.066Z] [INFO] },
[2026-01-26T19:22:37.066Z] [INFO] "time": {
[2026-01-26T19:22:37.066Z] [INFO] "start": 1769455357060,
[2026-01-26T19:22:37.066Z] [INFO] "end": 1769455357062
[2026-01-26T19:22:37.066Z] [INFO] }
[2026-01-26T19:22:37.066Z] [INFO] }
[2026-01-26T19:22:37.066Z] [INFO] }
[2026-01-26T19:22:37.067Z] [INFO] }
[2026-01-26T19:22:37.103Z] [INFO] {
[2026-01-26T19:22:37.104Z] [INFO] "type": "text",
[2026-01-26T19:22:37.104Z] [INFO] "timestamp": 1769455357103,
[2026-01-26T19:22:37.104Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:37.105Z] [INFO] "part": {
[2026-01-26T19:22:37.105Z] [INFO] "id": "prt_bfbc20849001UGhZQJOBmq5WPD",
[2026-01-26T19:22:37.105Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:37.105Z] [INFO] "messageID": "msg_bfbc1fd9c001UVfzcX3EbogPyr",
[2026-01-26T19:22:37.105Z] [INFO] "type": "text",
[2026-01-26T19:22:37.105Z] [INFO] "text": "\nЭто обработчики событий для элементов Smart Design. Найдем основной обработчик DOMContentLoaded:",
[2026-01-26T19:22:37.105Z] [INFO] "time": {
[2026-01-26T19:22:37.105Z] [INFO] "start": 1769455357102,
[2026-01-26T19:22:37.106Z] [INFO] "end": 1769455357102
[2026-01-26T19:22:37.106Z] [INFO] }
[2026-01-26T19:22:37.106Z] [INFO] }
[2026-01-26T19:22:37.106Z] [INFO] }
[2026-01-26T19:22:37.112Z] [INFO] {
[2026-01-26T19:22:37.112Z] [INFO] "type": "step_finish",
[2026-01-26T19:22:37.112Z] [INFO] "timestamp": 1769455357111,
[2026-01-26T19:22:37.112Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:37.113Z] [INFO] "part": {
[2026-01-26T19:22:37.113Z] [INFO] "id": "prt_bfbc20caf001NVc6rcuXOmdVBO",
[2026-01-26T19:22:37.113Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:37.113Z] [INFO] "messageID": "msg_bfbc1fd9c001UVfzcX3EbogPyr",
[2026-01-26T19:22:37.113Z] [INFO] "type": "step-finish",
[2026-01-26T19:22:37.113Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:22:37.113Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a",
[2026-01-26T19:22:37.113Z] [INFO] "cost": 0,
[2026-01-26T19:22:37.114Z] [INFO] "tokens": {
[2026-01-26T19:22:37.114Z] [INFO] "input": 437,
[2026-01-26T19:22:37.114Z] [INFO] "output": 73,
[2026-01-26T19:22:37.114Z] [INFO] "reasoning": 1,
[2026-01-26T19:22:37.114Z] [INFO] "cache": {
[2026-01-26T19:22:37.114Z] [INFO] "read": 80338,
[2026-01-26T19:22:37.114Z] [INFO] "write": 0
[2026-01-26T19:22:37.114Z] [INFO] }
[2026-01-26T19:22:37.114Z] [INFO] }
[2026-01-26T19:22:37.114Z] [INFO] }
[2026-01-26T19:22:37.115Z] [INFO] }
[2026-01-26T19:22:39.444Z] [INFO] {
[2026-01-26T19:22:39.444Z] [INFO] "type": "step_start",
[2026-01-26T19:22:39.445Z] [INFO] "timestamp": 1769455359444,
[2026-01-26T19:22:39.445Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:39.445Z] [INFO] "part": {
[2026-01-26T19:22:39.445Z] [INFO] "id": "prt_bfbc215d3001DZaBGoVrK1xgY3",
[2026-01-26T19:22:39.446Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:39.446Z] [INFO] "messageID": "msg_bfbc20cc9001PsgvYbZHRTSjG8",
[2026-01-26T19:22:39.446Z] [INFO] "type": "step-start",
[2026-01-26T19:22:39.447Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a"
[2026-01-26T19:22:39.447Z] [INFO] }
[2026-01-26T19:22:39.447Z] [INFO] }
[2026-01-26T19:22:49.604Z] [INFO] {
[2026-01-26T19:22:49.605Z] [INFO] "type": "tool_use",
[2026-01-26T19:22:49.605Z] [INFO] "timestamp": 1769455369604,
[2026-01-26T19:22:49.605Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:49.605Z] [INFO] "part": {
[2026-01-26T19:22:49.606Z] [INFO] "id": "prt_bfbc23d80001TkGZx2YOMWcX2i",
[2026-01-26T19:22:49.606Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:49.606Z] [INFO] "messageID": "msg_bfbc20cc9001PsgvYbZHRTSjG8",
[2026-01-26T19:22:49.606Z] [INFO] "type": "tool",
[2026-01-26T19:22:49.606Z] [INFO] "callID": "call_1c987f765f6e4b5b90905dcc",
[2026-01-26T19:22:49.606Z] [INFO] "tool": "todowrite",
[2026-01-26T19:22:49.606Z] [INFO] "state": {
[2026-01-26T19:22:49.606Z] [INFO] "status": "completed",
[2026-01-26T19:22:49.607Z] [INFO] "input": {
[2026-01-26T19:22:49.607Z] [INFO] "todos": [
[2026-01-26T19:22:49.607Z] [INFO] {
[2026-01-26T19:22:49.607Z] [INFO] "content": "Анализ проблемы: Изучить issue #169 и понять что не работает в ver8a",
[2026-01-26T19:22:49.607Z] [INFO] "id": "1",
[2026-01-26T19:22:49.607Z] [INFO] "priority": "high",
[2026-01-26T19:22:49.607Z] [INFO] "status": "completed"
[2026-01-26T19:22:49.607Z] [INFO] },
[2026-01-26T19:22:49.607Z] [INFO] {
[2026-01-26T19:22:49.608Z] [INFO] "content": "Проверить текущую структуру файлов ver8a и наличие необходимых файлов",
[2026-01-26T19:22:49.608Z] [INFO] "id": "2",
[2026-01-26T19:22:49.608Z] [INFO] "priority": "high",
[2026-01-26T19:22:49.608Z] [INFO] "status": "completed"
[2026-01-26T19:22:49.608Z] [INFO] },
[2026-01-26T19:22:49.608Z] [INFO] {
[2026-01-26T19:22:49.608Z] [INFO] "content": "Исследовать ошибки загрузки файлов RDF данных или URI",
[2026-01-26T19:22:49.608Z] [INFO] "id": "3",
[2026-01-26T19:22:49.609Z] [INFO] "priority": "high",
[2026-01-26T19:22:49.609Z] [INFO] "status": "in_progress"
[2026-01-26T19:22:49.609Z] [INFO] },
[2026-01-26T19:22:49.610Z] [INFO] {
[2026-01-26T19:22:49.610Z] [INFO] "content": "Проверить работу кнопок: Показать, Очистить, Тест, Сохранить как",
[2026-01-26T19:22:49.610Z] [INFO] "id": "4",
[2026-01-26T19:22:49.610Z] [INFO] "priority": "high",
[2026-01-26T19:22:49.610Z] [INFO] "status": "pending"
[2026-01-26T19:22:49.610Z] [INFO] },
[2026-01-26T19:22:49.610Z] [INFO] {
[2026-01-26T19:22:49.610Z] [INFO] "content": "Исправить отображение полей: Макс. длина VAD, Smart Design, Result in SPARQL",
[2026-01-26T19:22:49.611Z] [INFO] "id": "5",
[2026-01-26T19:22:49.611Z] [INFO] "priority": "high",
[2026-01-26T19:22:49.611Z] [INFO] "status": "pending"
[2026-01-26T19:22:49.611Z] [INFO] },
[2026-01-26T19:22:49.611Z] [INFO] {
[2026-01-26T19:22:49.611Z] [INFO] "content": "Протестировать все исправления и убедиться что работает",
[2026-01-26T19:22:49.611Z] [INFO] "id": "6",
[2026-01-26T19:22:49.611Z] [INFO] "priority": "medium",
[2026-01-26T19:22:49.611Z] [INFO] "status": "pending"
[2026-01-26T19:22:49.612Z] [INFO] },
[2026-01-26T19:22:49.612Z] [INFO] {
[2026-01-26T19:22:49.612Z] [INFO] "content": "Создать pull request с исправлениями",
[2026-01-26T19:22:49.612Z] [INFO] "id": "7",
[2026-01-26T19:22:49.612Z] [INFO] "priority": "medium",
[2026-01-26T19:22:49.612Z] [INFO] "status": "pending"
[2026-01-26T19:22:49.612Z] [INFO] }
[2026-01-26T19:22:49.612Z] [INFO] ]
[2026-01-26T19:22:49.612Z] [INFO] },
[2026-01-26T19:22:49.613Z] [INFO] "output": "[\n {\n \"content\": \"Анализ проблемы: Изучить issue #169 и понять что не работает в ver8a\",\n \"id\": \"1\",\n \"priority\": \"high\",\n \"status\": \"completed\"\n },\n {\n \"content\": \"Проверить текущую структуру файлов ver8a и наличие необходимых файлов\",\n \"id\": \"2\",\n \"priority\": \"high\",\n \"status\": \"completed\"\n },\n {\n \"content\": \"Исследовать ошибки загрузки файлов RDF данных или URI\",\n \"id\": \"3\",\n \"priority\": \"high\",\n \"status\": \"in_progress\"\n },\n {\n \"content\": \"Проверить работу кнопок: Показать, Очистить, Тест, Сохранить как\",\n \"id\": \"4\",\n \"priority\": \"high\",\n \"status\": \"pending\"\n },\n {\n \"content\": \"Исправить отображение полей: Макс. длина VAD, Smart Design, Result in SPARQL\",\n \"id\": \"5\",\n \"priority\": \"high\",\n \"status\": \"pending\"\n },\n {\n \"content\": \"Протестировать все исправления и убедиться что работает\",\n \"id\": \"6\",\n \"priority\": \"medium\",\n \"status\": \"pending\"\n },\n {\n \"content\": \"Создать pull request с исправлениями\",\n \"id\": \"7\",\n \"priority\": \"medium\",\n \"status\": \"pending\"\n }\n]",
[2026-01-26T19:22:49.613Z] [INFO] "title": "5 todos",
[2026-01-26T19:22:49.613Z] [INFO] "metadata": {
[2026-01-26T19:22:49.613Z] [INFO] "todos": [
[2026-01-26T19:22:49.613Z] [INFO] {
[2026-01-26T19:22:49.613Z] [INFO] "content": "Анализ проблемы: Изучить issue #169 и понять что не работает в ver8a",
[2026-01-26T19:22:49.613Z] [INFO] "id": "1",
[2026-01-26T19:22:49.614Z] [INFO] "priority": "high",
[2026-01-26T19:22:49.614Z] [INFO] "status": "completed"
[2026-01-26T19:22:49.614Z] [INFO] },
[2026-01-26T19:22:49.614Z] [INFO] {
[2026-01-26T19:22:49.615Z] [INFO] "content": "Проверить текущую структуру файлов ver8a и наличие необходимых файлов",
[2026-01-26T19:22:49.615Z] [INFO] "id": "2",
[2026-01-26T19:22:49.616Z] [INFO] "priority": "high",
[2026-01-26T19:22:49.616Z] [INFO] "status": "completed"
[2026-01-26T19:22:49.616Z] [INFO] },
[2026-01-26T19:22:49.616Z] [INFO] {
[2026-01-26T19:22:49.616Z] [INFO] "content": "Исследовать ошибки загрузки файлов RDF данных или URI",
[2026-01-26T19:22:49.616Z] [INFO] "id": "3",
[2026-01-26T19:22:49.617Z] [INFO] "priority": "high",
[2026-01-26T19:22:49.617Z] [INFO] "status": "in_progress"
[2026-01-26T19:22:49.617Z] [INFO] },
[2026-01-26T19:22:49.617Z] [INFO] {
[2026-01-26T19:22:49.617Z] [INFO] "content": "Проверить работу кнопок: Показать, Очистить, Тест, Сохранить как",
[2026-01-26T19:22:49.617Z] [INFO] "id": "4",
[2026-01-26T19:22:49.617Z] [INFO] "priority": "high",
[2026-01-26T19:22:49.618Z] [INFO] "status": "pending"
[2026-01-26T19:22:49.618Z] [INFO] },
[2026-01-26T19:22:49.618Z] [INFO] {
[2026-01-26T19:22:49.618Z] [INFO] "content": "Исправить отображение полей: Макс. длина VAD, Smart Design, Result in SPARQL",
[2026-01-26T19:22:49.618Z] [INFO] "id": "5",
[2026-01-26T19:22:49.618Z] [INFO] "priority": "high",
[2026-01-26T19:22:49.618Z] [INFO] "status": "pending"
[2026-01-26T19:22:49.619Z] [INFO] },
[2026-01-26T19:22:49.619Z] [INFO] {
[2026-01-26T19:22:49.619Z] [INFO] "content": "Протестировать все исправления и убедиться что работает",
[2026-01-26T19:22:49.619Z] [INFO] "id": "6",
[2026-01-26T19:22:49.620Z] [INFO] "priority": "medium",
[2026-01-26T19:22:49.620Z] [INFO] "status": "pending"
[2026-01-26T19:22:49.620Z] [INFO] },
[2026-01-26T19:22:49.620Z] [INFO] {
[2026-01-26T19:22:49.621Z] [INFO] "content": "Создать pull request с исправлениями",
[2026-01-26T19:22:49.621Z] [INFO] "id": "7",
[2026-01-26T19:22:49.621Z] [INFO] "priority": "medium",
[2026-01-26T19:22:49.621Z] [INFO] "status": "pending"
[2026-01-26T19:22:49.621Z] [INFO] }
[2026-01-26T19:22:49.621Z] [INFO] ]
[2026-01-26T19:22:49.621Z] [INFO] },
[2026-01-26T19:22:49.621Z] [INFO] "time": {
[2026-01-26T19:22:49.622Z] [INFO] "start": 1769455369602,
[2026-01-26T19:22:49.622Z] [INFO] "end": 1769455369603
[2026-01-26T19:22:49.622Z] [INFO] }
[2026-01-26T19:22:49.622Z] [INFO] }
[2026-01-26T19:22:49.623Z] [INFO] }
[2026-01-26T19:22:49.623Z] [INFO] }
[2026-01-26T19:22:49.646Z] [INFO] {
[2026-01-26T19:22:49.646Z] [INFO] "type": "text",
[2026-01-26T19:22:49.647Z] [INFO] "timestamp": 1769455369645,
[2026-01-26T19:22:49.647Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:49.647Z] [INFO] "part": {
[2026-01-26T19:22:49.647Z] [INFO] "id": "prt_bfbc215d400285v1vD0B3ObY8x",
[2026-01-26T19:22:49.647Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:49.647Z] [INFO] "messageID": "msg_bfbc20cc9001PsgvYbZHRTSjG8",
[2026-01-26T19:22:49.648Z] [INFO] "type": "text",
[2026-01-26T19:22:49.648Z] [INFO] "text": "\nПонял! У меня есть несколько обработчиков DOMContentLoaded. Теперь нужно понять, почему функции не работают. Давайте проверим, правильно ли вызывается основная функция визуализации.",
[2026-01-26T19:22:49.648Z] [INFO] "time": {
[2026-01-26T19:22:49.648Z] [INFO] "start": 1769455369645,
[2026-01-26T19:22:49.648Z] [INFO] "end": 1769455369645
[2026-01-26T19:22:49.648Z] [INFO] }
[2026-01-26T19:22:49.648Z] [INFO] }
[2026-01-26T19:22:49.648Z] [INFO] }
[2026-01-26T19:22:49.654Z] [INFO] {
[2026-01-26T19:22:49.654Z] [INFO] "type": "step_finish",
[2026-01-26T19:22:49.655Z] [INFO] "timestamp": 1769455369654,
[2026-01-26T19:22:49.655Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:49.655Z] [INFO] "part": {
[2026-01-26T19:22:49.655Z] [INFO] "id": "prt_bfbc23dae001e1hPhKLAMhO96t",
[2026-01-26T19:22:49.655Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:22:49.655Z] [INFO] "messageID": "msg_bfbc20cc9001PsgvYbZHRTSjG8",
[2026-01-26T19:22:49.655Z] [INFO] "type": "step-finish",
[2026-01-26T19:22:49.655Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:22:49.655Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a",
[2026-01-26T19:22:49.656Z] [INFO] "cost": 0,
[2026-01-26T19:22:49.656Z] [INFO] "tokens": {
[2026-01-26T19:22:49.656Z] [INFO] "input": 181,
[2026-01-26T19:22:49.656Z] [INFO] "output": 332,
[2026-01-26T19:22:49.656Z] [INFO] "reasoning": 1,
[2026-01-26T19:22:49.656Z] [INFO] "cache": {
[2026-01-26T19:22:49.657Z] [INFO] "read": 80847,
[2026-01-26T19:22:49.657Z] [INFO] "write": 0
[2026-01-26T19:22:49.657Z] [INFO] }
[2026-01-26T19:22:49.657Z] [INFO] }
[2026-01-26T19:22:49.657Z] [INFO] }
[2026-01-26T19:22:49.658Z] [INFO] }
[2026-01-26T19:23:03.478Z] [INFO] {
[2026-01-26T19:23:03.478Z] [INFO] "type": "step_start",
[2026-01-26T19:23:03.479Z] [INFO] "timestamp": 1769455383477,
[2026-01-26T19:23:03.479Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:23:03.479Z] [INFO] "part": {
[2026-01-26T19:23:03.479Z] [INFO] "id": "prt_bfbc273b4001BGWT4ySjQthf8U",
[2026-01-26T19:23:03.479Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:23:03.479Z] [INFO] "messageID": "msg_bfbc23dcc001PeybmyiXn9sfcG",
[2026-01-26T19:23:03.480Z] [INFO] "type": "step-start",
[2026-01-26T19:23:03.480Z] [INFO] "snapshot": "ac47e08cafd4e5154a15de09f6680801a4fc5e8a"
[2026-01-26T19:23:03.480Z] [INFO] }
[2026-01-26T19:23:03.480Z] [INFO] }
[2026-01-26T19:23:23.873Z] [INFO] {
[2026-01-26T19:23:23.873Z] [INFO] "type": "tool_use",
[2026-01-26T19:23:23.873Z] [INFO] "timestamp": 1769455403872,
[2026-01-26T19:23:23.874Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:23:23.874Z] [INFO] "part": {
[2026-01-26T19:23:23.874Z] [INFO] "id": "prt_bfbc2c35d001BC1eCt5vRUwOQI",
[2026-01-26T19:23:23.874Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:23:23.874Z] [INFO] "messageID": "msg_bfbc23dcc001PeybmyiXn9sfcG",
[2026-01-26T19:23:23.874Z] [INFO] "type": "tool",
[2026-01-26T19:23:23.875Z] [INFO] "callID": "call_d752d1de3d2949fd86647043",
[2026-01-26T19:23:23.875Z] [INFO] "tool": "write",
[2026-01-26T19:23:23.875Z] [INFO] "state": {
[2026-01-26T19:23:23.875Z] [INFO] "status": "completed",
[2026-01-26T19:23:23.875Z] [INFO] "input": {
[2026-01-26T19:23:23.875Z] [INFO] "content": "// Проверяем основные проблемы с ver8a\n\nconsole.log('=== VER8A DEBUG TEST ===');\n\n// 1. Проверяем наличие основных элементов\nfunction checkElements() {\n const elements = [\n 'rdf-input',\n 'input-format', \n 'output-format',\n 'layout-engine',\n 'visualization-mode',\n 'sparql-mode',\n 'visualize-btn',\n 'max-label-length',\n 'max-vad-row-length',\n 'smart-design-container',\n 'result-sparql-panel',\n 'sparql-panel'\n ];\n \n console.log('=== ELEMENT CHECK ===');\n elements.forEach(id => {\n const element = document.getElementById(id);\n console.log(`${id}: ${element ? 'EXISTS' : 'MISSING'}`);\n if (element) {\n console.log(` - display: ${window.getComputedStyle(element).display}`);\n console.log(` - visible: ${element.offsetWidth > 0 && element.offsetHeight > 0}`);\n }\n });\n}\n\n// 2. Проверяем наличие функций\nfunction checkFunctions() {\n const functions = [\n 'visualize',\n 'clearRdfInput',\n 'saveAsFile', \n 'testRdfValidation',\n 'loadExampleTrigVADv4',\n 'updateModeDescription',\n 'toggleSparqlPanel'\n ];\n \n console.log('\\n=== FUNCTION CHECK ===');\n functions.forEach(funcName => {\n const exists = typeof window[funcName] === 'function';\n console.log(`${funcName}: ${exists ? 'EXISTS' : 'MISSING'}`);\n });\n}\n\n// 3. Проверяем текущие значения\nfunction checkCurrentValues() {\n console.log('\\n=== CURRENT VALUES ===');\n \n const vizMode = document.getElementById('visualization-mode');\n const sparqlMode = document.getElementById('sparql-mode');\n \n if (vizMode) console.log(`visualization-mode: ${vizMode.value}`);\n if (sparqlMode) console.log(`sparql-mode: ${sparqlMode.value}`);\n \n // Проверяем видимость полей\n const maxVadGroup = document.getElementById('max-vad-row-length-group');\n if (maxVadGroup) {\n console.log(`max-vad-row-length-group display: ${maxVadGroup.style.display}`);\n }\n \n const smartDesignContainer = document.getElementById('smart-design-container');\n if (smartDesignContainer) {\n console.log(`smart-design-container classes: ${smartDesignContainer.className}`);\n }\n}\n\n// Запускаем проверки\ndocument.addEventListener('DOMContentLoaded', function() {\n setTimeout(() => {\n checkElements();\n checkFunctions();\n checkCurrentValues();\n \n // Пробуем вызвать updateModeDescription\n if (typeof window.updateModeDescription === 'function') {\n console.log('\\n=== CALLING updateModeDescription ===');\n window.updateModeDescription();\n \n // Проверяем результат\n setTimeout(() => {\n checkCurrentValues();\n }, 100);\n }\n }, 1000);\n});",
[2026-01-26T19:23:23.875Z] [INFO] "filePath": "/tmp/gh-issue-solver-1769454766276/experiments/debug_ver8a.js"
[2026-01-26T19:23:23.875Z] [INFO] },
[2026-01-26T19:23:23.875Z] [INFO] "output": "",
[2026-01-26T19:23:23.876Z] [INFO] "title": "experiments/debug_ver8a.js",
[2026-01-26T19:23:23.876Z] [INFO] "metadata": {
[2026-01-26T19:23:23.877Z] [INFO] "diagnostics": {},
[2026-01-26T19:23:23.877Z] [INFO] "filepath": "/tmp/gh-issue-solver-1769454766276/experiments/debug_ver8a.js",
[2026-01-26T19:23:23.877Z] [INFO] "exists": false
[2026-01-26T19:23:23.877Z] [INFO] },
[2026-01-26T19:23:23.877Z] [INFO] "time": {
[2026-01-26T19:23:23.877Z] [INFO] "start": 1769455403871,
[2026-01-26T19:23:23.877Z] [INFO] "end": 1769455403872
[2026-01-26T19:23:23.877Z] [INFO] }
[2026-01-26T19:23:23.878Z] [INFO] }
[2026-01-26T19:23:23.878Z] [INFO] }
[2026-01-26T19:23:23.878Z] [INFO] }
[2026-01-26T19:23:23.920Z] [INFO] {
[2026-01-26T19:23:23.921Z] [INFO] "type": "text",
[2026-01-26T19:23:23.921Z] [INFO] "timestamp": 1769455403919,
[2026-01-26T19:23:23.921Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:23:23.921Z] [INFO] "part": {
[2026-01-26T19:23:23.922Z] [INFO] "id": "prt_bfbc2743b001ENg75MWMHN0vi0",
[2026-01-26T19:23:23.922Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:23:23.922Z] [INFO] "messageID": "msg_bfbc23dcc001PeybmyiXn9sfcG",
[2026-01-26T19:23:23.922Z] [INFO] "type": "text",
[2026-01-26T19:23:23.922Z] [INFO] "text": "\nОсновная проблема найдена! Анализ показывает:\n\n1. **Функции существуют** - все функции на месте\n2. **Проблема в логике отображения** - панели скрываются по умолчанию и показываются только при определенных условиях\n3. **Множественные обработчики DOMContentLoaded** могут вызывать конфликты\n\nТеперь создам исправления:",
[2026-01-26T19:23:23.923Z] [INFO] "time": {
[2026-01-26T19:23:23.923Z] [INFO] "start": 1769455403919,
[2026-01-26T19:23:23.923Z] [INFO] "end": 1769455403919
[2026-01-26T19:23:23.923Z] [INFO] }
[2026-01-26T19:23:23.923Z] [INFO] }
[2026-01-26T19:23:23.923Z] [INFO] }
[2026-01-26T19:23:23.932Z] [INFO] {
[2026-01-26T19:23:23.933Z] [INFO] "type": "step_finish",
[2026-01-26T19:23:23.933Z] [INFO] "timestamp": 1769455403932,
[2026-01-26T19:23:23.933Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:23:23.934Z] [INFO] "part": {
[2026-01-26T19:23:23.934Z] [INFO] "id": "prt_bfbc2c390001Vc4KlK11k0rQHN",
[2026-01-26T19:23:23.934Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:23:23.934Z] [INFO] "messageID": "msg_bfbc23dcc001PeybmyiXn9sfcG",
[2026-01-26T19:23:23.934Z] [INFO] "type": "step-finish",
[2026-01-26T19:23:23.934Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:23:23.935Z] [INFO] "snapshot": "458610d2480eadd4cd2aea22bc32638080aefcb3",
[2026-01-26T19:23:23.935Z] [INFO] "cost": 0,
[2026-01-26T19:23:23.935Z] [INFO] "tokens": {
[2026-01-26T19:23:23.936Z] [INFO] "input": 69619,
[2026-01-26T19:23:23.936Z] [INFO] "output": 759,
[2026-01-26T19:23:23.936Z] [INFO] "reasoning": 1,
[2026-01-26T19:23:23.936Z] [INFO] "cache": {
[2026-01-26T19:23:23.936Z] [INFO] "read": 12079,
[2026-01-26T19:23:23.936Z] [INFO] "write": 0
[2026-01-26T19:23:23.936Z] [INFO] }
[2026-01-26T19:23:23.937Z] [INFO] }
[2026-01-26T19:23:23.937Z] [INFO] }
[2026-01-26T19:23:23.937Z] [INFO] }
[2026-01-26T19:23:35.298Z] [INFO] {
[2026-01-26T19:23:35.299Z] [INFO] "type": "step_start",
[2026-01-26T19:23:35.299Z] [INFO] "timestamp": 1769455415298,
[2026-01-26T19:23:35.299Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:23:35.299Z] [INFO] "part": {
[2026-01-26T19:23:35.299Z] [INFO] "id": "prt_bfbc2f000001DtNQeq6zD3qR4j",
[2026-01-26T19:23:35.299Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:23:35.300Z] [INFO] "messageID": "msg_bfbc2c3b9001w8mC3AgyyoTwaf",
[2026-01-26T19:23:35.300Z] [INFO] "type": "step-start",
[2026-01-26T19:23:35.300Z] [INFO] "snapshot": "458610d2480eadd4cd2aea22bc32638080aefcb3"
[2026-01-26T19:23:35.300Z] [INFO] }
[2026-01-26T19:23:35.300Z] [INFO] }
[2026-01-26T19:23:43.551Z] [INFO] {
[2026-01-26T19:23:43.551Z] [INFO] "type": "tool_use",
[2026-01-26T19:23:43.551Z] [INFO] "timestamp": 1769455423550,
[2026-01-26T19:23:43.552Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:23:43.552Z] [INFO] "part": {
[2026-01-26T19:23:43.552Z] [INFO] "id": "prt_bfbc3103b001E6PNXLq1qFy2n1",
[2026-01-26T19:23:43.552Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:23:43.552Z] [INFO] "messageID": "msg_bfbc2c3b9001w8mC3AgyyoTwaf",
[2026-01-26T19:23:43.553Z] [INFO] "type": "tool",
[2026-01-26T19:23:43.553Z] [INFO] "callID": "call_88f64849edcb4ab0b51f44f2",
[2026-01-26T19:23:43.553Z] [INFO] "tool": "read",
[2026-01-26T19:23:43.554Z] [INFO] "state": {
[2026-01-26T19:23:43.554Z] [INFO] "status": "completed",
[2026-01-26T19:23:43.554Z] [INFO] "input": {
[2026-01-26T19:23:43.554Z] [INFO] "filePath": "/tmp/gh-issue-solver-1769454766276/ver8a/styles.css",
[2026-01-26T19:23:43.554Z] [INFO] "limit": 20,
[2026-01-26T19:23:43.555Z] [INFO] "offset": 1089
[2026-01-26T19:23:43.555Z] [INFO] },
[2026-01-26T19:23:43.555Z] [INFO] "output": "<file>\n01090| margin-top: 20px;\n01091| display: none;\n01092| }\n01093| \n01094| .smart-design-container.visible {\n01095| display: block;\n01096| }\n01097| \n01098| .smart-design-panel {\n01099| padding: 15px;\n01100| background-color: #e3f2fd;\n01101| border: 1px solid #2196F3;\n01102| border-radius: 8px;\n01103| margin-bottom: 15px;\n01104| }\n01105| \n01106| .smart-design-panel h3 {\n01107| margin: 0 0 15px 0;\n01108| color: #1565C0;\n01109| font-size: 14px;\n\n(File has more lines. Use 'offset' parameter to read beyond line 1109)\n</file>",
[2026-01-26T19:23:43.556Z] [INFO] "title": "ver8a/styles.css",
[2026-01-26T19:23:43.557Z] [INFO] "metadata": {
[2026-01-26T19:23:43.557Z] [INFO] "preview": " margin-top: 20px;\n display: none;\n }\n\n .smart-design-container.visible {\n display: block;\n }\n\n .smart-design-panel {\n padding: 15px;\n background-color: #e3f2fd;\n border: 1px solid #2196F3;\n border-radius: 8px;\n margin-bottom: 15px;\n }\n\n .smart-design-panel h3 {\n margin: 0 0 15px 0;\n color: #1565C0;\n font-size: 14px;"
[2026-01-26T19:23:43.557Z] [INFO] },
[2026-01-26T19:23:43.557Z] [INFO] "time": {
[2026-01-26T19:23:43.558Z] [INFO] "start": 1769455423549,
[2026-01-26T19:23:43.558Z] [INFO] "end": 1769455423550
[2026-01-26T19:23:43.558Z] [INFO] }
[2026-01-26T19:23:43.558Z] [INFO] }
[2026-01-26T19:23:43.559Z] [INFO] }
[2026-01-26T19:23:43.559Z] [INFO] }
[2026-01-26T19:23:43.655Z] [INFO] {
[2026-01-26T19:23:43.656Z] [INFO] "type": "text",
[2026-01-26T19:23:43.656Z] [INFO] "timestamp": 1769455423654,
[2026-01-26T19:23:43.656Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:23:43.656Z] [INFO] "part": {
[2026-01-26T19:23:43.656Z] [INFO] "id": "prt_bfbc304710014q34AunRlg7Ca1",
[2026-01-26T19:23:43.657Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:23:43.657Z] [INFO] "messageID": "msg_bfbc2c3b9001w8mC3AgyyoTwaf",
[2026-01-26T19:23:43.657Z] [INFO] "type": "text",
[2026-01-26T19:23:43.657Z] [INFO] "text": "\nТеперь проанализирую основную проблему и создам исправления. Судя по коду, основная проблема в том, что:\n\n1. `updateModeDescription()` вызывается только при изменении select, но возможно не при начальной загрузке\n2. Некоторые панели имеют CSS классы для отображения/скрытия\n\nПроверим CSS для smart-design-container:",
[2026-01-26T19:23:43.657Z] [INFO] "time": {
[2026-01-26T19:23:43.657Z] [INFO] "start": 1769455423653,
[2026-01-26T19:23:43.658Z] [INFO] "end": 1769455423653
[2026-01-26T19:23:43.658Z] [INFO] }
[2026-01-26T19:23:43.658Z] [INFO] }
[2026-01-26T19:23:43.658Z] [INFO] }
[2026-01-26T19:23:43.662Z] [INFO] {
[2026-01-26T19:23:43.663Z] [INFO] "type": "step_finish",
[2026-01-26T19:23:43.663Z] [INFO] "timestamp": 1769455423662,
[2026-01-26T19:23:43.663Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:23:43.663Z] [INFO] "part": {
[2026-01-26T19:23:43.663Z] [INFO] "id": "prt_bfbc310a7001smb0J8GkmfE1fH",
[2026-01-26T19:23:43.664Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:23:43.664Z] [INFO] "messageID": "msg_bfbc2c3b9001w8mC3AgyyoTwaf",
[2026-01-26T19:23:43.664Z] [INFO] "type": "step-finish",
[2026-01-26T19:23:43.664Z] [INFO] "reason": "tool-calls",
[2026-01-26T19:23:43.664Z] [INFO] "snapshot": "458610d2480eadd4cd2aea22bc32638080aefcb3",
[2026-01-26T19:23:43.664Z] [INFO] "cost": 0,
[2026-01-26T19:23:43.664Z] [INFO] "tokens": {
[2026-01-26T19:23:43.664Z] [INFO] "input": 82401,
[2026-01-26T19:23:43.665Z] [INFO] "output": 132,
[2026-01-26T19:23:43.665Z] [INFO] "reasoning": 1,
[2026-01-26T19:23:43.666Z] [INFO] "cache": {
[2026-01-26T19:23:43.666Z] [INFO] "read": 67,
[2026-01-26T19:23:43.666Z] [INFO] "write": 0
[2026-01-26T19:23:43.667Z] [INFO] }
[2026-01-26T19:23:43.668Z] [INFO] }
[2026-01-26T19:23:43.669Z] [INFO] }
[2026-01-26T19:23:43.669Z] [INFO] }
[2026-01-26T19:23:45.920Z] [INFO] {
[2026-01-26T19:23:45.920Z] [INFO] "type": "step_start",
[2026-01-26T19:23:45.920Z] [INFO] "timestamp": 1769455425919,
[2026-01-26T19:23:45.921Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:23:45.921Z] [INFO] "part": {
[2026-01-26T19:23:45.921Z] [INFO] "id": "prt_bfbc3197f001V1uZweueSNkc1z",
[2026-01-26T19:23:45.921Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:23:45.921Z] [INFO] "messageID": "msg_bfbc310cf001DalgcNX6W4uUh6",
[2026-01-26T19:23:45.921Z] [INFO] "type": "step-start",
[2026-01-26T19:23:45.921Z] [INFO] "snapshot": "458610d2480eadd4cd2aea22bc32638080aefcb3"
[2026-01-26T19:23:45.922Z] [INFO] }
[2026-01-26T19:23:45.922Z] [INFO] }
[2026-01-26T19:23:56.088Z] [INFO] {
[2026-01-26T19:23:56.093Z] [INFO] "type": "tool_use",
[2026-01-26T19:23:56.094Z] [INFO] "timestamp": 1769455436081,
[2026-01-26T19:23:56.094Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:23:56.094Z] [INFO] "part": {
[2026-01-26T19:23:56.094Z] [INFO] "id": "prt_bfbc3410b001M6oalKaeIznYuD",
[2026-01-26T19:23:56.094Z] [INFO] "sessionID": "ses_40446a307ffe4Lfb1CAPmh9yyZ",
[2026-01-26T19:23:56.094Z] [INFO] "messageID": "msg_bfbc310cf001DalgcNX6W4uUh6",
[2026-01-26T19:23:56.094Z] [INFO] "type": "tool",
[2026-01-26T19:23:56.094Z] [INFO] "callID": "call_0063b4b131b14eaabdd65ae0",
[2026-01-26T19:23:56.095Z] [INFO] "tool": "edit",
[2026-01-26T19:23:56.095Z] [INFO] "state": {
[2026-01-26T19:23:56.095Z] [INFO] "status": "completed",
[2026-01-26T19:23:56.095Z] [INFO] "input": {
[2026-01-26T19:23:56.095Z] [INFO] "filePath": "/tmp/gh-issue-solver-1769454766276/ver8a/index.html",
[2026-01-26T19:23:56.095Z] [INFO] "oldString": " document.addEventListener('DOMContentLoaded', function() {\n updateModeDescription();\n\n // Парсим URL параметры",
[2026-01-26T19:23:56.096Z] [INFO] "newString": " document.addEventListener('DOMContentLoaded', function() {\n // Инициализация при загрузке страницы\n console.log('DOMContentLoaded: Initializing ver8a...');\n \n // Убеждаемся что режим визуализации установлен правильно\n const vizMode = document.getElementById('visualization-mode');\n if (vizMode && vizMode.value === 'vad-trig') {\n console.log('Setting VAD TriG mode as default');\n }\n \n // Вызываем updateModeDescription для правильной инициализации отображения\n updateModeDescription();\n \n console.log('DOMContentLoaded: Initialization complete');\n\n // Парсим URL параметры"
[2026-01-26T19:23:56.097Z] [INFO] },
[2026-01-26T19:23:56.097Z] [INFO] "output": "",
[2026-01-26T19:23:56.098Z] [INFO] "title": "ver8a/index.html",
[2026-01-26T19:23:56.098Z] [INFO] "metadata": {
[2026-01-26T19:23:56.098Z] [INFO] "diagnostics": {},
[2026-01-26T19:23:56.099Z] [INFO] "diff": "Index: /tmp/gh-issue-solver-1769454766276/ver8a/index.html\n===================================================================\n--- /tmp/gh-issue-solver-1769454766276/ver8a/index.html\n+++ /tmp/gh-issue-solver-1769454766276/ver8a/index.html\n@@ -7945,9 +7945,21 @@\n }\n \n // Инициализация при загрузке страницы\n document.addEventListener('DOMContentLoaded', function() {\n+ // Инициализация при загрузке страницы\n+ console.log('DOMContentLoaded: Initializing ver8a...');\n+ \n+ // Убеждаемся что режим визуализации установлен правильно\n+ const vizMode = document.getElementById('visualization-mode');\n+ if (vizMode && vizMode.value === 'vad-trig') {\n+ console.log('Setting VAD TriG mode as default');\n+ }\n+ \n+ // Вызываем updateModeDescription для правильной инициализации отображения\n updateModeDescription();\n+ \n+ console.log('DOMContentLoaded: Initialization complete');\n \n // Парсим URL параметры\n const params = parseUrlParams();\n \n",
[2026-01-26T19:23:56.099Z] [INFO] "filediff": {
[2026-01-26T19:23:56.099Z] [INFO] "file": "/tmp/gh-issue-solver-1769454766276/ver8a/index.html",
[2026-01-26T19:23:56.100Z] [INFO] "before": "<!DOCTYPE html>\n<html lang=\"ru\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>RDF Grapher ver8 - Визуализация RDF данных с поддержкой TriG + SPARQL Smart Design + Ontology </title>\n\n <!--\n RDF Grapher ver8a - Сервис для парсинга RDF данных и их визуализации в виде графа\n Новые возможности ver8: SPARQL Smart Design + Ontology\n Этот сервис является продолжением https://www.ldf.fi/service/rdf-grapher в части BPM\n Использует клиентские JavaScript библиотеки:\n - N3.js для парсинга RDF (замена Redland Raptor)\n - Viz.js для рендеринга графов (замена Graphviz)\n\n Сервис работает полностью на стороне клиента и может быть размещен на GitHub Pages\n -->\n\n <!-- Стили для интерфейса -->\n <link rel=\"stylesheet\" href=\"styles.css\">\n</head>\n<body>\n <!-- Контейнер для панелей свойств узлов -->\n <div id=\"properties-panels-container\"></div>\n\n <div class=\"description\">\n <p>\n <strong>Загрузить пример RDF данных:</strong>\n <span class=\"example-link\" onclick=\"loadExampleTrigVADv4()\">Trig VADv4</span>\n </p>\n <div id=\"example-status\" class=\"example-status\" style=\"display: none;\"></div>\n </div>\n\n <div class=\"container\">\n <div class=\"rdf-input-header\">\n <label for=\"rdf-input\">RDF данные или URI:</label>\n <button class=\"show-in-window-btn\" onclick=\"showRdfInSeparateWindow()\">Показать в окне</button>\n <button class=\"clear-btn\" onclick=\"clearRdfInput()\">Очистить</button>\n <button class=\"save-as-btn\" onclick=\"saveAsFile()\">Сохранить как</button>\n <button class=\"load-btn\" onclick=\"document.getElementById('file-input').click()\">Загрузить</button>\n <button class=\"test-btn\" onclick=\"testRdfValidation()\">Тест</button>\n <button class=\"virtual-rdf-btn\" onclick=\"showVirtualRDFData()\">virtualRDFdata</button>\n <input type=\"file\" id=\"file-input\" style=\"display: none;\" accept=\".ttl,.rdf,.nt,.nq,.trig\" onchange=\"loadFile(event)\">\n </div>\n <textarea id=\"rdf-input\" placeholder=\"Введите RDF данные в формате TriG...\"></textarea>\n\n <div class=\"format-selectors\">\n <div class=\"form-group\">\n <label for=\"input-format\">Входной формат:</label>\n <select id=\"input-format\">\n <option value=\"trig\" selected>TriG</option>\n </select>\n </div>\n\n <div class=\"form-group\">\n <label for=\"output-format\">Выходной формат:</label>\n <select id=\"output-format\">\n <option value=\"svg\" selected>SVG</option>\n <option value=\"png\">PNG</option>\n </select>\n </div>\n\n <div class=\"form-group\">\n <label for=\"layout-engine\">Движок компоновки:</label>\n <select id=\"layout-engine\">\n <option value=\"dot\" selected>dot (иерархическая)</option>\n <option value=\"neato\">neato (spring model)</option>\n <option value=\"fdp\">fdp (force-directed)</option>\n <option value=\"circo\">circo (круговая)</option>\n <option value=\"twopi\">twopi (радиальная)</option>\n </select>\n </div>\n\n <div class=\"form-group\">\n <label for=\"visualization-mode\">Режим визуализации:</label>\n <select id=\"visualization-mode\" onchange=\"updateModeDescription()\">\n <option value=\"vad-trig\" selected>Режим VAD TriG</option>\n </select>\n <div class=\"mode-description\" id=\"mode-description\">\n VAD диаграмма с поддержкой иерархии TriG графов\n </div>\n </div>\n\n <div class=\"form-group\">\n <label for=\"sparql-mode\">Режим SPARQL:</label>\n <select id=\"sparql-mode\" onchange=\"toggleSparqlPanel()\">\n <option value=\"smart-design\" selected>SPARQL Smart Design</option>\n </select>\n <div class=\"mode-description\" id=\"sparql-mode-description\">\n Smart Design для создания SPARQL запросов\n </div>\n </div>\n\n <div class=\"form-group\" id=\"max-label-length-group\">\n <label for=\"max-label-length\">Макс. длина имени:</label>\n <input type=\"number\" id=\"max-label-length\" value=\"25\" min=\"5\" max=\"200\" style=\"width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;\">\n <div class=\"mode-description\" id=\"max-label-length-description\">\n Перенос по словам при превышении\n </div>\n </div>\n\n <div class=\"form-group\" id=\"max-vad-row-length-group\" style=\"display: none;\">\n <label for=\"max-vad-row-length\">Макс. длина VAD:</label>\n <input type=\"number\" id=\"max-vad-row-length\" value=\"8\" min=\"2\" max=\"20\" style=\"width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;\">\n <div class=\"mode-description\" id=\"max-vad-row-length-description\">\n Количество VAD-элементов в строке до переноса\n </div>\n </div>\n </div>\n\n <button id=\"visualize-btn\" onclick=\"visualize()\">Показать</button>\n </div>\n\n <!-- Smart Design Container - отображается только при выборе \"SPARQL Smart Design\" в режиме VAD TriG -->\n <div class=\"smart-design-container\" id=\"smart-design-container\">\n <!-- Панель Smart Design -->\n <div class=\"smart-design-panel\">\n <h3>Smart Design</h3>\n <!-- Верхний ряд: TriG -->\n <div class=\"smart-design-trig-row\">\n <div class=\"smart-design-field smart-design-field-trig\">\n <label for=\"smart-design-trig\">TriG:</label>\n <select id=\"smart-design-trig\">\n <option value=\"\">-- Выберите TriG --</option>\n </select>\n </div>\n </div>\n <!-- Нижний ряд: Subject Type, Subject, Predicate, Object -->\n <div class=\"smart-design-fields\">\n <div class=\"smart-design-field\">\n <label for=\"smart-design-subject-type\">Subject Type:</label>\n <div class=\"smart-design-field-wrapper\">\n <select id=\"smart-design-subject-type\">\n <option value=\"\">-- Выберите тип Subject --</option>\n </select>\n <button class=\"copy-to-clipboard-btn\" onclick=\"copyFieldValue('smart-design-subject-type')\" title=\"Копировать значение\">📋</button>\n </div>\n </div>\n <div class=\"smart-design-field\">\n <label for=\"smart-design-subject\">Subject:</label>\n <div class=\"smart-design-field-wrapper\">\n <select id=\"smart-design-subject\">\n <option value=\"\">-- Выберите Subject --</option>\n </select>\n <button class=\"copy-to-clipboard-btn\" onclick=\"copyFieldValue('smart-design-subject')\" title=\"Копировать значение\">📋</button>\n </div>\n </div>\n <div class=\"smart-design-field\">\n <label for=\"smart-design-predicate\">Predicate:</label>\n <div class=\"smart-design-field-wrapper\">\n <select id=\"smart-design-predicate\">\n <option value=\"\">-- Выберите Predicate --</option>\n </select>\n <button class=\"copy-to-clipboard-btn\" onclick=\"copyFieldValue('smart-design-predicate')\" title=\"Копировать значение\">📋</button>\n </div>\n </div>\n <div class=\"smart-design-field\">\n <label for=\"smart-design-object\">Object:</label>\n <div class=\"smart-design-field-wrapper\">\n <select id=\"smart-design-object\">\n <option value=\"\">-- Выберите Object --</option>\n </select>\n <button class=\"copy-to-clipboard-btn\" onclick=\"copyFieldValue('smart-design-object')\" title=\"Копировать значение\">📋</button>\n </div>\n </div>\n </div>\n <div class=\"smart-design-buttons\">\n <button class=\"new-trig-btn\" onclick=\"openNewTrigModal()\">New TriG (VADProcessDia)</button>\n <button class=\"del-trig-btn\" onclick=\"openDelTrigModal()\">Del TriG</button>\n <button onclick=\"smartDesignCreate()\">Создать SPARQL</button>\n <button onclick=\"smartDesignCreateWithPrefix()\" style=\"background-color: #1565C0;\">Создать SPARQL (prefix)</button>\n <button class=\"delete-btn\" onclick=\"smartDesignDelete()\">Удалить триплет</button>\n <button class=\"clear-btn\" onclick=\"smartDesignClear()\">Очистить</button>\n </div>\n <div id=\"smart-design-message\" class=\"smart-design-message\" style=\"display: none;\"></div>\n </div>\n <!-- Панель Result in SPARQL -->\n <div class=\"result-sparql-panel\">\n <h3>Result in SPARQL</h3>\n <textarea class=\"result-sparql-textarea\" id=\"result-sparql-query\" placeholder=\"Здесь будет сгенерированный SPARQL запрос...\"></textarea>\n <div class=\"smart-design-buttons\" style=\"margin-top: 10px;\">\n <button class=\"apply-btn\" onclick=\"smartDesignApply()\">Применить как Simple Triple</button>\n <button class=\"apply-btn\" onclick=\"smartDesignApplyShorthand()\" style=\"background-color: #8E24AA;\">Применить как Shorthand Triple</button>\n </div>\n <div id=\"result-sparql-message\" class=\"smart-design-message\" style=\"display: none;\"></div>\n </div>\n </div>\n\n <!-- Модальное окно для создания нового TriG -->\n <div id=\"new-trig-modal\" class=\"new-trig-modal\">\n <div class=\"new-trig-modal-content\">\n <div class=\"new-trig-modal-header\">\n <h3>Создание нового TriG контейнера (VADProcessDia)</h3>\n <button class=\"new-trig-modal-close\" onclick=\"closeNewTrigModal()\">&times;</button>\n </div>\n <div class=\"new-trig-modal-body\">\n <div class=\"new-trig-field\">\n <label for=\"new-trig-process-concept\">Концепт процесса:</label>\n <select id=\"new-trig-process-concept\" onchange=\"updateNewTrigFields()\">\n <option value=\"\">-- Выберите концепт процесса --</option>\n </select>\n <small class=\"field-hint\">ID и label TriG формируются автоматически на основе выбранного концепта</small>\n </div>\n <div class=\"new-trig-field\">\n <label for=\"new-trig-id\">ID нового TriG (автоматически):</label>\n <input type=\"text\" id=\"new-trig-id\" placeholder=\"t_{processId}\" readonly style=\"background-color: #f5f5f5;\">\n </div>\n <div class=\"new-trig-field\">\n <label for=\"new-trig-label\">rdfs:label (автоматически):</label>\n <input type=\"text\" id=\"new-trig-label\" placeholder=\"Схема процесса {processLabel}\" readonly style=\"background-color: #f5f5f5;\">\n </div>\n <div class=\"new-trig-field\">\n <label for=\"new-trig-parent\">vad:hasParentTrig (родитель):</label>\n <select id=\"new-trig-parent\">\n <option value=\"\">-- Выберите родителя --</option>\n </select>\n </div>\n </div>\n <div class=\"new-trig-modal-buttons\">\n <button class=\"new-trig-cancel-btn\" onclick=\"closeNewTrigModal()\">Отмена</button>\n <button class=\"new-trig-create-btn\" onclick=\"createNewTrig()\">Создать запрос New TriG</button>\n </div>\n </div>\n </div>\n\n <!-- Модальное окно для удаления TriG -->\n <div id=\"del-trig-modal\" class=\"new-trig-modal\">\n <div class=\"new-trig-modal-content\">\n <div class=\"new-trig-modal-header\" style=\"background-color: #f44336;\">\n <h3>Удаление TriG контейнера</h3>\n <button class=\"new-trig-modal-close\" onclick=\"closeDelTrigModal()\">&times;</button>\n </div>\n <div class=\"new-trig-modal-body\">\n <div class=\"new-trig-field\">\n <label for=\"del-trig-select\">Выберите TriG для удаления:</label>\n <select id=\"del-trig-select\" onchange=\"updateDelTrigInfo()\">\n <option value=\"\">-- Выберите TriG --</option>\n </select>\n </div>\n <div class=\"new-trig-field\">\n <label>Информация о TriG:</label>\n <div id=\"del-trig-info\" style=\"padding: 10px; background-color: #f5f5f5; border-radius: 4px; font-size: 13px; min-height: 60px;\">\n Выберите TriG для просмотра информации\n </div>\n </div>\n <div id=\"del-trig-warning\" style=\"display: none; padding: 10px; background-color: #ffebee; border: 1px solid #f44336; border-radius: 4px; margin-top: 10px; color: #c62828;\">\n </div>\n </div>\n <div class=\"new-trig-modal-buttons\">\n <button class=\"new-trig-cancel-btn\" onclick=\"closeDelTrigModal()\">Отмена</button>\n <button class=\"new-trig-create-btn\" id=\"del-trig-confirm-btn\" onclick=\"deleteTriG()\" style=\"background-color: #f44336;\" disabled>Создать запрос Del TriG</button>\n </div>\n </div>\n </div>\n\n <div class=\"result-container\" id=\"result-container\">\n <h2>Результат:</h2>\n\n <!-- VAD TriG режим: контейнер с диаграммой слева и панелями справа -->\n <div class=\"vad-trig-container\" id=\"vad-trig-container\" style=\"display: none;\">\n <!-- Обертка для диаграммы -->\n <div class=\"vad-trig-diagram-wrapper\">\n <div class=\"diagram-panel\">\n <div class=\"diagram-panel-header\">Диаграмма</div>\n <div class=\"diagram-panel-content\">\n <div class=\"zoom-container vad-trig-zoom-container\" id=\"vad-trig-zoom-container\">\n <div class=\"zoom-content\" id=\"vad-trig-zoom-content\">\n <div id=\"vad-trig-output\"></div>\n </div>\n </div>\n </div>\n <div class=\"zoom-controls vad-trig-zoom-controls\" id=\"vad-trig-zoom-controls\" style=\"display: none;\">\n <button onclick=\"zoomOut()\">-</button>\n <span id=\"vad-trig-zoom-level\">100%</span>\n <button onclick=\"zoomIn()\">+</button>\n <button onclick=\"zoomReset()\">Сброс</button>\n <button onclick=\"zoomFit()\">Вписать</button>\n </div>\n </div>\n </div>\n\n <!-- Панели слева -->\n <div class=\"vad-trig-left-panels\">\n <!-- Панель дерева TriG -->\n <div class=\"trig-tree-panel\">\n <div class=\"trig-tree-header\">Дерево TriG</div>\n <div class=\"trig-tree-content\" id=\"trig-tree-content\">\n <div class=\"trig-properties-empty\">Выберите данные TriG</div>\n </div>\n </div>\n\n <!-- Панель свойств объекта -->\n <div class=\"trig-properties-panel\">\n <div class=\"trig-properties-header\">Окно свойств объекта</div>\n <div class=\"trig-properties-content\" id=\"trig-properties-content\">\n <div class=\"trig-properties-empty\">Выберите TriG в дереве</div>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"zoom-controls\" id=\"zoom-controls\" style=\"display: none;\">\n <button onclick=\"zoomOut()\">-</button>\n <span id=\"zoom-level\">100%</span>\n <button onclick=\"zoomIn()\">+</button>\n <button onclick=\"zoomReset()\">Сброс</button>\n <button onclick=\"zoomFit()\">Вписать</button>\n </div>\n <div class=\"zoom-container\" id=\"zoom-container\">\n <div class=\"zoom-content\" id=\"zoom-content\">\n <div id=\"output\"></div>\n </div>\n </div>\n <div class=\"export-buttons\" id=\"export-buttons\" style=\"display: none;\">\n <button onclick=\"downloadSVG()\">Скачать SVG</button>\n <button onclick=\"downloadPNG()\">Скачать PNG</button>\n <button onclick=\"openInNewWindowGitHub()\">Показать в окне github</button>\n <button onclick=\"openInNewWindowLdfFi()\">Показать в окне ldf.fi</button>\n <button onclick=\"openInNewWindowGraphvizOnline()\">Показать в окне GraphvizOnline</button>\n </div>\n <div class=\"legend-panel\" id=\"legend-panel\" style=\"display: none;\">\n <h3>Легенда стилей:</h3>\n <div class=\"legend-content\" id=\"legend-content\"></div>\n </div>\n <div class=\"prefixes-panel\" id=\"prefixes-panel\" style=\"display: none;\">\n <h3>Prefixes:</h3>\n <div class=\"prefixes-content\" id=\"prefixes-content\"></div>\n </div>\n <div class=\"sparql-panel\" id=\"sparql-panel\">\n <h3>SPARQL запрос:</h3>\n <textarea class=\"sparql-query-textarea\" id=\"sparql-query\" placeholder=\"Введите SPARQL запрос...\">SELECT ?s ?p ?o\nWHERE {\n ?s ?p ?o .\n}</textarea>\n <div class=\"sparql-buttons\">\n <button onclick=\"executeSparqlQuery()\">Выполнить запрос</button>\n <button onclick=\"resetSparqlQuery()\">Сбросить</button>\n </div>\n <div class=\"sparql-results\" id=\"sparql-results\" style=\"display: none;\">\n <h4>Результаты запроса:</h4>\n <div id=\"sparql-results-content\"></div>\n </div>\n </div>\n </div>\n\n <!-- Подключение библиотеки N3.js для парсинга RDF -->\n <script src=\"https://unpkg.com/[email protected]/browser/n3.min.js\"></script>\n\n <!-- Подключение библиотеки Viz.js для рендеринга графов -->\n <script src=\"https://unpkg.com/@viz-js/[email protected]/lib/viz-standalone.js\"></script>\n\n <!-- Подключение библиотеки Comunica для SPARQL запросов -->\n <script src=\"https://rdf.js.org/comunica-browser/versions/v4/engines/query-sparql-rdfjs/comunica-browser.js\"></script>\n\n <!-- Подключение SPARQL запросов для Smart Design (справочники) -->\n <script src=\"sparql-queries-choice.js\"></script>\n\n <!-- Подключение SPARQL запросов для логики кода -->\n <script src=\"sparql-queries-code.js\"></script>\n\n <!-- Подключение правил валидации VAD схемы -->\n <script src=\"vad-validation-rules.js\"></script>\n\n <script>\n /**\n * RDF Grapher ver4p - Основной JavaScript модуль\n */\n\n // ============================================================================\n // РЕЖИМ ВИЗУАЛИЗАЦИИ\n // ============================================================================\n\n const Mode = 'notation';\n\n // ============================================================================\n // EMBEDDED EXAMPLE DATA\n // ============================================================================\n\n const EXAMPLE_DATA = {\n // TriG VADv4 пример\n 'trig-vad-v4': `# Пример TriG VADv4 (Value Added Chain Diagram с иерархией TriG)\n# Демонстрация иерархии TriG графов через hasParentTrig\n# Исполнители определены в vad:rtree (Дерево Исполнителей)\n#\n# vad:processSubtype вычисляется автоматически в virtualRDFdata:\n# - vad:DetailedChild: процесс с vad:hasTrig, где дочерний TriG указывает на текущий\n# - vad:notDetailedChild: процесс с vad:hasParentProcess, совпадающим с родителем текущей схемы\n# - vad:NotDefinedType: процесс с vad:hasParentProcess = vad:NotDefined\n#\n# ВАЖНЫЕ ПРАВИЛА:\n# 1. vad:hasTrig и vad:hasParentProcess - взаимоисключающие предикаты\n# 2. vad:processSubtype вычисляется автоматически в virtualRDFdata\n# 3. vad:isSubprocessTrig указывается первым для явной связи индивида с TriG\n# 4. ID группы исполнителей формируется как: ExecutorGroup_ + ID процесса\n\n@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n@prefix dcterms: <http://purl.org/dc/terms/> .\n@prefix vad: <http://example.org/vad#> .\n\n# ============================================================================\n# Дерево Процессов (ptree) - общий граф с метаданными всех процессов\n# ============================================================================\n\nvad:ptree {\n vad:ptree rdf:type vad:ProcessTree ;\n rdfs:label \"Дерево Процессов (TriG)\" ;\n vad:hasParentTrig vad:root .\n\n vad:p1 rdf:type vad:TypeProcess ;\n rdfs:label \"p1 Процесс 1\" ;\n dcterms:description \"p1 Процесс 1\" ;\n vad:hasTrig vad:t_p1 .\n\n vad:p1.1 rdf:type vad:TypeProcess ;\n rdfs:label \"p1.1 Процесс 1.1\" ;\n dcterms:description \"Первый процесс в цепочке добавленной стоимости\" ;\n vad:hasTrig vad:t_p1.1 .\n\n vad:p1.2 rdf:type vad:TypeProcess ;\n rdfs:label \"Процесс 1.2\" ;\n dcterms:description \"Второй процесс, выполняется после Процесса 1\" ;\n vad:hasParentProcess vad:p1 .\n\n # p1.1.1\n vad:p1.1.1 rdf:type vad:TypeProcess ;\n rdfs:label \"p1.1.1 Процесс 1.1.1\" ;\n dcterms:description \"Первый процесс в цепочке добавленной стоимости\" ;\n vad:hasParentProcess vad:p1.1 .\n\n vad:p1.1.2 rdf:type vad:TypeProcess ;\n rdfs:label \"p1.1.2 Процесс 1.1.2\" ;\n dcterms:description \"Второй процесс в цепочке добавленной стоимости\" ;\n vad:hasParentProcess vad:p2 .\n\n vad:p2 rdf:type vad:TypeProcess ;\n rdfs:label \"p2 Процесс 2\" ;\n dcterms:description \"p2 Процесс 2\" ;\n vad:hasTrig vad:t_p2 .\n\n vad:p2.1 rdf:type vad:TypeProcess ;\n rdfs:label \"p2.1 Процесс 2.1\" ;\n dcterms:description \"Первый процесс в цепочке добавленной стоимости\" ;\n vad:hasParentProcess vad:p2 .\n\n vad:p2.2 rdf:type vad:TypeProcess ;\n rdfs:label \"p2.2 Процесс 2.2\" ;\n dcterms:description \"Второй процесс\" ;\n vad:hasParentProcess vad:NotDefined .\n}\n\n# ============================================================================\n# Дерево Исполнителей (rtree) - общий граф с метаданными всех исполнителей\n# Содержит rdf:type, rdfs:label для всех vad:Executor\n# ============================================================================\n\nvad:rtree {\n vad:rtree rdf:type vad:ExecutorTree ;\n rdfs:label \"Дерево Исполнителей (TriG)\" ;\n vad:hasParentTrig vad:root .\n\n # Исполнители\n vad:Executor1 rdf:type vad:TypeExecutor ;\n rdfs:label \"Исполнитель 1\" .\n\n vad:Executor2 rdf:type vad:TypeExecutor ;\n rdfs:label \"Исполнитель 2\" .\n\n vad:Executor3 rdf:type vad:TypeExecutor ;\n rdfs:label \"Исполнитель 3\" .\n\n vad:Executor4 rdf:type vad:TypeExecutor ;\n rdfs:label \"Исполнитель 4\" .\n\n vad:Executor5 rdf:type vad:TypeExecutor ;\n rdfs:label \"Исполнитель 5\" .\n\n vad:Executor6 rdf:type vad:TypeExecutor ;\n rdfs:label \"Исполнитель 6\" .\n\n vad:Executor7 rdf:type vad:TypeExecutor ;\n rdfs:label \"Исполнитель 7\" .\n\n vad:Executor8 rdf:type vad:TypeExecutor ;\n rdfs:label \"Исполнитель 8\" .\n\n # Исполнители\n vad:Executor21 rdf:type vad:TypeExecutor ;\n rdfs:label \"Исполнитель 21\" .\n\n vad:Executor22 rdf:type vad:TypeExecutor ;\n rdfs:label \"Исполнитель 22\" .\n\n vad:Executor23 rdf:type vad:TypeExecutor ;\n rdfs:label \"Исполнитель 23\" .\n\n vad:Executor24 rdf:type vad:TypeExecutor ;\n rdfs:label \"Исполнитель 24\" .\n\n vad:Executor25 rdf:type vad:TypeExecutor ;\n rdfs:label \"Исполнитель 25\" .\n\n vad:Executor26 rdf:type vad:TypeExecutor ;\n rdfs:label \"Исполнитель 26\" .\n\n vad:Executor27 rdf:type vad:TypeExecutor ;\n rdfs:label \"Исполнитель 27\" .\n\n vad:Executor28 rdf:type vad:TypeExecutor ;\n rdfs:label \"Исполнитель 28\" .\n}\n\n# ============================================================================\n# Корневой TriG граф: t_p1 (hasParentTrig = root)\n# ============================================================================\n\nvad:t_p1 {\n vad:t_p1 rdf:type vad:VADProcessDia ;\n rdfs:label \"Схема t_p1 процесса p1\" ;\n vad:hasParentTrig vad:root ;\n vad:definesProcess vad:p1 .\n\n # Процесс p1.1 - DetailedChild (имеет дочернюю схему vad:t_p1.1 с hasParentTrig на текущую)\n # rdf:type находится в vad:ptree\n # isSubprocessTrig указывается первым для явной связи с TriG\n vad:p1.1 vad:isSubprocessTrig vad:t_p1 ;\n vad:hasExecutor vad:ExecutorGroup_p1.1 ;\n vad:hasNext vad:p1.2 .\n\n # Процесс p1.2 - notDetailedChild\n vad:p1.2 vad:isSubprocessTrig vad:t_p1 ;\n vad:hasExecutor vad:ExecutorGroup_p1.2 .\n\n # Группы исполнителей (ID формируется как ExecutorGroup_ + ID процесса)\n vad:ExecutorGroup_p1.1 rdf:type vad:ExecutorGroup ;\n rdfs:label \"Группа исполнителей процесса p1.1\" ;\n vad:includes vad:Executor1 .\n\n vad:ExecutorGroup_p1.2 rdf:type vad:ExecutorGroup ;\n rdfs:label \"Группа исполнителей процесса p1.2\" ;\n vad:includes vad:Executor1, vad:Executor2 .\n\n # Примечание: Исполнители (vad:Executor1..8) определены в vad:rtree\n}\n\n# ============================================================================\n# Дочерний TriG граф: t_p1.1 (hasParentTrig = t_p1)\n# Это детализация процесса p1.1 из родительского графа t_p1\n# ============================================================================\n\nvad:t_p1.1 {\n vad:t_p1.1 rdf:type vad:VADProcessDia ;\n rdfs:label \"Схема t_p1.1 процесса p1.1\" ;\n vad:hasParentTrig vad:t_p1 ;\n vad:definesProcess vad:p1.1 .\n\n # Процесс p1.1.1 - notDetailedChild\n # rdf:type находится в vad:ptree\n # isSubprocessTrig указывается первым для явной связи с TriG\n vad:p1.1.1 vad:isSubprocessTrig vad:t_p1.1 ;\n vad:hasExecutor vad:ExecutorGroup_p1.1.1 ;\n vad:hasNext vad:p1.1.2 .\n\n # Процесс p1.1.2 - notDetailedExternal (hasParentProcess = p2, не совпадает с p1.1)\n vad:p1.1.2 vad:isSubprocessTrig vad:t_p1.1 ;\n vad:hasExecutor vad:ExecutorGroup_p1.1.2 .\n\n # Группы исполнителей (ID формируется как ExecutorGroup_ + ID процесса)\n vad:ExecutorGroup_p1.1.1 rdf:type vad:ExecutorGroup ;\n rdfs:label \"Группа исполнителей процесса p1.1.1\" ;\n vad:includes vad:Executor21 .\n\n vad:ExecutorGroup_p1.1.2 rdf:type vad:ExecutorGroup ;\n rdfs:label \"Группа исполнителей процесса p1.1.2\" ;\n vad:includes vad:Executor21, vad:Executor22 .\n}\n\n# ============================================================================\n# Корневой TriG граф: t_p2 (hasParentTrig = root)\n# ============================================================================\n\nvad:t_p2 {\n vad:t_p2 rdf:type vad:VADProcessDia ;\n rdfs:label \"Схема t_p2 процесса p2\" ;\n vad:hasParentTrig vad:root ;\n vad:definesProcess vad:p2 .\n\n # Процесс p2.1 - notDetailedChild\n # rdf:type находится в vad:ptree\n # isSubprocessTrig указывается первым для явной связи с TriG\n vad:p2.1 vad:isSubprocessTrig vad:t_p2 ;\n vad:hasExecutor vad:ExecutorGroup_p2.1 ;\n vad:hasNext vad:p2.2 .\n\n # Процесс p2.2 - NotDefinedType (hasParentProcess = NotDefined)\n vad:p2.2 vad:isSubprocessTrig vad:t_p2 ;\n vad:hasExecutor vad:ExecutorGroup_p2.2 .\n\n # Группы исполнителей (ID формируется как ExecutorGroup_ + ID процесса)\n vad:ExecutorGroup_p2.1 rdf:type vad:ExecutorGroup ;\n rdfs:label \"Группа исполнителей процесса p2.1\" ;\n vad:includes vad:Executor1 .\n\n vad:ExecutorGroup_p2.2 rdf:type vad:ExecutorGroup ;\n rdfs:label \"Группа исполнителей процесса p2.2\" ;\n vad:includes vad:Executor1, vad:Executor2 .\n}\\`\n };\n\n // ============================================================================\n // КОНФИГУРАЦИЯ ФИЛЬТРОВ\n // ============================================================================\n\n const Filter = {\n hiddenPredicates: [\n 'rdf:type',\n 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',\n 'rdfs:subClassOf',\n 'http://www.w3.org/2000/01/rdf-schema#subClassOf'\n ]\n };\n\n const FilterBase = {\n hiddenPredicates: []\n };\n\n const FilterAggregation = {\n hiddenPredicates: [\n 'rdf:type',\n 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',\n 'rdfs:subClassOf',\n 'http://www.w3.org/2000/01/rdf-schema#subClassOf'\n ]\n };\n\n // Фильтры для режима VAD - скрываем hasParentTrig и rdf:type\n const FilterVAD = {\n hiddenPredicates: [\n 'rdf:type',\n 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',\n 'vad:hasParentTrig',\n 'http://example.org/vad#hasParentTrig'\n ]\n };\n\n function getFilterConfig(mode) {\n if (mode === 'base') {\n return FilterBase;\n } else if (mode === 'aggregation') {\n return FilterAggregation;\n } else if (mode === 'vad' || mode === 'vad-trig') {\n return FilterVAD;\n }\n return Filter;\n }\n\n // ============================================================================\n // КОНФИГУРАЦИЯ АГРЕГАЦИИ\n // ============================================================================\n\n const MaxAggregationParams = 5;\n const DEFAULT_MAX_LABEL_LENGTH = 25;\n let currentMaxLabelLength = DEFAULT_MAX_LABEL_LENGTH;\n\n // Максимальное количество VAD элементов (CDS) в одной горизонтальной строке\n const DEFAULT_MAX_VAD_ROW_LENGTH = 8;\n let currentMaxVadRowLength = DEFAULT_MAX_VAD_ROW_LENGTH;\n\n // ============================================================================\n // КОНФИГУРАЦИЯ VAD (Value Added Chain Diagram)\n // ============================================================================\n\n /**\n * VAD_ALLOWED_TYPES - Разрешенные типы объектов для режима VAD\n */\n const VAD_ALLOWED_TYPES = [\n 'vad:TypeProcess',\n 'http://example.org/vad#TypeProcess',\n 'vad:ExecutorGroup',\n 'http://example.org/vad#ExecutorGroup',\n 'vad:TypeExecutor',\n 'http://example.org/vad#TypeExecutor',\n 'vad:VADProcessDia',\n 'http://example.org/vad#VADProcessDia',\n 'vad:ProcessTree',\n 'http://example.org/vad#ProcessTree',\n 'vad:ExecutorTree',\n 'http://example.org/vad#ExecutorTree',\n // Process subtypes - parent classes\n 'vad:Detailed',\n 'http://example.org/vad#Detailed',\n 'vad:notDetailed',\n 'http://example.org/vad#notDetailed',\n // Detailed subtypes (process has vad:hasTrig)\n 'vad:DetailedChild',\n 'http://example.org/vad#DetailedChild',\n 'vad:DetailedExternal',\n 'http://example.org/vad#DetailedExternal',\n // notDetailed subtypes (process has vad:hasParentProcess)\n 'vad:notDetailedChild',\n 'http://example.org/vad#notDetailedChild',\n 'vad:notDetailedExternal',\n 'http://example.org/vad#notDetailedExternal',\n 'vad:NotDefinedType',\n 'http://example.org/vad#NotDefinedType',\n // Special values\n 'vad:NotDefined',\n 'http://example.org/vad#NotDefined'\n ];\n\n /**\n * VAD_ALLOWED_PREDICATES - Разрешенные предикаты для режима VAD\n */\n const VAD_ALLOWED_PREDICATES = [\n 'rdf:type',\n 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',\n 'rdfs:label',\n 'http://www.w3.org/2000/01/rdf-schema#label',\n 'dcterms:description',\n 'http://purl.org/dc/terms/description',\n 'vad:hasNext',\n 'http://example.org/vad#hasNext',\n 'vad:hasExecutor',\n 'http://example.org/vad#hasExecutor',\n 'vad:hasParentTrig',\n 'http://example.org/vad#hasParentTrig',\n 'vad:includes',\n 'http://example.org/vad#includes',\n 'vad:processSubtype',\n 'http://example.org/vad#processSubtype',\n 'vad:hasTrig',\n 'http://example.org/vad#hasTrig',\n 'vad:definesProcess',\n 'http://example.org/vad#definesProcess',\n 'vad:isSubprocessTrig',\n 'http://example.org/vad#isSubprocessTrig',\n 'vad:hasParentProcess',\n 'http://example.org/vad#hasParentProcess'\n ];\n\n /**\n * PTREE_PREDICATES - Предикаты, которые для сущностей типа vad:TypeProcess\n * должны храниться в графе vad:ptree (Дерево Процессов)\n * Остальные предикаты остаются в соответствующем TriG (схеме процесса)\n */\n const PTREE_PREDICATES = [\n 'rdf:type',\n 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',\n 'rdfs:label',\n 'http://www.w3.org/2000/01/rdf-schema#label',\n 'dcterms:description',\n 'http://purl.org/dc/terms/description',\n 'vad:hasTrig',\n 'http://example.org/vad#hasTrig',\n 'vad:hasParentProcess',\n 'http://example.org/vad#hasParentProcess'\n ];\n\n /**\n * RTREE_PREDICATES - Предикаты, которые для сущностей типа vad:TypeExecutor\n * должны храниться в графе vad:rtree (Дерево Исполнителей)\n * Остальные предикаты остаются в соответствующем TriG (схеме процесса)\n */\n const RTREE_PREDICATES = [\n 'rdf:type',\n 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',\n 'rdfs:label',\n 'http://www.w3.org/2000/01/rdf-schema#label'\n ];\n\n /**\n * TRIG_TYPES - Типы TriG графов в VAD онтологии\n * Используется для валидации и определения типов графов\n */\n const TRIG_TYPES = {\n PROCESS_TREE: ['vad:ProcessTree', 'http://example.org/vad#ProcessTree'],\n EXECUTOR_TREE: ['vad:ExecutorTree', 'http://example.org/vad#ExecutorTree'],\n VAD_PROCESS_DIA: ['vad:VADProcessDia', 'http://example.org/vad#VADProcessDia']\n };\n\n /**\n * PROCESS_SUBTYPES - Подтипы процессов в VAD онтологии\n *\n * Detailed (имеет vad:hasTrig):\n * - DetailedChild: дочерняя схема указывает на текущую (hasParentTrig)\n * - DetailedExternal: дочерняя схема НЕ указывает на текущую\n *\n * notDetailed (НЕ имеет vad:hasTrig):\n * - notDetailedChild: hasParentProcess совпадает с текущим процессом\n * - notDetailedExternal: hasParentProcess НЕ совпадает\n * - NotDefinedType: hasParentProcess = NotDefined\n *\n * Basic и Detailed - устаревшие, для обратной совместимости\n */\n const PROCESS_SUBTYPES = {\n // Устаревшие (для обратной совместимости)\n BASIC: ['vad:Basic', 'http://example.org/vad#Basic'],\n DETAILED: ['vad:Detailed', 'http://example.org/vad#Detailed'],\n // Детализированные (имеют vad:hasTrig)\n DETAILED_CHILD: ['vad:DetailedChild', 'http://example.org/vad#DetailedChild'],\n DETAILED_EXTERNAL: ['vad:DetailedExternal', 'http://example.org/vad#DetailedExternal'],\n // Недетализированные (НЕ имеют vad:hasTrig)\n NOT_DETAILED: ['vad:notDetailed', 'http://example.org/vad#notDetailed'],\n NOT_DETAILED_CHILD: ['vad:notDetailedChild', 'http://example.org/vad#notDetailedChild'],\n NOT_DETAILED_EXTERNAL: ['vad:notDetailedExternal', 'http://example.org/vad#notDetailedExternal'],\n NOT_DEFINED_TYPE: ['vad:NotDefinedType', 'http://example.org/vad#NotDefinedType']\n };\n\n /**\n * TYPE_PREDICATE_MAP - Сводная таблица «Объект-Предикат» (Приложение 1 к онтологии)\n * Определяет допустимые предикаты для каждого типа объекта в зависимости от контекста TriG\n *\n * Связь с онтологией: Приложение 1 в vad-basic-ontology.ttl\n */\n const TYPE_PREDICATE_MAP = {\n // Process в ptree (концепт) - общие свойства\n 'vad:TypeProcess': {\n ptree: [\n 'rdf:type',\n 'rdfs:label',\n 'dcterms:description',\n 'vad:hasTrig',\n 'vad:hasParentProcess'\n ],\n // Process в VADProcessDia (индивид) - индивидуальные свойства\n vadProcessDia: [\n 'vad:isSubprocessTrig',\n 'vad:hasExecutor',\n 'vad:processSubtype',\n 'vad:hasNext'\n ]\n },\n // Executor в rtree - общие свойства\n 'vad:TypeExecutor': {\n rtree: [\n 'rdf:type',\n 'rdfs:label'\n ]\n },\n // ExecutorGroup в VADProcessDia\n 'vad:ExecutorGroup': {\n vadProcessDia: [\n 'rdf:type',\n 'rdfs:label',\n 'vad:includes'\n ]\n },\n // VADProcessDia - свойства схемы процесса\n 'vad:VADProcessDia': {\n vadProcessDia: [\n 'rdf:type',\n 'rdfs:label',\n 'vad:hasParentTrig',\n 'vad:definesProcess'\n ]\n },\n // ProcessTree (vad:ptree)\n 'vad:ProcessTree': {\n ptree: [\n 'rdf:type',\n 'rdfs:label',\n 'vad:hasParentTrig'\n ]\n },\n // ExecutorTree (vad:rtree)\n 'vad:ExecutorTree': {\n rtree: [\n 'rdf:type',\n 'rdfs:label',\n 'vad:hasParentTrig'\n ]\n }\n };\n\n /**\n * Режим работы Smart Design:\n * - 'filtered' - справочники фильтруются по связанным значениям (Subject Type -> Predicate)\n * - 'full' - справочники отображают полный набор значений\n */\n let smartDesignMode = 'filtered';\n\n /**\n * Проверяет, является ли предикат предикатом для ptree\n * @param {string} predicateUri - URI или prefixed name предиката\n * @returns {boolean}\n */\n function isPtreePredicate(predicateUri) {\n return PTREE_PREDICATES.some(allowed =>\n predicateUri === allowed || predicateUri.endsWith('#' + allowed.split(':')[1])\n );\n }\n\n /**\n * Проверяет, является ли предикат предикатом для rtree\n * @param {string} predicateUri - URI или prefixed name предиката\n * @returns {boolean}\n */\n function isRtreePredicate(predicateUri) {\n return RTREE_PREDICATES.some(allowed =>\n predicateUri === allowed || predicateUri.endsWith('#' + allowed.split(':')[1])\n );\n }\n\n /**\n * Проверяет, является ли субъект типом vad:TypeProcess\n * @param {string} subjectUri - URI субъекта\n * @returns {boolean}\n */\n function isSubjectVadProcess(subjectUri) {\n const types = nodeTypesCache[subjectUri] || [];\n return types.some(t =>\n t === 'vad:TypeProcess' || t === 'http://example.org/vad#TypeProcess'\n );\n }\n\n /**\n * Проверяет, является ли субъект типом vad:TypeExecutor\n * @param {string} subjectUri - URI субъекта\n * @returns {boolean}\n */\n function isSubjectVadExecutor(subjectUri) {\n const types = nodeTypesCache[subjectUri] || [];\n return types.some(t =>\n t === 'vad:TypeExecutor' || t === 'http://example.org/vad#TypeExecutor'\n );\n }\n\n /**\n * Определяет тип TriG графа\n * @param {string} trigUri - URI TriG графа\n * @param {Array} quads - Массив квадов\n * @returns {string|null} - Тип графа или null\n */\n function getTrigType(trigUri, quads) {\n const typeQuad = quads.find(quad =>\n quad.subject.value === trigUri &&\n (quad.predicate.value.endsWith('#type') ||\n quad.predicate.value === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type')\n );\n\n if (typeQuad) {\n const typeValue = typeQuad.object.value;\n if (TRIG_TYPES.PROCESS_TREE.some(t => typeValue === t || typeValue.endsWith('#ProcessTree'))) {\n return 'vad:ProcessTree';\n }\n if (TRIG_TYPES.EXECUTOR_TREE.some(t => typeValue === t || typeValue.endsWith('#ExecutorTree'))) {\n return 'vad:ExecutorTree';\n }\n if (TRIG_TYPES.VAD_PROCESS_DIA.some(t => typeValue === t || typeValue.endsWith('#VADProcessDia'))) {\n return 'vad:VADProcessDia';\n }\n }\n\n // Определение типа по эвристике (для обратной совместимости)\n if (trigUri.endsWith('#ptree') || trigUri.includes('ptree')) {\n return 'vad:ProcessTree';\n }\n if (trigUri.endsWith('#rtree') || trigUri.includes('rtree')) {\n return 'vad:ExecutorTree';\n }\n return 'vad:VADProcessDia';\n }\n\n /**\n * Проверяет, является ли тип типом ProcessTree\n * @param {string} typeUri - URI типа\n * @returns {boolean}\n */\n function isProcessTreeType(typeUri) {\n return TRIG_TYPES.PROCESS_TREE.some(t =>\n typeUri === t || typeUri.endsWith('#ProcessTree')\n );\n }\n\n /**\n * Проверяет, является ли тип типом ExecutorTree\n * @param {string} typeUri - URI типа\n * @returns {boolean}\n */\n function isExecutorTreeType(typeUri) {\n return TRIG_TYPES.EXECUTOR_TREE.some(t =>\n typeUri === t || typeUri.endsWith('#ExecutorTree')\n );\n }\n\n /**\n * Проверяет, является ли тип типом VADProcessDia\n * @param {string} typeUri - URI типа\n * @returns {boolean}\n */\n function isVADProcessDiaType(typeUri) {\n return TRIG_TYPES.VAD_PROCESS_DIA.some(t =>\n typeUri === t || typeUri.endsWith('#VADProcessDia')\n );\n }\n\n /**\n * Проверяет, является ли подтип процесса детализированным (Detailed, DetailedChild, DetailedExternal)\n * @param {string} subtypeUri - URI подтипа\n * @returns {boolean}\n */\n function isDetailedSubtype(subtypeUri) {\n return PROCESS_SUBTYPES.DETAILED.some(t => subtypeUri === t || subtypeUri.endsWith('#Detailed')) ||\n PROCESS_SUBTYPES.DETAILED_CHILD.some(t => subtypeUri === t || subtypeUri.endsWith('#DetailedChild')) ||\n PROCESS_SUBTYPES.DETAILED_EXTERNAL.some(t => subtypeUri === t || subtypeUri.endsWith('#DetailedExternal'));\n }\n\n /**\n * Получает стиль VADNodeStyle для процесса на основе его processSubtype\n * Используется для визуализации процессов с разными цветами в зависимости от подтипа\n * @param {Array<string>} nodeSubtypes - массив подтипов процесса\n * @returns {string} - DOT-строка стиля для узла\n */\n function getProcessStyleForSubtype(nodeSubtypes) {\n // Порядок приоритета проверки:\n // 1. Конкретные подтипы (DetailedChild, DetailedExternal, notDetailedChild, notDetailedExternal, NotDefinedType)\n // 2. Общие подтипы (Detailed, notDetailed)\n // 3. Устаревшие подтипы (Basic)\n // 4. По умолчанию - notDetailedChild (зеленый)\n\n for (const subtype of nodeSubtypes) {\n // DetailedChild - голубой\n if (PROCESS_SUBTYPES.DETAILED_CHILD.some(t => subtype === t || subtype.endsWith('#DetailedChild'))) {\n return VADNodeStyles['ProcessStyleDetailedChild'].dot;\n }\n // DetailedExternal - оранжевый\n if (PROCESS_SUBTYPES.DETAILED_EXTERNAL.some(t => subtype === t || subtype.endsWith('#DetailedExternal'))) {\n return VADNodeStyles['ProcessStyleDetailedExternal'].dot;\n }\n // notDetailedChild - зеленый\n if (PROCESS_SUBTYPES.NOT_DETAILED_CHILD.some(t => subtype === t || subtype.endsWith('#notDetailedChild'))) {\n return VADNodeStyles['ProcessStyleNotDetailedChild'].dot;\n }\n // notDetailedExternal - серый\n if (PROCESS_SUBTYPES.NOT_DETAILED_EXTERNAL.some(t => subtype === t || subtype.endsWith('#notDetailedExternal'))) {\n return VADNodeStyles['ProcessStyleNotDetailedExternal'].dot;\n }\n // NotDefinedType - красноватый\n if (PROCESS_SUBTYPES.NOT_DEFINED_TYPE.some(t => subtype === t || subtype.endsWith('#NotDefinedType'))) {\n return VADNodeStyles['ProcessStyleNotDefinedType'].dot;\n }\n // Общий Detailed - голубой (для обратной совместимости)\n if (PROCESS_SUBTYPES.DETAILED.some(t => subtype === t || subtype.endsWith('#Detailed'))) {\n return VADNodeStyles['ProcessStyleDetailed'].dot;\n }\n // notDetailed общий - зеленый\n if (PROCESS_SUBTYPES.NOT_DETAILED.some(t => subtype === t || subtype.endsWith('#notDetailed'))) {\n return VADNodeStyles['ProcessStyleNotDetailedChild'].dot;\n }\n // Basic (устаревший) - зеленый\n if (PROCESS_SUBTYPES.BASIC.some(t => subtype === t || subtype.endsWith('#Basic'))) {\n return VADNodeStyles['ProcessStyleBasic'].dot;\n }\n }\n\n // По умолчанию - notDetailedChild (зеленый)\n return VADNodeStyles['ProcessStyleNotDetailedChild'].dot;\n }\n\n /**\n * Проверяет существование триплета во всех графах (для проверки дубликатов)\n * Поддерживает проверку как полных URI, так и prefixed names\n * @param {string} subjectValue - URI или prefixed name субъекта\n * @param {string} predicateValue - URI или prefixed name предиката\n * @param {string} objectValue - URI, prefixed name или литерал объекта\n * @returns {Object|null} - {graphUri, graphLabel} если найден дубликат, иначе null\n */\n function findDuplicateTriple(subjectValue, predicateValue, objectValue) {\n // Преобразуем prefixed names в полные URI для сравнения\n let subjectUri = subjectValue;\n let predicateUri = predicateValue;\n let objectUri = objectValue;\n\n for (const [prefix, namespace] of Object.entries(currentPrefixes)) {\n if (subjectValue.startsWith(prefix + ':')) {\n subjectUri = namespace + subjectValue.substring(prefix.length + 1);\n }\n if (predicateValue.startsWith(prefix + ':')) {\n predicateUri = namespace + predicateValue.substring(prefix.length + 1);\n }\n if (objectValue.startsWith(prefix + ':')) {\n objectUri = namespace + objectValue.substring(prefix.length + 1);\n }\n }\n\n // Также преобразуем полные URI в prefixed names для альтернативной проверки\n const subjectPrefixed = getPrefixedName(subjectUri, currentPrefixes);\n const predicatePrefixed = getPrefixedName(predicateUri, currentPrefixes);\n const objectPrefixed = getPrefixedName(objectUri, currentPrefixes);\n\n for (const quad of currentQuads) {\n const qSubjectUri = quad.subject.value;\n const qPredicateUri = quad.predicate.value;\n const qObjectValue = quad.object.value;\n const qSubjectPrefixed = getPrefixedName(qSubjectUri, currentPrefixes);\n const qPredicatePrefixed = getPrefixedName(qPredicateUri, currentPrefixes);\n const qObjectPrefixed = quad.object.termType === 'Literal'\n ? quad.object.value\n : getPrefixedName(qObjectValue, currentPrefixes);\n\n // Сравниваем как полные URI, так и prefixed names\n const subjectMatch = (subjectUri === qSubjectUri) || (subjectPrefixed === qSubjectPrefixed);\n const predicateMatch = (predicateUri === qPredicateUri) || (predicatePrefixed === qPredicatePrefixed);\n const objectMatch = (objectUri === qObjectValue) || (objectPrefixed === qObjectPrefixed) ||\n (objectValue === qObjectValue) || (objectValue === qObjectPrefixed);\n\n if (subjectMatch && predicateMatch && objectMatch) {\n const graphUri = quad.graph ? quad.graph.value : null;\n const graphLabel = graphUri ? getPrefixedName(graphUri, currentPrefixes) : 'default graph';\n return { graphUri, graphLabel };\n }\n }\n\n return null;\n }\n\n /**\n * Определяет целевой граф для триплета на основе правил ptree\n * Если субъект является vad:TypeProcess и предикат в PTREE_PREDICATES,\n * триплет должен быть добавлен в vad:ptree\n * @param {string} subjectValue - URI или prefixed name субъекта\n * @param {string} predicateValue - URI или prefixed name предиката\n * @param {string} originalTrigValue - Исходный выбранный граф\n * @param {string} objectValue - URI или prefixed name объекта (опционально, для проверки rdf:type)\n * @returns {string} - URI графа для добавления триплета\n */\n function determineTargetGraph(subjectValue, predicateValue, originalTrigValue, objectValue = null) {\n // Преобразуем prefixed names в полные URI\n let subjectUri = subjectValue;\n let predicateUri = predicateValue;\n let objectUri = objectValue;\n\n for (const [prefix, namespace] of Object.entries(currentPrefixes)) {\n if (subjectValue.startsWith(prefix + ':')) {\n subjectUri = namespace + subjectValue.substring(prefix.length + 1);\n }\n if (predicateValue.startsWith(prefix + ':')) {\n predicateUri = namespace + predicateValue.substring(prefix.length + 1);\n }\n if (objectValue && objectValue.startsWith(prefix + ':')) {\n objectUri = namespace + objectValue.substring(prefix.length + 1);\n }\n }\n\n // Специальный случай: добавление rdf:type vad:TypeProcess - всегда в ptree\n const isRdfType = (predicateUri === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' ||\n predicateValue === 'rdf:type');\n const isProcessType = (objectUri === 'http://example.org/vad#TypeProcess' ||\n objectValue === 'vad:TypeProcess');\n if (isRdfType && isProcessType) {\n return 'vad:ptree';\n }\n\n // Проверяем, является ли субъект типом vad:TypeProcess\n if (isSubjectVadProcess(subjectUri)) {\n // Проверяем, является ли предикат предикатом для ptree\n if (isPtreePredicate(predicateUri) || isPtreePredicate(predicateValue)) {\n // Возвращаем vad:ptree\n return 'vad:ptree';\n }\n }\n\n return originalTrigValue;\n }\n\n /**\n * Валидирует RDF триплеты на соответствие схеме VAD онтологии\n *\n * Проверяет, что все предикаты и типы объектов в RDF данных соответствуют\n * разрешенным значениям, определенным в VAD онтологии (VAD_ALLOWED_PREDICATES,\n * VAD_ALLOWED_TYPES). Используется для проверки корректности данных перед\n * визуализацией и при ручном тестировании через кнопку \"Тест\".\n *\n * @param {Array} quads - Массив RDF квадов (N3.js quad objects)\n * @param {Object} prefixes - Объект с префиксами {prefix: namespace}\n * @returns {Object} Результат валидации с полями:\n * - valid {boolean} - true если данные валидны, false если есть ошибки\n * - errors {Array<Object>} - Массив ошибок, каждая с полями:\n * - triple {string} - Триплет с ошибкой\n * - position {string} - Позиция ошибки (predicate, object, etc.)\n * - value {string} - Недопустимое значение\n * - message {string} - Описание ошибки\n *\n * @example\n * const quads = [...]; // Parsed RDF quads\n * const prefixes = { vad: 'http://example.org/vad#', rdf: '...' };\n * const result = validateVAD(quads, prefixes);\n * if (!result.valid) {\n * console.error('Validation errors:', result.errors);\n * }\n *\n * @see VAD_ALLOWED_PREDICATES - Список разрешенных предикатов\n * @see VAD_ALLOWED_TYPES - Список разрешенных типов объектов\n * @see for
[2026-01-26T19:23:56.105Z] [INFO] matVADErrors - Форматирование ошибок для отображения\n */\n function validateVAD(quads, prefixes) {\n const errors = [];\n\n quads.forEach((quad, index) => {\n const predicateUri = quad.predicate.value;\n const predicateLabel = getPrefixedName(predicateUri, prefixes);\n\n // Проверяем, что предикат разрешен\n const predicateAllowed = VAD_ALLOWED_PREDICATES.some(allowed =>\n predicateUri === allowed || predicateLabel === allowed\n );\n\n if (!predicateAllowed) {\n const subjectLabel = getPrefixedName(quad.subject.value, prefixes);\n const objectLabel = quad.object.termType === 'Literal'\n ? `\"${quad.object.value}\"`\n : getPrefixedName(quad.object.value, prefixes);\n\n errors.push({\n triple: `${subjectLabel} ${predicateLabel} ${objectLabel}`,\n position: 'predicate',\n value: predicateLabel,\n message: `Недопустимый предикат: ${predicateLabel}`\n });\n }\n\n // Если предикат - rdf:type, проверяем, что тип разрешен\n const typePredicates = [\n 'rdf:type',\n 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'\n ];\n\n if (typePredicates.includes(predicateUri) || typePredicates.includes(predicateLabel)) {\n const typeUri = quad.object.value;\n const typeLabel = getPrefixedName(typeUri, prefixes);\n\n const typeAllowed = VAD_ALLOWED_TYPES.some(allowed =>\n typeUri === allowed || typeLabel === allowed\n );\n\n if (!typeAllowed) {\n const subjectLabel = getPrefixedName(quad.subject.value, prefixes);\n\n errors.push({\n triple: `${subjectLabel} ${predicateLabel} ${typeLabel}`,\n position: 'object (type)',\n value: typeLabel,\n message: `Недопустимый тип объекта: ${typeLabel}`\n });\n }\n }\n });\n\n return {\n valid: errors.length === 0,\n errors: errors\n };\n }\n\n /**\n * Форматирует ошибки валидации VAD для отображения\n * @param {Array} errors - Массив ошибок\n * @returns {string} - Отформатированное сообщение\n */\n function formatVADErrors(errors) {\n let message = 'ОШИБКА ВАЛИДАЦИИ VAD\\n';\n message += '═══════════════════════════════════════\\n\\n';\n\n errors.forEach((error, index) => {\n message += `Ошибка ${index + 1}:\\n`;\n message += ` Триплет: ${error.triple}\\n`;\n message += ` Позиция: ${error.position}\\n`;\n message += ` Значение: ${error.value}\\n`;\n message += ` ${error.message}\\n\\n`;\n });\n\n message += '═══════════════════════════════════════\\n';\n message += `Всего ошибок: ${errors.length}\\n`;\n message += '\\nРазрешенные типы: vad:TypeProcess, vad:ExecutorGroup, vad:TypeExecutor\\n';\n message += 'Разрешенные предикаты: rdf:type, rdfs:label, dcterms:description,\\n';\n message += ' vad:hasNext, vad:hasExecutor, vad:hasParentTrig, vad:includes,\\n';\n message += ' vad:processSubtype, vad:hasTrig';\n\n return message;\n }\n\n // ============================================================================\n // КОНФИГУРАЦИЯ СТИЛЕЙ\n // ============================================================================\n\n const StyleName = {\n nodeStyles: {\n 'PersonStyle': {\n types: ['foaf:Person', 'schema:Person', 'http://xmlns.com/foaf/0.1/Person'],\n dot: 'shape=\"octagon\" height=\"0.75\" width=\"0.75\" fixedsize=\"true\" color=\"#9C27B0\" fillcolor=\"#F3E5F5\" fontname=\"Arial\" fontsize=\"10\" style=\"filled\"',\n label: 'Люди (foaf:Person)',\n description: 'Объекты типа foaf:Person или schema:Person'\n },\n 'OrganizationStyle': {\n types: ['foaf:Organization', 'schema:Organization', 'http://xmlns.com/foaf/0.1/Organization'],\n dot: 'shape=\"box\" height=\"0.6\" width=\"1.2\" color=\"Blue\" fillcolor=\"#E6F3FF\" fontname=\"Arial\" fontsize=\"10\" style=\"filled,bold\"',\n label: 'Организации (foaf:Organization)',\n description: 'Объекты типа foaf:Organization'\n },\n 'DocumentStyle': {\n types: ['foaf:Document', 'schema:Document', 'http://xmlns.com/foaf/0.1/Document'],\n dot: 'shape=\"note\" height=\"0.6\" width=\"1.0\" color=\"Green\" fillcolor=\"#E8F5E9\" fontname=\"Arial\" fontsize=\"10\" style=\"filled\"',\n label: 'Документы (foaf:Document)',\n description: 'Объекты типа foaf:Document'\n },\n 'LiteralStyle': {\n types: ['_Literal'],\n dot: 'shape=\"box\" color=\"#666666\" fillcolor=\"#FFFFCC\" fontname=\"Arial\" fontsize=\"9\" style=\"filled,rounded\"',\n label: 'Литералы (Literal)',\n description: 'Строковые значения, числа, даты'\n },\n 'BlankNodeStyle': {\n types: ['_BlankNode'],\n dot: 'shape=\"ellipse\" color=\"#999999\" fillcolor=\"#E0E0E0\" fontname=\"Arial\" fontsize=\"9\" style=\"filled,dashed\"',\n label: 'Пустые узлы (BlankNode)',\n description: 'Анонимные узлы без URI'\n },\n 'default': {\n types: [],\n dot: 'shape=\"ellipse\" color=\"#1976D2\" fillcolor=\"#CCE5FF\" fontname=\"Arial\" fontsize=\"10\" style=\"filled\"',\n label: 'По умолчанию (URI)',\n description: 'Все остальные URI-ресурсы'\n }\n },\n edgeStyles: {\n 'TypeStyle': {\n predicates: ['rdf:type', 'a', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'],\n dot: 'color=\"#9C27B0\" penwidth=\"2\" style=\"dashed\" arrowhead=\"empty\"',\n label: 'Тип объекта (rdf:type)',\n description: 'Связь объекта с его RDF-типом'\n },\n 'KnowsStyle': {\n predicates: ['foaf:knows', 'http://xmlns.com/foaf/0.1/knows'],\n dot: 'color=\"#4CAF50\" penwidth=\"2\" style=\"solid\" arrowhead=\"vee\"',\n label: 'Знает (foaf:knows)',\n description: 'Социальная связь между людьми'\n },\n 'MemberStyle': {\n predicates: ['foaf:member', 'schema:member', 'http://xmlns.com/foaf/0.1/member'],\n dot: 'color=\"#795548\" penwidth=\"2\" style=\"solid\" arrowhead=\"diamond\"',\n label: 'Член (foaf:member)',\n description: 'Членство в организации'\n },\n 'AttributeStyle': {\n predicates: [\n 'foaf:name', 'http://xmlns.com/foaf/0.1/name',\n 'foaf:age', 'http://xmlns.com/foaf/0.1/age',\n 'rdfs:label', 'http://www.w3.org/2000/01/rdf-schema#label',\n 'rdfs:comment', 'http://www.w3.org/2000/01/rdf-schema#comment'\n ],\n dot: 'color=\"#2196F3\" penwidth=\"1\" style=\"dotted\" arrowhead=\"normal\"',\n label: 'Атрибуты (name, label...)',\n description: 'Свойства объекта: имя, возраст и др.'\n },\n 'default': {\n predicates: [],\n dot: 'color=\"#666666\" penwidth=\"1\" style=\"solid\" arrowhead=\"normal\"',\n label: 'По умолчанию',\n description: 'Все остальные предикаты'\n }\n }\n };\n\n // ============================================================================\n // СТИЛИ VAD (Value Added Chain Diagram)\n // ============================================================================\n\n const VADNodeStyles = {\n // =================================================================\n // Детализированные процессы (Detailed) - имеют vad:hasTrig\n // =================================================================\n 'ProcessStyleDetailedChild': {\n types: ['vad:TypeProcess', 'http://example.org/vad#TypeProcess'],\n subtypes: ['vad:DetailedChild', 'http://example.org/vad#DetailedChild'],\n // cds shape с голубой заливкой для детализированного подпроцесса\n dot: 'shape=\"cds\" height=\"0.8\" width=\"1.5\" color=\"#1565C0\" fillcolor=\"#90CAF9\" fontname=\"Arial\" fontsize=\"11\" style=\"filled\"',\n label: 'Детализированный подпроцесс (vad:DetailedChild)',\n description: 'Детализированный процесс, являющийся подпроцессом текущей схемы'\n },\n 'ProcessStyleDetailedExternal': {\n types: ['vad:TypeProcess', 'http://example.org/vad#TypeProcess'],\n subtypes: ['vad:DetailedExternal', 'http://example.org/vad#DetailedExternal'],\n // cds shape с оранжевой заливкой для детализированного внешнего процесса\n dot: 'shape=\"cds\" height=\"0.8\" width=\"1.5\" color=\"#E65100\" fillcolor=\"#FFB74D\" fontname=\"Arial\" fontsize=\"11\" style=\"filled\"',\n label: 'Детализированный внешний (vad:DetailedExternal)',\n description: 'Детализированный процесс, НЕ являющийся подпроцессом текущей схемы'\n },\n // =================================================================\n // Недетализированные процессы (notDetailed) - НЕ имеют vad:hasTrig\n // =================================================================\n 'ProcessStyleNotDetailedChild': {\n types: ['vad:TypeProcess', 'http://example.org/vad#TypeProcess'],\n subtypes: ['vad:notDetailedChild', 'http://example.org/vad#notDetailedChild'],\n // cds shape с зелёной заливкой для недетализированного подпроцесса\n dot: 'shape=\"cds\" height=\"0.8\" width=\"1.5\" color=\"#2E7D32\" fillcolor=\"#A5D6A7\" fontname=\"Arial\" fontsize=\"11\" style=\"filled\"',\n label: 'Недетализированный подпроцесс (vad:notDetailedChild)',\n description: 'Недетализированный процесс-подпроцесс'\n },\n 'ProcessStyleNotDetailedExternal': {\n types: ['vad:TypeProcess', 'http://example.org/vad#TypeProcess'],\n subtypes: ['vad:notDetailedExternal', 'http://example.org/vad#notDetailedExternal'],\n // cds shape со светло-серой заливкой для недетализированного внешнего процесса\n dot: 'shape=\"cds\" height=\"0.8\" width=\"1.5\" color=\"#616161\" fillcolor=\"#E0E0E0\" fontname=\"Arial\" fontsize=\"11\" style=\"filled\"',\n label: 'Недетализированный внешний (vad:notDetailedExternal)',\n description: 'Недетализированный внешний процесс'\n },\n 'ProcessStyleNotDefinedType': {\n types: ['vad:TypeProcess', 'http://example.org/vad#TypeProcess'],\n subtypes: ['vad:NotDefinedType', 'http://example.org/vad#NotDefinedType'],\n // cds shape с красноватой заливкой для процесса с неопределенным типом\n dot: 'shape=\"cds\" height=\"0.8\" width=\"1.5\" color=\"#C62828\" fillcolor=\"#FFCDD2\" fontname=\"Arial\" fontsize=\"11\" style=\"filled\"',\n label: 'Тип не определен (vad:NotDefinedType)',\n description: 'Процесс с неопределенным родительским процессом'\n },\n // =================================================================\n // Устаревшие стили для обратной совместимости\n // =================================================================\n 'ProcessStyleBasic': {\n types: ['vad:TypeProcess', 'http://example.org/vad#TypeProcess'],\n subtypes: ['vad:Basic', 'http://example.org/vad#Basic'],\n // cds shape (chevron) с зелёной заливкой для базового типа (устаревший)\n dot: 'shape=\"cds\" height=\"0.8\" width=\"1.5\" color=\"#2E7D32\" fillcolor=\"#A5D6A7\" fontname=\"Arial\" fontsize=\"11\" style=\"filled\"',\n label: 'Процесс Базовый (vad:Basic) [устаревший]',\n description: 'Устаревший базовый бизнес-процесс в VAD диаграмме'\n },\n 'ProcessStyleDetailed': {\n types: ['vad:TypeProcess', 'http://example.org/vad#TypeProcess'],\n subtypes: ['vad:Detailed', 'http://example.org/vad#Detailed'],\n // cds shape (chevron) с голубой заливкой для детализированного типа (устаревший)\n dot: 'shape=\"cds\" height=\"0.8\" width=\"1.5\" color=\"#1565C0\" fillcolor=\"#90CAF9\" fontname=\"Arial\" fontsize=\"11\" style=\"filled\"',\n label: 'Процесс Детализированный (vad:Detailed) [устаревший]',\n description: 'Устаревший детализированный бизнес-процесс в VAD диаграмме'\n },\n // =================================================================\n // Другие элементы\n // =================================================================\n 'ExecutorGroupStyle': {\n types: ['vad:ExecutorGroup', 'http://example.org/vad#ExecutorGroup'],\n dot: 'shape=\"ellipse\" color=\"#B8860B\" fillcolor=\"#FFFFCC\" fontname=\"Arial\" fontsize=\"9\" style=\"filled\"',\n label: 'Группа исполнителей (vad:ExecutorGroup)',\n description: 'Группа исполнителей процесса (эллипс с желтоватой заливкой)'\n },\n 'ExecutorStyle': {\n types: ['vad:TypeExecutor', 'http://example.org/vad#TypeExecutor'],\n dot: 'shape=\"ellipse\" height=\"0.4\" width=\"0.8\" color=\"#6A1B9A\" fillcolor=\"#E1BEE7\" fontname=\"Arial\" fontsize=\"9\" style=\"filled\"',\n label: 'Исполнитель (vad:TypeExecutor)',\n description: 'Исполнитель процесса'\n },\n 'default': {\n types: [],\n dot: 'shape=\"ellipse\" color=\"#1976D2\" fillcolor=\"#CCE5FF\" fontname=\"Arial\" fontsize=\"10\" style=\"filled\"',\n label: 'По умолчанию',\n description: 'Другие объекты'\n }\n };\n\n const VADEdgeStyles = {\n 'HasNextStyle': {\n predicates: ['vad:hasNext', 'http://example.org/vad#hasNext'],\n // Зелёная стрелка для связей между процессами\n dot: 'color=\"#2E7D32\" penwidth=\"2\" style=\"solid\" arrowhead=\"vee\"',\n label: 'Следующий (vad:hasNext)',\n description: 'Связь с следующим процессом'\n },\n 'HasExecutorStyle': {\n predicates: ['vad:hasExecutor', 'http://example.org/vad#hasExecutor'],\n dot: 'color=\"#1565C0\" penwidth=\"1\" style=\"dashed\" arrowhead=\"none\"',\n label: 'Исполнитель (vad:hasExecutor)',\n description: 'Связь процесса с группой исполнителей (ненаправленная)'\n },\n 'IncludesStyle': {\n predicates: ['vad:includes', 'http://example.org/vad#includes'],\n dot: 'color=\"#6A1B9A\" penwidth=\"1\" style=\"dotted\" arrowhead=\"normal\"',\n label: 'Включает (vad:includes)',\n description: 'Связь группы с исполнителями'\n },\n 'HasParentStyle': {\n predicates: ['vad:hasParentTrig', 'http://example.org/vad#hasParentTrig'],\n dot: 'color=\"#999999\" penwidth=\"1\" style=\"dashed\" arrowhead=\"empty\"',\n label: 'Родитель (vad:hasParentTrig)',\n description: 'Связь с родительским процессом'\n },\n 'TypeStyle': {\n predicates: ['rdf:type', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'],\n dot: 'color=\"#9C27B0\" penwidth=\"1\" style=\"dashed\" arrowhead=\"empty\"',\n label: 'Тип (rdf:type)',\n description: 'Тип объекта'\n },\n 'default': {\n predicates: [],\n dot: 'color=\"#666666\" penwidth=\"1\" style=\"solid\" arrowhead=\"normal\"',\n label: 'По умолчанию',\n description: 'Другие связи'\n }\n };\n\n const AggregationNodeStyles = {\n 'PersonStyle': {\n types: ['foaf:Person', 'schema:Person', 'http://xmlns.com/foaf/0.1/Person'],\n dot: 'shape=\"ellipse\" color=\"#9C27B0\" penwidth=\"2\" fillcolor=\"#F3E5F5\" fontname=\"Arial\" fontsize=\"10\" style=\"filled\"',\n label: 'Люди (foaf:Person)',\n description: 'Объекты типа foaf:Person'\n },\n 'OrganizationStyle': {\n types: ['foaf:Organization', 'schema:Organization', 'http://xmlns.com/foaf/0.1/Organization'],\n dot: 'shape=\"ellipse\" color=\"Blue\" penwidth=\"3\" fillcolor=\"#E3F2FD\" fontname=\"Arial\" fontsize=\"10\" style=\"filled\"',\n label: 'Организации (foaf:Organization)',\n description: 'Объекты типа foaf:Organization'\n },\n 'DocumentStyle': {\n types: ['foaf:Document', 'schema:Document', 'http://xmlns.com/foaf/0.1/Document'],\n dot: 'shape=\"ellipse\" color=\"Green\" penwidth=\"2\" fillcolor=\"#E8F5E9\" fontname=\"Arial\" fontsize=\"10\" style=\"filled\"',\n label: 'Документы (foaf:Document)',\n description: 'Объекты типа foaf:Document'\n },\n 'BlankNodeStyle': {\n types: ['_BlankNode'],\n dot: 'shape=\"ellipse\" color=\"#999999\" penwidth=\"1\" fillcolor=\"#E0E0E0\" fontname=\"Arial\" fontsize=\"9\" style=\"filled,dashed\"',\n label: 'Пустые узлы (BlankNode)',\n description: 'Анонимные узлы без URI'\n },\n 'default': {\n types: [],\n dot: 'shape=\"ellipse\" color=\"#1976D2\" penwidth=\"1\" fillcolor=\"#CCE5FF\" fontname=\"Arial\" fontsize=\"10\" style=\"filled\"',\n label: 'По умолчанию (URI)',\n description: 'Все остальные URI-ресурсы'\n }\n };\n\n // ============================================================================\n // ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ\n // ============================================================================\n\n let currentSvgElement = null;\n let currentScale = 1.0;\n let currentPrefixes = {};\n let nodeTypesCache = {};\n let nodeSubtypesCache = {};\n let currentQuads = [];\n let nodeLabelToUri = {};\n let selectedNodeElement = null;\n let propertiesPanelCounter = 0;\n let openPropertiesPanels = [];\n let currentMode = Mode;\n let draggedPanel = null;\n let dragOffsetX = 0;\n let dragOffsetY = 0;\n let currentStore = null;\n let comunicaEngine = null;\n let currentDotCode = '';\n\n // virtualRDFdata - хранилище вычисляемых RDF данных (vad:processSubtype и др.)\n // Структура: { 'vad:vt_trigName': { quads: [...], trigData: {...} } }\n let virtualRDFdata = {};\n\n const defaultSparqlQuery = `SELECT ?s ?p ?o\nWHERE {\n ?s ?p ?o .\n}`;\n\n /**\n * Генерирует SPARQL PREFIX декларации из объекта префиксов\n * @param {Object} prefixes - Объект с префиксами {prefix: uri}\n * @returns {string} - Строка с PREFIX декларациями\n */\n function generateSparqlPrefixes(prefixes) {\n if (!prefixes || Object.keys(prefixes).length === 0) {\n return '';\n }\n\n let prefixLines = [];\n for (const [prefix, uri] of Object.entries(prefixes)) {\n prefixLines.push(`PREFIX ${prefix}: <${uri}>`);\n }\n return prefixLines.join('\\n') + '\\n\\n';\n }\n\n /**\n * Генерирует SPARQL запрос с GRAPH clause для указанного TriG\n * @param {string} trigUri - URI TriG для фильтрации\n * @returns {string} - SPARQL запрос с GRAPH clause и PREFIX декларациями\n */\n function getSparqlQueryForTriG(trigUri) {\n if (!trigUri) return defaultSparqlQuery;\n\n const prefixedUri = getPrefixedName(trigUri, currentPrefixes);\n // Если URI имеет префикс, используем его, иначе используем полный URI в угловых скобках\n const graphRef = prefixedUri.includes(':') && !prefixedUri.startsWith('http')\n ? prefixedUri\n : `<${trigUri}>`;\n\n // Генерируем PREFIX декларации для SPARQL запроса\n const prefixDeclarations = generateSparqlPrefixes(currentPrefixes);\n\n return `${prefixDeclarations}SELECT ?s ?p ?o\nWHERE {\n GRAPH ${graphRef} {\n ?s ?p ?o .\n }\n}`;\n }\n\n /**\n * Обновляет SPARQL запрос в текстовом поле для текущего выбранного TriG\n */\n function updateSparqlQueryForTriG() {\n if (currentMode !== 'vad-trig' || !selectedTrigUri) return;\n\n const queryInput = document.getElementById('sparql-query');\n if (!queryInput) return;\n\n queryInput.value = getSparqlQueryForTriG(selectedTrigUri);\n }\n\n let activeFilters = [...getFilterConfig(Mode).hiddenPredicates];\n let allPredicates = [];\n\n // ============================================================================\n // ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ДЛЯ VAD TriG РЕЖИМА\n // ============================================================================\n\n let trigHierarchy = {}; // Иерархия TriG графов: { uri -> { label, hasParentTrig, children, quads } }\n let selectedTrigUri = null; // Текущий выбранный TriG для отображения\n let allTrigGraphs = []; // Список всех TriG графов\n let isNewTrigQuery = false; // Флаг: текущий SPARQL запрос создан функцией \"New TriG\"\n const PTREE_GRAPH_URI = 'http://example.org/vad#ptree'; // URI графа Дерево Процессов\n const RTREE_GRAPH_URI = 'http://example.org/vad#rtree'; // URI графа Дерево Исполнителей\n\n /**\n * Получает метаданные процесса из vad:ptree (rdfs:label, dcterms:description, vad:hasTrig)\n * @param {string} processUri - URI процесса\n * @param {Object} prefixes - Словарь префиксов\n * @returns {Object} - { label: string|null, description: string|null, hasTrig: string|null }\n */\n function getProcessMetadataFromPtree(processUri, prefixes) {\n const result = { label: null, description: null, hasTrig: null };\n\n // Если нет trigHierarchy или нет vad:ptree, возвращаем пустой результат\n if (!trigHierarchy || !trigHierarchy[PTREE_GRAPH_URI]) {\n return result;\n }\n\n const ptreeQuads = trigHierarchy[PTREE_GRAPH_URI].quads;\n\n ptreeQuads.forEach(quad => {\n if (quad.subject.value !== processUri) return;\n\n const predicateUri = quad.predicate.value;\n const predicateLabel = getPrefixedName(predicateUri, prefixes);\n\n if (predicateLabel === 'rdfs:label' || predicateUri === 'http://www.w3.org/2000/01/rdf-schema#label') {\n result.label = quad.object.value;\n }\n if (predicateLabel === 'dcterms:description' || predicateUri === 'http://purl.org/dc/terms/description') {\n result.description = quad.object.value;\n }\n if (predicateLabel === 'vad:hasTrig' || predicateUri === 'http://example.org/vad#hasTrig') {\n result.hasTrig = quad.object.value;\n }\n });\n\n return result;\n }\n\n /**\n * Получает имя исполнителя (rdfs:label) из vad:rtree\n * @param {string} executorUri - URI исполнителя\n * @param {Object} prefixes - Словарь префиксов\n * @returns {string|null} - rdfs:label исполнителя или null\n */\n function getExecutorNameFromRtree(executorUri, prefixes) {\n // Если нет trigHierarchy или нет vad:rtree, возвращаем null\n if (!trigHierarchy || !trigHierarchy[RTREE_GRAPH_URI]) {\n return null;\n }\n\n const rtreeQuads = trigHierarchy[RTREE_GRAPH_URI].quads;\n\n for (const quad of rtreeQuads) {\n if (quad.subject.value !== executorUri) continue;\n\n const predicateUri = quad.predicate.value;\n const predicateLabel = getPrefixedName(predicateUri, prefixes);\n\n if (predicateLabel === 'rdfs:label' || predicateUri === 'http://www.w3.org/2000/01/rdf-schema#label') {\n return quad.object.value;\n }\n }\n\n return null;\n }\n\n // ============================================================================\n // ФУНКЦИИ ДЛЯ VAD TriG РЕЖИМА\n // ============================================================================\n\n /**\n * Парсит иерархию TriG графов из квадов\n * @param {Array} quads - Все квады из RDF данных\n * @param {Object} prefixes - Словарь префиксов\n * @returns {Object} - { valid: boolean, errors: [], hierarchy: {}, rootTrigUri: string }\n */\n function parseTriGHierarchy(quads, prefixes) {\n const errors = [];\n const hierarchy = {};\n const graphUris = new Set();\n\n // Собираем все уникальные именованные графы\n quads.forEach(quad => {\n if (quad.graph && quad.graph.value && quad.graph.value !== '') {\n graphUris.add(quad.graph.value);\n }\n });\n\n // Инициализируем структуру для каждого графа\n graphUris.forEach(graphUri => {\n hierarchy[graphUri] = {\n uri: graphUri,\n label: null,\n hasParent: null,\n children: [],\n quads: [],\n processes: []\n };\n });\n\n // Собираем квады для каждого графа\n quads.forEach(quad => {\n const graphUri = quad.graph?.value || '';\n if (graphUri && hierarchy[graphUri]) {\n hierarchy[graphUri].quads.push(quad);\n }\n });\n\n // Ищем rdfs:label и vad:hasParentTrig для каждого графа (определяются вне графа)\n quads.forEach(quad => {\n const subjectUri = quad.subject.value;\n const predicateUri = quad.predicate.value;\n const predicateLabel = getPrefixedName(predicateUri, prefixes);\n\n // Проверяем, является ли субъект именованным графом\n if (hierarchy[subjectUri]) {\n // rdfs:label для графа\n if (predicateLabel === 'rdfs:label' || predicateUri === 'http://www.w3.org/2000/01/rdf-schema#label') {\n hierarchy[subjectUri].label = quad.object.value;\n }\n\n // vad:hasParentTrig для графа\n if (predicateLabel === 'vad:hasParentTrig' || predicateUri === 'http://example.org/vad#hasParentTrig') {\n hierarchy[subjectUri].hasParent = quad.object.value;\n }\n }\n });\n\n // Проверяем, что все графы имеют hasParentTrig\n Object.values(hierarchy).forEach(graphInfo => {\n if (!graphInfo.hasParent) {\n const graphLabel = getPrefixedName(graphInfo.uri, prefixes);\n errors.push({\n graph: graphLabel,\n message: `TriG \"${graphLabel}\" не имеет свойства hasParentTrig. В режиме VAD TriG каждый TriG граф должен иметь свойство hasParentTrig.`\n });\n }\n });\n\n if (errors.length > 0) {\n return { valid: false, errors, hierarchy: null, rootTrigUris: [] };\n }\n\n // Строим дерево: находим корневые элементы (hasParentTrig = vad:root)\n const rootUri = 'http://example.org/vad#root';\n const rootTrigUris = [];\n\n Object.values(hierarchy).forEach(graphInfo => {\n const parentUri = graphInfo.hasParent;\n const parentLabel = getPrefixedName(parentUri, prefixes);\n\n // Проверяем, является ли родитель \"root\"\n if (parentUri === rootUri || parentLabel === 'vad:root') {\n // vad:ptree и vad:rtree - это специальные графы для метаданных процессов и исполнителей,\n // они не должны быть включены в rootTrigUris для отображения дерева\n if (graphInfo.uri !== PTREE_GRAPH_URI && graphInfo.uri !== RTREE_GRAPH_URI) {\n rootTrigUris.push(graphInfo.uri);\n }\n } else if (hierarchy[parentUri]) {\n // Добавляем как дочерний элемент к родителю\n hierarchy[parentUri].children.push(graphInfo.uri);\n }\n });\n\n // Собираем информацию о процессах для каждого графа\n // Шаг 1: Собираем все URI процессов из всех графов (включая vad:ptree)\n const allProcessUris = new Set();\n Object.values(hierarchy).forEach(graphInfo => {\n graphInfo.quads.forEach(quad => {\n const predicateUri = quad.predicate.value;\n const predicateLabel = getPrefixedName(predicateUri, prefixes);\n\n // Если это rdf:type и тип = vad:TypeProcess\n if (predicateLabel === 'rdf:type' || predicateUri === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type') {\n const typeUri = quad.object.value;\n const typeLabel = getPrefixedName(typeUri, prefixes);\n if (typeLabel === 'vad:TypeProcess' || typeUri === 'http://example.org/vad#TypeProcess') {\n allProcessUris.add(quad.subject.value);\n }\n }\n });\n });\n\n // Шаг 2: Для каждого графа (кроме ptree) определяем, какие процессы в нём присутствуют\n // Процесс считается присутствующим в графе, если у него есть свойства (vad:hasExecutor, vad:hasNext и т.д.) в этом графе\n const processPredicates = [\n 'http://example.org/vad#hasExecutor',\n 'vad:hasExecutor',\n 'http://example.org/vad#hasNext',\n 'vad:hasNext',\n 'http://example.org/vad#processSubtype',\n 'vad:processSubtype'\n ];\n\n Object.values(hierarchy).forEach(graphInfo => {\n // Для vad:ptree пропускаем присваивание процессов (это граф метаданных)\n if (graphInfo.uri === PTREE_GRAPH_URI) {\n return;\n }\n\n // Ищем процессы, которые имеют свойства в этом графе\n const processesInGraph = new Set();\n graphInfo.quads.forEach(quad => {\n const subjectUri = quad.subject.value;\n const predicateUri = quad.predicate.value;\n const predicateLabel = getPrefixedName(predicateUri, prefixes);\n\n // Если субъект является процессом (из allProcessUris) и предикат - это свойство процесса\n if (allProcessUris.has(subjectUri)) {\n if (processPredicates.includes(predicateUri) || processPredicates.includes(predicateLabel)) {\n processesInGraph.add(subjectUri);\n }\n }\n });\n\n graphInfo.processes = Array.from(processesInGraph);\n });\n\n return { valid: true, errors: [], hierarchy, rootTrigUris };\n }\n\n /**\n * Форматирует ошибки VAD TriG для отображения\n * @param {Array} errors - Массив ошибок\n * @returns {string} - Отформатированное сообщение\n */\n function formatVADTriGErrors(errors) {\n let message = 'ОШИБКА ВАЛИДАЦИИ VAD TriG\\n';\n message += '═══════════════════════════════════════\\n\\n';\n\n errors.forEach((error, index) => {\n message += `Ошибка ${index + 1}:\\n`;\n message += ` TriG граф: ${error.graph}\\n`;\n message += ` ${error.message}\\n\\n`;\n });\n\n message += '═══════════════════════════════════════\\n';\n message += `Всего ошибок: ${errors.length}\\n`;\n message += '\\nВ режиме VAD TriG каждый TriG граф должен иметь свойство vad:hasParentTrig.\\n';\n message += 'Корневой граф должен иметь hasParentTrig = vad:root.\\n';\n message += 'Дочерние графы указывают на родительский граф через hasParentTrig.';\n\n return message;\n }\n\n /**\n * Строит HTML для дерева TriG\n * @param {string} trigUri - URI текущего TriG\n * @param {Object} hierarchy - Иерархия TriG\n * @param {Object} prefixes - Словарь префиксов\n * @param {number} level - Уровень вложенности (для отступов)\n * @returns {string} - HTML дерева\n */\n function buildTriGTreeHtml(trigUri, hierarchy, prefixes, level = 0) {\n const graphInfo = hierarchy[trigUri];\n if (!graphInfo) return '';\n\n const prefixedUri = getPrefixedName(trigUri, prefixes);\n const localName = getLocalName(trigUri);\n const displayLabel = graphInfo.label || localName;\n const isSelected = trigUri === selectedTrigUri;\n const hasChildren = graphInfo.children.length > 0;\n const hasProcesses = graphInfo.processes.length > 0;\n const hasExpandableContent = hasChildren || hasProcesses;\n\n let html = '';\n\n // Элемент дерева\n html += `<div class=\"trig-tree-item ${isSelected ? 'selected active' : ''}\"\n data-trig-uri=\"${escapeHtml(trigUri)}\"\n onclick=\"selectTriG('${escapeHtml(trigUri)}')\">`;\n\n // Значок раскрытия/закрытия\n if (hasExpandableContent) {\n html += `<span class=\"trig-tree-toggle\">▼</span>`;\n } else {\n html += `<span class=\"trig-tree-toggle\"></span>`;\n }\n\n // Метка с id\n html += `<span class=\"trig-tree-label\">${escapeHtml(displayLabel)}</span>`;\n html += `<span class=\"trig-tree-id\">(${escapeHtml(localName)})</span>`;\n html += `</div>`;\n\n // Содержимое дерева (дочерние TriG и состав объектов)\n if (hasExpandableContent) {\n html += `<div class=\"trig-tree-children\">`;\n\n // Сначала показываем дочерние TriG\n graphInfo.children.forEach(childUri => {\n html += buildTriGTreeHtml(childUri, hierarchy, prefixes, level + 1);\n });\n\n // Затем показываем \"Состав объектов\" с процессами\n if (hasProcesses) {\n html += buildObjectCompositionHtml(trigUri, graphInfo.processes, prefixes);\n }\n\n html += `</div>`;\n }\n\n return html;\n }\n\n /**\n * Строит HTML для раздела \"Состав объектов\" с процессами\n * @param {string} trigUri - URI TriG, которому принадлежат процессы\n * @param {Array} processes - Массив URI процессов\n * @param {Object} prefixes - Словарь префиксов\n * @returns {string} - HTML раздела\n */\n function buildObjectCompositionHtml(trigUri, processes, prefixes) {\n const objectCompositionId = `obj-comp-${escapeHtml(trigUri).replace(/[^a-zA-Z0-9]/g, '_')}`;\n\n let html = '';\n\n // Заголовок \"Состав объектов\"\n html += `<div class=\"trig-tree-item object-composition-header\"\n onclick=\"toggleObjectComposition('${objectCompositionId}')\">`;\n html += `<span class=\"trig-tree-toggle object-composition-toggle\" id=\"${objectCompositionId}-toggle\">▶</span>`;\n html += `<span class=\"trig-tree-label object-composition-label\">Состав объектов</span>`;\n html += `<span class=\"trig-tree-id\">(${processes.length})</span>`;\n html += `</div>`;\n\n // Список процессов (скрыт по умолчанию)\n html += `<div class=\"trig-tree-children object-composition-list\" id=\"${objectCompositionId}\" style=\"display: none;\">`;\n\n processes.forEach(processUri => {\n const processLabel = getPrefixedName(processUri, prefixes);\n const processLocalName = getLocalName(processUri);\n\n // Ищем rdfs:label процесса - сначала в текущем TriG, затем в vad:ptree\n let processDisplayName = processLocalName;\n if (trigHierarchy && trigHierarchy[trigUri]) {\n const graphQuads = trigHierarchy[trigUri].quads;\n const labelQuad = graphQuads.find(q =>\n q.subject.value === processUri &&\n (q.predicate.value === 'http://www.w3.org/2000/01/rdf-schema#label' ||\n getPrefixedName(q.predicate.value, prefixes) === 'rdfs:label')\n );\n if (labelQuad) {\n processDisplayName = labelQuad.object.value;\n } else {\n // Fallback: ищем в vad:ptree\n const ptreeMetadata = getProcessMetadataFromPtree(processUri, prefixes);\n if (ptreeMetadata.label) {\n processDisplayName = ptreeMetadata.label;\n }\n }\n }\n\n html += `<div class=\"trig-tree-item process-item\"\n data-process-uri=\"${escapeHtml(processUri)}\"\n data-trig-uri=\"${escapeHtml(trigUri)}\"\n onclick=\"event.stopPropagation(); selectProcess('${escapeHtml(processUri)}', '${escapeHtml(trigUri)}')\">`;\n html += `<span class=\"trig-tree-toggle\"></span>`;\n html += `<span class=\"process-icon\">⚙</span>`;\n html += `<span class=\"trig-tree-label\">${escapeHtml(processDisplayName)}</span>`;\n html += `</div>`;\n });\n\n html += `</div>`;\n\n return html;\n }\n\n /**\n * Переключает видимость списка объектов (Состав объектов)\n * @param {string} listId - ID списка для переключения\n */\n function toggleObjectComposition(listId) {\n const list = document.getElementById(listId);\n const toggle = document.getElementById(listId + '-toggle');\n\n if (list && toggle) {\n if (list.style.display === 'none') {\n list.style.display = 'block';\n toggle.textContent = '▼';\n } else {\n list.style.display = 'none';\n toggle.textContent = '▶';\n }\n }\n }\n\n /**\n * Обработчик выбора процесса в дереве\n * @param {string} processUri - URI выбранного процесса\n * @param {string} trigUri - URI TriG, содержащего процесс\n */\n function selectProcess(processUri, trigUri) {\n // Если выбранный процесс из другого TriG, сначала переключаемся на этот TriG\n if (selectedTrigUri !== trigUri) {\n selectTriG(trigUri);\n }\n\n // Подсвечиваем процесс на диаграмме\n highlightProcessOnDiagram(processUri);\n }\n\n /**\n * Подсвечивает процесс на диаграмме\n * @param {string} processUri - URI процесса для подсветки\n */\n function highlightProcessOnDiagram(processUri) {\n // Снимаем предыдущее выделение\n const previouslySelected = document.querySelectorAll('#vad-trig-output .node.process-highlighted');\n previouslySelected.forEach(node => {\n node.classList.remove('process-highlighted');\n });\n\n // Находим узел процесса по его ID (используем nodeLabelToUri для поиска)\n const prefixedUri = getPrefixedName(processUri, currentPrefixes);\n const nodeId = generateVadNodeId(processUri, currentPrefixes);\n\n // Ищем узел SVG по ID или по тексту\n const output = document.getElementById('vad-trig-output');\n if (!output) return;\n\n const nodes = output.querySelectorAll('.node');\n nodes.forEach(node => {\n const titleElement = node.querySelector('title');\n if (titleElement) {\n const nodeTitle = titleElement.textContent.trim();\n // Проверяем, совпадает ли ID узла или его метка с URI процесса\n if (nodeTitle === nodeId || nodeLabelToUri[nodeTitle] === processUri) {\n node.classList.add('process-highlighted');\n // Прокручиваем к выбранному элементу\n node.scrollIntoView({ behavior: 'smooth', block: 'center' });\n }\n }\n });\n\n // Также выделяем элемент в дереве\n const processItems = document.querySelectorAll('.process-item');\n processItems.forEach(item => {\n if (item.getAttribute('data-process-uri') === processUri) {\n item.classList.add('process-selected');\n } else {\n item.classList.remove('process-selected');\n }\n });\n }\n\n /**\n * Отображает дерево TriG\n * @param {Object} hierarchy - Иерархия TriG\n * @param {string} rootUri - URI корневого TriG\n * @param {Object} prefixes - Словарь префиксов\n */\n function displayTriGTree(hierarchy, rootUris, prefixes) {\n const treeContent = document.getElementById('trig-tree-content');\n if (!treeContent) return;\n\n if (!rootUris || rootUris.length === 0) {\n treeContent.innerHTML = '<div class=\"trig-properties-empty\">Нет доступных TriG графов</div>';\n return;\n }\n\n let html = '';\n rootUris.forEach(rootUri => {\n if (hierarchy[rootUri]) {\n html += buildTriGTreeHtml(rootUri, hierarchy, prefixes, 0);\n }\n });\n treeContent.innerHTML = html;\n }\n\n /**\n * Отображает свойства выбранного TriG\n * @param {string} trigUri - URI TriG\n * @param {Object} hierarchy - Иерархия TriG\n * @param {Object} prefixes - Словарь префиксов\n */\n function displayTriGProperties(trigUri, hierarchy, prefixes) {\n const propertiesContent = document.getElementById('trig-properties-content');\n if (!propertiesContent) return;\n\n const graphInfo = hierarchy[trigUri];\n if (!graphInfo) {\n propertiesContent.innerHTML = '<div class=\"trig-properties-empty\">Выберите TriG в дереве</div>';\n return;\n }\n\n let html = '';\n\n // Основные свойства TriG\n const prefixedUri = getPrefixedName(trigUri, prefixes);\n\n // URI with copy button\n html += `<div class=\"trig-property-item\">`;\n html += `<div class=\"trig-property-predicate\">URI</div>`;\n html += `<div class=\"trig-property-value-container\">`;\n html += `<div class=\"trig-property-value uri\">${escapeHtml(prefixedUri)}</div>`;\n html += `<button class=\"copy-id-btn\" onclick=\"copyObjectId('${escapeHtml(trigUri)}', this)\">Копировать</button>`;\n html += `</div>`;\n html += `</div>`;\n\n // Label\n if (graphInfo.label) {\n html += `<div class=\"trig-property-item\">`;\n html += `<div class=\"trig-property-predicate\">rdfs:label</div>`;\n html += `<div class=\"trig-property-value literal\">\"${escapeHtml(graphInfo.label)}\"</div>`;\n html += `</div>`;\n }\n\n // hasParent\n if (graphInfo.hasParent) {\n const parentLabel = getPrefixedName(graphInfo.hasParent, prefixes);\n html += `<div class=\"trig-property-item\">`;\n html += `<div class=\"trig-property-predicate\">vad:hasParentTrig</div>`;\n html += `<div class=\"trig-property-value uri\">${escapeHtml(parentLabel)}</div>`;\n html += `</div>`;\n }\n\n // Количество процессов\n html += `<div class=\"trig-property-item\">`;\n html += `<div class=\"trig-property-predicate\">Процессы (vad:TypeProcess)</div>`;\n html += `<div class=\"trig-property-value\">${graphInfo.processes.length} шт.</div>`;\n html += `</div>`;\n\n // Количество дочерних TriG\n if (graphInfo.children.length > 0) {\n html += `<div class=\"trig-property-item\">`;\n html += `<div class=\"trig-property-predicate\">Дочерние TriG</div>`;\n html += `<div class=\"trig-property-value\">${graphInfo.children.length} шт.</div>`;\n html += `</div>`;\n }\n\n // Количество триплетов\n html += `<div class=\"trig-property-item\">`;\n html += `<div class=\"trig-property-predicate\">Триплеты</div>`;\n html += `<div class=\"trig-property-value\">${graphInfo.quads.length} шт.</div>`;\n html += `</div>`;\n\n propertiesContent.innerHTML = html;\n }\n\n /**\n * Обработчик выбора TriG в дереве\n * @param {string} trigUri - URI выбранного TriG\n */\n function selectTriG(trigUri) {\n selectedTrigUri = trigUri;\n\n // Обновляем выделение в дереве\n const treeItems = document.querySelectorAll('.trig-tree-item');\n treeItems.forEach(item => {\n if (item.getAttribute('data-trig-uri') === trigUri) {\n item.classList.add('selected', 'active');\n } else {\n item.classList.remove('selected', 'active');\n }\n });\n\n // Отображаем свойства выбранного TriG\n displayTriGProperties(trigUri, trigHierarchy, currentPrefixes);\n\n // Перевизуализируем граф для выбранного TriG\n revisualizeTrigVAD(trigUri);\n\n // Обновляем SPARQL запрос для выбранного TriG\n updateSparqlQueryForTriG();\n }\n\n /**\n * Перевизуализирует VAD для выбранного TriG\n * @param {string} trigUri - URI TriG для отображения\n */\n async function revisualizeTrigVAD(trigUri) {\n const graphInfo = trigHierarchy[trigUri];\n if (!graphInfo) return;\n\n const layoutEngine = document.getElementById('layout-engine').value;\n\n try {\n // Используем квады только из выбранного графа\n const filteredQuads = graphInfo.quads.filter(quad => {\n const predicateUri = quad.predicate.value;\n const predicateLabel = getPrefixedName(predicateUri, currentPrefixes);\n return !isPredicateHidden(predicateUri, predicateLabel);\n });\n\n // Генерируем DOT-код в режиме VAD\n const originalMode = currentMode;\n currentMode = 'vad'; // Используем логику VAD для рендеринга\n\n // Временно заменяем currentQuads на квады выбранного графа\n const originalQuads = currentQuads;\n currentQuads = graphInfo.quads;\n\n const dotCode = rdfToDot(filteredQuads, currentPrefixes);\n currentDotCode = dotCode;\n console.log('VAD TriG - Сгенерированный DOT-код:', dotCode);\n\n // Восстанавливаем\n currentQuads = originalQuads;\n currentMode = originalMode;\n\n const viz = await Viz.instance();\n const svgString = viz.renderString(dotCode, { format: 'svg', engine: layoutEngine });\n\n // В режиме VAD TriG используем специальный контейнер vad-trig-output\n const output = document.getElementById('vad-trig-output');\n output.innerHTML = svgString;\n currentSvgElement = output.querySelector('svg');\n document.getElementById('vad-trig-zoom-controls').style.display = 'flex';\n addNodeClickHandlers();\n\n } catch (error) {\n console.error('Ошибка при перевизуализации VAD TriG:', error);\n }\n }\n\n /**\n * Показывает или скрывает панели VAD TriG режима\n * @param {boolean} show - Показать или скрыть\n */\n function toggleVADTriGPanels(show) {\n const vadTrigContainer = document.getElementById('vad-trig-container');\n const regularZoomContainer = document.getElementById('zoom-container');\n const regularZoomControls = document.getElementById('zoom-controls');\n const regularOutput = document.getElementById('output');\n\n if (vadTrigContainer) {\n vadTrigContainer.style.display = show ? 'flex' : 'none';\n }\n\n // Hide regular zoom container and clear output when showing VAD TriG panels\n if (regularZoomContainer) {\n regularZoomContainer.style.display = show ? 'none' : 'block';\n }\n if (regularZoomControls && show) {\n regularZoomControls.style.display = 'none';\n }\n if (regularOutput && show) {\n regularOutput.innerHTML = '';\n }\n }\n\n // ============================================================================\n // ФУНКЦИИ РАБОТЫ СО СТИЛЯМИ\n // ============================================================================\n\n const BaseStyles = {\n literal: 'shape=\"box\" style=\"filled\" fillcolor=\"#ffffcc\"',\n blankNode: 'shape=\"ellipse\" style=\"filled\" fillcolor=\"#e0e0e0\"',\n uri: 'shape=\"ellipse\" style=\"filled\" fillcolor=\"#cce5ff\"',\n edge: ''\n };\n\n function getNodeStyle(nodeUri, isLiteral, isBlankNode) {\n if (currentMode === 'base') {\n if (isLiteral) return BaseStyles.literal;\n if (isBlankNode) return BaseStyles.blankNode;\n return BaseStyles.uri;\n }\n\n if (currentMode === 'aggregation') {\n if (isBlankNode) return AggregationNodeStyles['BlankNodeStyle'].dot;\n const nodeTypes = nodeTypesCache[nodeUri] || [];\n for (const [styleName, styleConfig] of Object.entries(AggregationNodeStyles)) {\n if (styleName === 'default') continue;\n for (const type of styleConfig.types) {\n if (type.startsWith('_')) continue;\n if (nodeTypes.includes(type)) return styleConfig.dot;\n }\n }\n return AggregationNodeStyles['default'].dot;\n }\n\n if (currentMode === 'vad' || currentMode === 'vad-trig') {\n if (isBlankNode) return VADNodeStyles['default'].dot;\n const nodeTypes = nodeTypesCache[nodeUri] || [];\n const nodeSubtypes = nodeSubtypesCache[nodeUri] || [];\n\n // First, check styles that have subtypes defined (ProcessStyleBasic, ProcessStyleDetailed)\n for (const [styleName, styleConfig] of Object.entries(VADNodeStyles)) {\n if (styleName === 'default') continue;\n if (!styleConfig.subtypes) continue; // Skip styles without subtypes\n\n // Check if node has matching type\n const hasMatchingType = styleConfig.types.some(type => nodeTypes.includes(type));\n if (!hasMatchingType) continue;\n\n // Check if node has matching subtype\n const hasMatchingSubtype = styleConfig.subtypes.some(subtype => nodeSubtypes.includes(subtype));\n if (hasMatchingSubtype) return styleConfig.dot;\n }\n\n // Then, check styles without subtypes (ExecutorGroupStyle, ExecutorStyle, etc.)\n for (const [styleName, styleConfig] of Object.entries(VADNodeStyles)) {\n if (styleName === 'default') continue;\n if (styleConfig.subtypes) continue; // Skip styles with subtypes (already checked)\n\n for (const type of styleConfig.types) {\n if (nodeTypes.includes(type)) return styleConfig.dot;\n }\n }\n\n // For Process nodes without explicit subtype, default to ProcessStyleBasic\n const isProcess = nodeTypes.some(t =>\n t === 'vad:TypeProcess' || t === 'http://example.org/vad#TypeProcess'\n );\n if (isProcess) {\n return VADNodeStyles['ProcessStyleBasic'].dot;\n }\n\n return VADNodeStyles['default'].dot;\n }\n\n // Режим нотации\n if (isLiteral) return StyleName.nodeStyles['LiteralStyle'].dot;\n if (isBlankNode) return StyleName.nodeStyles['BlankNodeStyle'].dot;\n\n const nodeTypes = nodeTypesCache[nodeUri] || [];\n for (const [styleName, styleConfig] of Object.entries(StyleName.nodeStyles)) {\n if (styleName === 'default') continue;\n for (const type of styleConfig.types) {\n if (type.startsWith('_')) continue;\n if (nodeTypes.includes(type)) return styleConfig.dot;\n }\n }\n return StyleName.nodeStyles['default'].dot;\n }\n\n function getEdgeStyle(predicateUri, predicateLabel) {\n if (currentMode === 'base') return BaseStyles.edge;\n\n if (currentMode === 'vad') {\n for (const [styleName, styleConfig] of Object.entries(VADEdgeStyles)) {\n if (styleName === 'default') continue;\n for (const predicate of styleConfig.predicates) {\n if (predicateUri === predicate || predicateLabel === predicate) {\n return styleConfig.dot;\n }\n }\n }\n return VADEdgeStyles['default'].dot;\n }\n\n // Режим нотации или агрегации\n for (const [styleName, styleConfig] of Object.entries(StyleName.edgeStyles)) {\n if (styleName === 'default') continue;\n for (const predicate of styleConfig.predicates) {\n if (predicateUri === predicate || predicateLabel === predicate) {\n return styleConfig.dot;\n }\n }\n }\n return StyleName.edgeStyles['default'].dot;\n }\n\n function buildNodeTypesCache(quads, prefixes) {\n nodeTypesCache = {};\n nodeSubtypesCache = {};\n const typePredicates = [\n 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',\n 'rdf:type',\n 'a'\n ];\n const subtypePredicates = [\n 'http://example.org/vad#processSubtype',\n 'vad:processSubtype'\n ];\n\n // Функция для обработки квадов и наполнения кешей\n function processQuads(quadsToProcess) {\n quadsToProcess.forEach(quad => {\n const predicateValue = quad.predicate.value;\n const predicateLabel = getPrefixedName(predicateValue, prefixes);\n\n // Build types cache\n if (typePredicates.includes(predicateValue) ||\n typePredicates.includes(predicateLabel) ||\n predicateLabel === 'a') {\n\n const subjectUri = quad.subject.value;\n const typeUri = quad.object.value;\n const typeLabel = getPrefixedName(typeUri, prefixes);\n\n if (!nodeTypesCache[subjectUri]) {\n nodeTypesCache[subjectUri] = [];\n }\n\n if (!nodeTypesCache[subjectUri].includes(typeUri)) {\n nodeTypesCache[subjectUri].push(typeUri);\n }\n if (!nodeTypesCache[subjectUri].includes(typeLabel)) {\n nodeTypesCache[subjectUri].push(typeLabel);\n }\n }\n\n // Build subtypes cache for vad:processSubtyp
[2026-01-26T19:23:56.110Z] [INFO] e\n if (subtypePredicates.includes(predicateValue) ||\n subtypePredicates.includes(predicateLabel)) {\n\n const subjectUri = quad.subject.value;\n const subtypeUri = quad.object.value;\n const subtypeLabel = getPrefixedName(subtypeUri, prefixes);\n\n if (!nodeSubtypesCache[subjectUri]) {\n nodeSubtypesCache[subjectUri] = [];\n }\n\n if (!nodeSubtypesCache[subjectUri].includes(subtypeUri)) {\n nodeSubtypesCache[subjectUri].push(subtypeUri);\n }\n if (!nodeSubtypesCache[subjectUri].includes(subtypeLabel)) {\n nodeSubtypesCache[subjectUri].push(subtypeLabel);\n }\n }\n });\n }\n\n // Обрабатываем переданные квады\n processQuads(quads);\n\n // Дополнительно: в режиме VAD TriG также включаем типы из vad:ptree\n // чтобы rdf:type vad:TypeProcess были доступны для всех TriG графов\n if (trigHierarchy && trigHierarchy[PTREE_GRAPH_URI]) {\n const ptreeQuads = trigHierarchy[PTREE_GRAPH_URI].quads;\n processQuads(ptreeQuads);\n }\n }\n\n // ============================================================================\n // ФУНКЦИИ МАСШТАБИРОВАНИЯ\n // ============================================================================\n\n function applyZoom() {\n // Применяем масштаб к обоим контейнерам (обычный и VAD TriG)\n const zoomContent = document.getElementById('zoom-content');\n const zoomLevel = document.getElementById('zoom-level');\n const vadTrigZoomContent = document.getElementById('vad-trig-zoom-content');\n const vadTrigZoomLevel = document.getElementById('vad-trig-zoom-level');\n\n if (zoomContent) zoomContent.style.transform = `scale(${currentScale})`;\n if (zoomLevel) zoomLevel.textContent = Math.round(currentScale * 100) + '%';\n if (vadTrigZoomContent) vadTrigZoomContent.style.transform = `scale(${currentScale})`;\n if (vadTrigZoomLevel) vadTrigZoomLevel.textContent = Math.round(currentScale * 100) + '%';\n }\n\n function zoomIn() {\n if (currentScale < 3.0) { currentScale += 0.1; applyZoom(); }\n }\n\n function zoomOut() {\n if (currentScale > 0.1) { currentScale -= 0.1; applyZoom(); }\n }\n\n function zoomReset() {\n currentScale = 1.0;\n applyZoom();\n }\n\n function zoomFit() {\n // Определяем, какой контейнер сейчас активен\n const vadTrigContainer = document.getElementById('vad-trig-container');\n const isVadTrigMode = vadTrigContainer && vadTrigContainer.style.display !== 'none';\n\n let zoomContainer, output;\n if (isVadTrigMode) {\n zoomContainer = document.getElementById('vad-trig-zoom-container');\n output = document.getElementById('vad-trig-output');\n } else {\n zoomContainer = document.getElementById('zoom-container');\n output = document.getElementById('output');\n }\n\n const svg = output ? output.querySelector('svg') : null;\n if (!zoomContainer || !svg) return;\n\n const containerWidth = zoomContainer.clientWidth - 20;\n const containerHeight = zoomContainer.clientHeight - 20;\n\n let svgWidth = parseFloat(svg.getAttribute('width')) || svg.getBoundingClientRect().width;\n let svgHeight = parseFloat(svg.getAttribute('height')) || svg.getBoundingClientRect().height;\n\n const widthStr = svg.getAttribute('width') || '';\n const heightStr = svg.getAttribute('height') || '';\n if (widthStr.includes('pt')) svgWidth = parseFloat(widthStr) * 1.33;\n if (heightStr.includes('pt')) svgHeight = parseFloat(heightStr) * 1.33;\n\n const scaleX = containerWidth / svgWidth;\n const scaleY = containerHeight / svgHeight;\n currentScale = Math.min(scaleX, scaleY, 1.0);\n applyZoom();\n }\n\n // ============================================================================\n // ФУНКЦИИ ПАНЕЛИ СВОЙСТВ УЗЛА\n // ============================================================================\n\n function closePropertiesPanel(panelId) {\n const panel = document.getElementById(panelId);\n if (panel) {\n panel.remove();\n openPropertiesPanels = openPropertiesPanels.filter(p => p.id !== panelId);\n }\n if (selectedNodeElement) {\n selectedNodeElement.classList.remove('selected');\n selectedNodeElement = null;\n }\n }\n\n function closeAllPropertiesPanels() {\n const container = document.getElementById('properties-panels-container');\n if (container) container.innerHTML = '';\n openPropertiesPanels = [];\n if (selectedNodeElement) {\n selectedNodeElement.classList.remove('selected');\n selectedNodeElement = null;\n }\n }\n\n function getNodeProperties(nodeUri) {\n const properties = [];\n currentQuads.forEach(quad => {\n if (quad.subject.value === nodeUri) {\n const predicateLabel = getPrefixedName(quad.predicate.value, currentPrefixes);\n const isLiteral = quad.object.termType === 'Literal';\n const objectLabel = isLiteral\n ? `\"${quad.object.value}\"`\n : getPrefixedName(quad.object.value, currentPrefixes);\n\n properties.push({\n predicate: quad.predicate.value,\n predicateLabel: predicateLabel,\n object: quad.object.value,\n objectLabel: objectLabel,\n isLiteral: isLiteral\n });\n }\n });\n return properties;\n }\n\n function showNodeProperties(nodeUri, nodeLabel) {\n const container = document.getElementById('properties-panels-container');\n if (!container) return;\n\n const existingPanel = openPropertiesPanels.find(p => p.uri === nodeUri);\n if (existingPanel) {\n const panel = document.getElementById(existingPanel.id);\n if (panel) bringPanelToFront(panel);\n return;\n }\n\n propertiesPanelCounter++;\n const panelId = 'properties-panel-' + propertiesPanelCounter;\n\n const offsetMultiplier = openPropertiesPanels.length % 5;\n const rightOffset = 20 + (offsetMultiplier * 30);\n const topOffset = 100 + (offsetMultiplier * 30);\n\n const properties = getNodeProperties(nodeUri);\n\n let propertiesHtml = '';\n if (properties.length === 0) {\n propertiesHtml = '<div class=\"properties-empty\">У этого узла нет свойств</div>';\n } else {\n properties.forEach(prop => {\n propertiesHtml += '<div class=\"property-item\">';\n propertiesHtml += `<div class=\"property-predicate\">${prop.predicateLabel}</div>`;\n propertiesHtml += `<div class=\"property-value ${prop.isLiteral ? 'literal' : 'uri'}\">${prop.objectLabel}</div>`;\n propertiesHtml += '</div>';\n });\n }\n\n const nodeTypes = nodeTypesCache[nodeUri] || [];\n if (nodeTypes.length > 0) {\n const prefixedTypes = nodeTypes.filter(t => t.includes(':') && !t.startsWith('http'));\n if (prefixedTypes.length > 0) {\n propertiesHtml += '<div style=\"margin-top: 15px; padding-top: 10px; border-top: 1px solid #ddd;\">';\n propertiesHtml += '<div style=\"font-size: 12px; color: #666; margin-bottom: 5px;\">Тип узла:</div>';\n prefixedTypes.forEach(type => {\n propertiesHtml += `<span class=\"properties-type-badge\">${type}</span> `;\n });\n propertiesHtml += '</div>';\n }\n }\n\n // Добавляем вычисленные свойства из virtualRDFdata (с разделителем)\n const computedProperties = getComputedPropertiesForNode(nodeUri);\n if (computedProperties.length > 0) {\n propertiesHtml += '<div style=\"margin-top: 15px; padding-top: 10px; border-top: 2px dashed #9C27B0;\">';\n propertiesHtml += '<div style=\"font-size: 12px; color: #9C27B0; margin-bottom: 8px; font-weight: bold;\">Вычисляемые свойства (virtualRDFdata):</div>';\n computedProperties.forEach(prop => {\n propertiesHtml += '<div class=\"property-item\" style=\"background-color: #F3E5F5; border-left: 3px solid #9C27B0;\">';\n propertiesHtml += `<div class=\"property-predicate\" style=\"color: #7B1FA2;\">${prop.predicateLabel}</div>`;\n propertiesHtml += `<div class=\"property-value uri\" style=\"color: #4A148C;\">${prop.valueLabel}</div>`;\n propertiesHtml += '</div>';\n });\n propertiesHtml += '</div>';\n }\n\n const escapedNodeLabel = nodeLabel.replace(/'/g, \"\\\\'\").replace(/\"/g, '&quot;');\n const panelHtml = `\n <div class=\"properties-panel visible\" id=\"${panelId}\" style=\"right: ${rightOffset}px; top: ${topOffset}px;\">\n <div class=\"properties-header\" onmousedown=\"startDragPanel(event, '${panelId}')\">\n <div class=\"properties-header-content\">\n <div class=\"properties-header-title\">Свойства объекта</div>\n <div class=\"properties-header-row\">\n <h3>${nodeLabel}</h3>\n <button class=\"properties-copy-btn\" onclick=\"event.stopPropagation(); copyObjectId('${escapedNodeLabel}', this)\">Копировать</button>\n </div>\n </div>\n <button class=\"properties-close-btn\" onclick=\"closePropertiesPanel('${panelId}')\">&times;</button>\n </div>\n <div class=\"properties-content\">\n ${propertiesHtml}\n </div>\n </div>\n `;\n\n container.insertAdjacentHTML('beforeend', panelHtml);\n openPropertiesPanels.push({ id: panelId, uri: nodeUri, label: nodeLabel });\n\n const newPanel = document.getElementById(panelId);\n if (newPanel) bringPanelToFront(newPanel);\n }\n\n function bringPanelToFront(panel) {\n let maxZIndex = 1000;\n openPropertiesPanels.forEach(p => {\n const el = document.getElementById(p.id);\n if (el) {\n const z = parseInt(el.style.zIndex) || 1000;\n if (z > maxZIndex) maxZIndex = z;\n }\n });\n panel.style.zIndex = maxZIndex + 1;\n }\n\n function startDragPanel(event, panelId) {\n if (event.target.classList.contains('properties-close-btn')) return;\n const panel = document.getElementById(panelId);\n if (!panel) return;\n\n draggedPanel = panel;\n const rect = panel.getBoundingClientRect();\n dragOffsetX = event.clientX - rect.left;\n dragOffsetY = event.clientY - rect.top;\n\n bringPanelToFront(panel);\n document.addEventListener('mousemove', dragPanel);\n document.addEventListener('mouseup', stopDragPanel);\n event.preventDefault();\n }\n\n function dragPanel(event) {\n if (!draggedPanel) return;\n const newLeft = event.clientX - dragOffsetX;\n const newTop = event.clientY - dragOffsetY;\n draggedPanel.style.left = newLeft + 'px';\n draggedPanel.style.top = newTop + 'px';\n draggedPanel.style.right = 'auto';\n }\n\n function stopDragPanel() {\n draggedPanel = null;\n document.removeEventListener('mousemove', dragPanel);\n document.removeEventListener('mouseup', stopDragPanel);\n }\n\n // ============================================================================\n // ФУНКЦИИ ДЛЯ КЛИКОВ ПО УЗЛАМ\n // ============================================================================\n\n function addNodeClickHandlers() {\n // Add click handlers to both regular output and VAD TriG output\n const regularSvg = document.querySelector('#output svg');\n const vadTrigSvg = document.querySelector('#vad-trig-output svg');\n\n const svgElements = [regularSvg, vadTrigSvg].filter(svg => svg !== null);\n\n svgElements.forEach(svg => {\n const nodes = svg.querySelectorAll('.node');\n nodes.forEach(node => {\n node.addEventListener('click', handleNodeClick);\n node.addEventListener('dblclick', handleNodeDoubleClick);\n });\n });\n }\n\n function handleNodeClick(event) {\n const nodeElement = event.currentTarget;\n const titleElement = nodeElement.querySelector('title');\n if (!titleElement) return;\n\n const dotId = titleElement.textContent;\n\n let nodeUri = null;\n let nodeLabel = null;\n\n for (const [label, info] of Object.entries(nodeLabelToUri)) {\n if (info.dotId === dotId) {\n nodeUri = info.uri;\n nodeLabel = label;\n break;\n }\n }\n\n if (!nodeUri) return;\n\n // НЕ снимаем выделение с элемента TriG-дерева при клике на узел диаграммы.\n // Выделенный элемент TriG дерева всегда должен отражать, чья схема отображается на диаграмме.\n // Снимаем только выделение с процессов в списке \"Состав объектов\" и подсветку на диаграмме.\n const processItems = document.querySelectorAll('.process-item.process-selected');\n processItems.forEach(item => {\n item.classList.remove('process-selected');\n });\n const highlightedProcesses = document.querySelectorAll('.node.process-highlighted');\n highlightedProcesses.forEach(node => {\n node.classList.remove('process-highlighted');\n });\n\n if (selectedNodeElement) {\n selectedNodeElement.classList.remove('selected');\n }\n nodeElement.classList.add('selected');\n selectedNodeElement = nodeElement;\n\n showNodeProperties(nodeUri, nodeLabel);\n }\n\n /**\n * Обработчик двойного клика по узлу диаграммы\n * Для процессов с подтипом \"Детализированный\" (vad:Detailed) открывает соответствующую схему TriG\n * @param {Event} event - Событие двойного клика\n */\n function handleNodeDoubleClick(event) {\n event.preventDefault();\n event.stopPropagation();\n\n const nodeElement = event.currentTarget;\n const titleElement = nodeElement.querySelector('title');\n if (!titleElement) return;\n\n const dotId = titleElement.textContent;\n\n let nodeUri = null;\n let nodeLabel = null;\n\n for (const [label, info] of Object.entries(nodeLabelToUri)) {\n if (info.dotId === dotId) {\n nodeUri = info.uri;\n nodeLabel = label;\n break;\n }\n }\n\n if (!nodeUri) return;\n\n // Ищем свойство vad:hasTrig для данного узла\n const hasTrigPredicate = 'http://example.org/vad#hasTrig';\n let targetTrigUri = null;\n\n // Сначала проверяем в vad:ptree (там хранятся метаданные процессов, включая hasTrig)\n if (trigHierarchy && trigHierarchy[PTREE_GRAPH_URI]) {\n const ptreeQuads = trigHierarchy[PTREE_GRAPH_URI].quads;\n for (const quad of ptreeQuads) {\n if (quad.subject.value === nodeUri && quad.predicate.value === hasTrigPredicate) {\n targetTrigUri = quad.object.value;\n break;\n }\n }\n }\n\n // Если не найдено в ptree, проверяем в текущем графе или во всех графах\n if (!targetTrigUri) {\n const quadsToCheck = selectedTrigUri && trigHierarchy[selectedTrigUri]\n ? trigHierarchy[selectedTrigUri].quads\n : currentQuads;\n\n for (const quad of quadsToCheck) {\n if (quad.subject.value === nodeUri && quad.predicate.value === hasTrigPredicate) {\n targetTrigUri = quad.object.value;\n break;\n }\n }\n }\n\n // Если найден связанный TriG, открываем его\n if (targetTrigUri && trigHierarchy[targetTrigUri]) {\n selectTriG(targetTrigUri);\n }\n }\n\n /**\n * Снимает выделение со всех элементов TriG-дерева\n */\n function clearTriGTreeSelection() {\n // Снимаем выделение с TriG-элементов дерева\n const treeItems = document.querySelectorAll('.trig-tree-item');\n treeItems.forEach(item => {\n item.classList.remove('selected', 'active');\n });\n\n // Снимаем выделение с процессов в дереве\n const processItems = document.querySelectorAll('.process-item.process-selected');\n processItems.forEach(item => {\n item.classList.remove('process-selected');\n });\n\n // Снимаем подсветку процесса на диаграмме\n const highlightedProcesses = document.querySelectorAll('.node.process-highlighted');\n highlightedProcesses.forEach(node => {\n node.classList.remove('process-highlighted');\n });\n }\n\n // ============================================================================\n // ФУНКЦИИ ФИЛЬТРОВ\n // ============================================================================\n\n function isPredicateHidden(predicateUri, predicateLabel) {\n return activeFilters.includes(predicateUri) || activeFilters.includes(predicateLabel);\n }\n\n function displayFilters() {\n // filter-panel removed in minimization - function kept for compatibility\n return;\n\n allPredicates = [];\n const predicateSet = new Set();\n\n currentQuads.forEach(quad => {\n const predicateUri = quad.predicate.value;\n const predicateLabel = getPrefixedName(predicateUri, currentPrefixes);\n if (!predicateSet.has(predicateLabel)) {\n predicateSet.add(predicateLabel);\n allPredicates.push({ uri: predicateUri, label: predicateLabel });\n }\n });\n\n if (allPredicates.length === 0) {\n filterPanel.style.display = 'none';\n return;\n }\n\n allPredicates.sort((a, b) => a.label.localeCompare(b.label));\n\n let html = '';\n allPredicates.forEach(pred => {\n const isHidden = isPredicateHidden(pred.uri, pred.label);\n const checkboxId = 'filter-' + pred.label.replace(/[^a-zA-Z0-9]/g, '_');\n\n html += `<div class=\"filter-item\">`;\n html += `<input type=\"checkbox\" id=\"${checkboxId}\" ${!isHidden ? 'checked' : ''} onchange=\"togglePredicateFilter('${pred.uri}', '${pred.label}', this.checked)\">`;\n html += `<label for=\"${checkboxId}\">${pred.label}</label>`;\n html += `</div>`;\n });\n\n filterContent.innerHTML = html;\n filterPanel.style.display = 'block';\n }\n\n function togglePredicateFilter(predicateUri, predicateLabel, isVisible) {\n if (isVisible) {\n activeFilters = activeFilters.filter(f => f !== predicateUri && f !== predicateLabel);\n } else {\n if (!activeFilters.includes(predicateUri)) {\n activeFilters.push(predicateUri);\n }\n if (!activeFilters.includes(predicateLabel)) {\n activeFilters.push(predicateLabel);\n }\n }\n revisualize();\n }\n\n function selectAllFilters() {\n activeFilters = [];\n displayFilters();\n revisualize();\n }\n\n function deselectAllFilters() {\n activeFilters = allPredicates.flatMap(p => [p.uri, p.label]);\n displayFilters();\n revisualize();\n }\n\n async function revisualize() {\n const layoutEngine = document.getElementById('layout-engine').value;\n\n try {\n const filteredQuads = currentQuads.filter(quad => {\n const predicateUri = quad.predicate.value;\n const predicateLabel = getPrefixedName(predicateUri, currentPrefixes);\n return !isPredicateHidden(predicateUri, predicateLabel);\n });\n\n const dotCode = rdfToDot(filteredQuads, currentPrefixes);\n currentDotCode = dotCode;\n console.log('Пересгенерированный DOT-код:', dotCode);\n\n const viz = await Viz.instance();\n const svgString = viz.renderString(dotCode, { format: 'svg', engine: layoutEngine });\n\n const output = document.getElementById('output');\n output.innerHTML = svgString;\n currentSvgElement = output.querySelector('svg');\n addNodeClickHandlers();\n\n } catch (error) {\n console.error('Ошибка при перевизуализации:', error);\n }\n }\n\n // ============================================================================\n // ФУНКЦИИ ЗАГРУЗКИ ПРИМЕРОВ\n // ============================================================================\n\n /**\n * Универсальная функция загрузки примера из файла.\n * Пытается загрузить файл через fetch. При ошибке CORS (локальный файл)\n * показывает информационное сообщение и использует встроенные данные как fallback.\n * @param {string} filename - Имя файла для загрузки\n * @param {string} exampleName - Человекочитаемое имя примера\n * @param {string} inputFormat - Формат ввода (turtle, n-triples, n-quads, trig)\n * @param {string} visualizationMode - Режим визуализации (notation, vad, vad-trig)\n * @param {string} fallbackDataKey - Ключ для доступа к встроенным данным EXAMPLE_DATA\n */\n async function loadExampleFromFile(filename, exampleName, inputFormat, visualizationMode, fallbackDataKey) {\n const statusEl = document.getElementById('example-status');\n\n statusEl.textContent = `Загрузка примера ${exampleName}...`;\n statusEl.style.display = 'block';\n statusEl.style.backgroundColor = '#fff3cd';\n statusEl.style.borderColor = '#ffc107';\n statusEl.style.color = '#856404';\n\n try {\n const response = await fetch(filename);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n const data = await response.text();\n\n document.getElementById('rdf-input').value = data;\n document.getElementById('input-format').value = inputFormat;\n document.getElementById('visualization-mode').value = visualizationMode;\n updateModeDescription();\n\n statusEl.textContent = `Пример ${exampleName} успешно загружен из файла ${filename}`;\n statusEl.style.backgroundColor = '#d4edda';\n statusEl.style.borderColor = '#c3e6cb';\n statusEl.style.color = '#155724';\n } catch (error) {\n console.warn(`Не удалось загрузить файл ${filename}:`, error.message);\n\n // Проверяем, это ошибка CORS (локальный файл) или сетевая ошибка\n const isCorsError = error.message.includes('Failed to fetch') ||\n error.message.includes('NetworkError') ||\n error.message.includes('CORS');\n\n if (isCorsError) {\n // Для локальных файлов показываем информационное сообщение и используем встроенные данные\n statusEl.textContent = `Файл ${filename} недоступен (CORS). Используются встроенные данные.`;\n statusEl.style.backgroundColor = '#fff3cd';\n statusEl.style.borderColor = '#ffc107';\n statusEl.style.color = '#856404';\n } else {\n // Для серверных ошибок показываем ошибку, но всё равно используем fallback\n statusEl.textContent = `Ошибка загрузки ${filename}: ${error.message}. Используются встроенные данные.`;\n statusEl.style.backgroundColor = '#fff3cd';\n statusEl.style.borderColor = '#ffc107';\n statusEl.style.color = '#856404';\n }\n\n // Используем встроенные данные как fallback\n try {\n document.getElementById('rdf-input').value = EXAMPLE_DATA[fallbackDataKey];\n document.getElementById('input-format').value = inputFormat;\n document.getElementById('visualization-mode').value = visualizationMode;\n updateModeDescription();\n } catch (fallbackError) {\n console.error(`Ошибка при использовании встроенных данных:`, fallbackError);\n statusEl.textContent = `Ошибка загрузки примера ${exampleName}: ${fallbackError.message}`;\n statusEl.style.backgroundColor = '#f8d7da';\n statusEl.style.borderColor = '#f5c6cb';\n statusEl.style.color = '#721c24';\n }\n }\n }\n\n /**\n * Загружает пример Turtle\n * Алгоритм загрузки файла:\n * 1. Отображает статус загрузки\n * 2. Выполняет fetch запрос к файлу 'Turtle.ttl' (относительный путь от текущего расположения HTML файла)\n * 3. При успехе: заполняет текстовое поле RDF данными, устанавливает формат ввода и режим визуализации\n * 4. При ошибке CORS (локальный файл): показывает информацию и использует встроенные данные\n * 5. При другой ошибке: отображает подробную информацию об ошибке\n */\n /**\n * Загружает пример TriG VADv4 (Value Added Chain Diagram с иерархией TriG)\n * Демонстрирует:\n * - Иерархию процессов: p1, p1.1, p1.2, p1.1.1, p1.1.2, p2, p2.1, p2.2\n * - Корневые TriG графы: t_p1, t_p2 (hasParentTrig = root)\n * - Дочерний TriG граф: t_p1.1 (hasParentTrig = t_p1)\n * - Вычисляемые подтипы: DetailedChild, notDetailedChild, notDetailedExternal, NotDefinedType\n */\n function loadExampleTrigVADv4() {\n loadExampleFromFile('Trig_VADv4.ttl', 'Trig VADv4', 'trig', 'vad-trig', 'trig-vad-v4');\n }\n\n /**\n * Для обратной совместимости: вызывает загрузку примера Trig VADv4\n */\n function loadExample() {\n loadExampleTrigVADv4();\n }\n\n /**\n * Очищает содержимое поля RDF данных\n */\n function clearRdfInput() {\n document.getElementById('rdf-input').value = '';\n }\n\n /**\n * Сохраняет содержимое RDF поля как файл\n */\n function saveAsFile() {\n const content = document.getElementById('rdf-input').value;\n if (!content.trim()) {\n alert('Нет данных для сохранения');\n return;\n }\n\n const format = document.getElementById('input-format').value;\n let extension = 'ttl';\n if (format === 'n-triples') extension = 'nt';\n else if (format === 'n-quads') extension = 'nq';\n else if (format === 'trig') extension = 'trig';\n\n const blob = new Blob([content], { type: 'text/plain' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = `rdf-data.${extension}`;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n }\n\n /**\n * Загружает файл, выбранный пользователем\n */\n function loadFile(event) {\n const file = event.target.files[0];\n if (!file) return;\n\n const reader = new FileReader();\n reader.onload = function(e) {\n document.getElementById('rdf-input').value = e.target.result;\n\n // Определяем формат по расширению\n const extension = file.name.split('.').pop().toLowerCase();\n let format = 'turtle';\n if (extension === 'nt') format = 'n-triples';\n else if (extension === 'nq') format = 'n-quads';\n else if (extension === 'trig') format = 'trig';\n\n document.getElementById('input-format').value = format;\n\n // Обновляем статус загрузки\n const statusEl = document.getElementById('example-status');\n statusEl.textContent = `Файл ${file.name} успешно загружен`;\n statusEl.style.display = 'block';\n statusEl.style.backgroundColor = '#d4edda';\n statusEl.style.borderColor = '#c3e6cb';\n statusEl.style.color = '#155724';\n\n // Сбрасываем значение input для возможности повторной загрузки того же файла\n event.target.value = '';\n };\n reader.readAsText(file);\n }\n\n /**\n * Тестирует валидность RDF данных вручную\n *\n * Выполняет комплексную проверку RDF данных из текстового поля:\n * 1. Проверяет синтаксическую корректность (парсинг через N3.js)\n * 2. Проверяет соответствие правилам VAD онтологии (через validateVAD)\n *\n * Вызывается при нажатии кнопки \"Тест\" в заголовке поля RDF данных.\n * Результаты отображаются через alert() с подробным описанием ошибок.\n *\n * @returns {void}\n *\n * @example\n * // Вызов при нажатии кнопки \"Тест\"\n * <button onclick=\"testRdfValidation()\">Тест</button>\n *\n * @see validateVAD - Функция валидации правил VAD\n * @see formatVADErrors - Форматирование ошибок валидации\n */\n function testRdfValidation() {\n const rdfInput = document.getElementById('rdf-input');\n const inputFormat = document.getElementById('input-format').value;\n\n if (!rdfInput || !rdfInput.value.trim()) {\n alert('Нет данных для проверки. Введите RDF данные в поле выше.');\n return;\n }\n\n const rdfData = rdfInput.value;\n\n // Создаем парсер N3\n const parser = new N3.Parser({ format: inputFormat });\n\n const quads = [];\n const prefixes = {};\n\n try {\n parser.parse(rdfData, (error, quad, prefixesParsed) => {\n if (error) {\n alert(`❌ ОШИБКА СИНТАКСИСА RDF\\n\\n${error.message}\\n\\nПроверьте корректность синтаксиса RDF данных.`);\n return;\n }\n\n if (quad) {\n quads.push(quad);\n } else {\n // Parsing complete\n Object.assign(prefixes, prefixesParsed);\n\n // Выполняем валидацию VAD онтологии\n const validation = validateVAD(quads, prefixes);\n\n // Выполняем валидацию VAD схемы\n const schemaValidation = validateVADSchema(quads, prefixes);\n\n // Формируем итоговое сообщение\n let resultMessage = '';\n\n if (validation.valid && schemaValidation.valid) {\n resultMessage = '✅ ВАЛИДАЦИЯ УСПЕШНА\\n\\n' +\n 'RDF данные полностью соответствуют правилам VAD онтологии и схемы.\\n\\n' +\n `Всего триплетов: ${quads.length}\\n` +\n `Префиксов: ${Object.keys(prefixes).length}`;\n alert(resultMessage);\n } else {\n // Есть ошибки - показываем детали\n if (!validation.valid) {\n resultMessage += formatVADErrors(validation.errors);\n resultMessage += '\\n\\n';\n }\n if (!schemaValidation.valid || schemaValidation.warnings.length > 0) {\n resultMessage += formatVADSchemaValidation(schemaValidation);\n }\n alert(resultMessage);\n }\n }\n });\n } catch (error) {\n alert(`❌ ОШИБКА ПРИ ПРОВЕРКЕ\\n\\n${error.message}\\n\\nПроверьте корректность RDF данных.`);\n }\n }\n\n // ============================================================================\n // ФУНКЦИИ ДЛЯ VIRTUALRDFDATA (вычисляемые RDF данные)\n // ============================================================================\n\n /**\n * Вычисляет processSubtype для всех процессов во всех TriG схемах\n * и сохраняет результаты в virtualRDFdata\n *\n * Алгоритм вычисления vad:processSubtype:\n * - Если процесс имеет vad:hasTrig (детализированный):\n * - DetailedChild: дочерний TriG указывает на текущий (hasParentTrig = currentTrig)\n * - DetailedExternal: дочерний TriG НЕ указывает на текущий\n * - Если процесс имеет vad:hasParentProcess (недетализированный):\n * - notDetailedChild: hasParentProcess совпадает с процессом-владельцем текущей схемы\n * - notDetailedExternal: hasParentProcess НЕ совпадает\n * - NotDefinedType: hasParentProcess = vad:NotDefined\n *\n * @returns {Object} virtualRDFdata - структура с вычисленными данными\n */\n function computeVirtualRDFdata() {\n virtualRDFdata = {};\n\n // Проверяем наличие trigHierarchy\n if (!trigHierarchy || Object.keys(trigHierarchy).length === 0) {\n console.warn('computeVirtualRDFdata: trigHierarchy пуст или не инициализирован');\n return virtualRDFdata;\n }\n\n // Получаем данные из ptree для определения hasTrig и hasParentProcess\n const ptreeQuads = trigHierarchy[PTREE_GRAPH_URI]?.quads || [];\n\n // Создаем карты для быстрого доступа\n const processHasTrig = new Map(); // processUri -> trigUri\n const processHasParentProcess = new Map(); // processUri -> parentProcessUri\n const trigDefinesProcess = new Map(); // trigUri -> processUri\n const trigHasParentTrig = new Map(); // trigUri -> parentTrigUri\n\n // Заполняем карту hasTrig из ptree\n ptreeQuads.forEach(quad => {\n const predicateLabel = getPrefixedName(quad.predicate.value, currentPrefixes);\n if (predicateLabel === 'vad:hasTrig' || quad.predicate.value === 'http://example.org/vad#hasTrig') {\n processHasTrig.set(quad.subject.value, quad.object.value);\n }\n if (predicateLabel === 'vad:hasParentProcess' || quad.predicate.value === 'http://example.org/vad#hasParentProcess') {\n processHasParentProcess.set(quad.subject.value, quad.object.value);\n }\n });\n\n // Заполняем карты definesProcess и hasParentTrig для каждого TriG\n Object.entries(trigHierarchy).forEach(([trigUri, graphInfo]) => {\n if (trigUri === PTREE_GRAPH_URI || trigUri === RTREE_GRAPH_URI) return;\n\n graphInfo.quads.forEach(quad => {\n const predicateLabel = getPrefixedName(quad.predicate.value, currentPrefixes);\n if (predicateLabel === 'vad:definesProcess' || quad.predicate.value === 'http://example.org/vad#definesProcess') {\n trigDefinesProcess.set(trigUri, quad.object.value);\n }\n if (predicateLabel === 'vad:hasParentTrig' || quad.predicate.value === 'http://example.org/vad#hasParentTrig') {\n trigHasParentTrig.set(trigUri, quad.object.value);\n }\n });\n });\n\n // Обрабатываем каждый TriG типа VADProcessDia\n Object.entries(trigHierarchy).forEach(([trigUri, graphInfo]) => {\n // Пропускаем специальные графы\n if (trigUri === PTREE_GRAPH_URI || trigUri === RTREE_GRAPH_URI) return;\n\n // Получаем процесс, который определяется этим TriG\n const definingProcess = trigDefinesProcess.get(trigUri);\n\n // Создаем виртуальный TriG для хранения processSubtype\n const virtualTrigId = trigUri.replace(/t_/, 'vt_');\n const virtualTrigLabel = getPrefixedName(virtualTrigId, currentPrefixes);\n\n virtualRDFdata[virtualTrigId] = {\n trigUri: trigUri,\n label: `Виртуальный TriG для ${getPrefixedName(trigUri, currentPrefixes)}`,\n processSubtypes: {}\n };\n\n // Находим все процессы в этом TriG (по vad:isSubprocessTrig)\n const processesInTrig = [];\n graphInfo.quads.forEach(quad => {\n const predicateLabel = getPrefixedName(quad.predicate.value, currentPrefixes);\n if (predicateLabel === 'vad:isSubprocessTrig' || quad.predicate.value === 'http://example.org/vad#isSubprocessTrig') {\n processesInTrig.push(quad.subject.value);\n }\n });\n\n // Вычисляем processSubtype для каждого процесса в этом TriG\n processesInTrig.forEach(processUri => {\n let subtype = null;\n\n // Проверяем, имеет ли процесс свой TriG (детализированный)\n if (processHasTrig.has(processUri)) {\n const childTrigUri = processHasTrig.get(processUri);\n const childParentTrig = trigHasParentTrig.get(childTrigUri);\n\n if (childParentTrig === trigUri) {\n subtype = 'vad:DetailedChild';\n } else {\n subtype = 'vad:DetailedExternal';\n }\n }\n // Проверяем, имеет ли процесс hasParentProcess (недетализированный)\n else if (processHasParentProcess.has(processUri)) {\n const parentProcess = processHasParentProcess.get(processUri);\n const parentProcessLabel = getPrefixedName(parentProcess, currentPrefixes);\n\n if (parentProcessLabel === 'vad:NotDefined' || parentProcess === 'http://example.org/vad#NotDefined') {\n subtype = 'vad:NotDefinedType';\n } else if (parentProcess === definingProcess) {\n subtype = 'vad:notDetailedChild';\n } else {\n subtype = 'vad:notDetailedExternal';\n }\n }\n // По умолчанию - notDetailedChild (если ничего не определено)\n else {\n subtype = 'vad:notDetailedChild';\n }\n\n virtualRDFdata[virtualTrigId].processSubtypes[processUri] = subtype;\n });\n });\n\n // Обновляем nodeSubtypesCache на основе вычисленных данных\n updateNodeSubtypesCacheFromVirtualData();\n\n return virtualRDFdata;\n }\n\n /**\n * Обновляет nodeSubtypesCache на основе вычисленных virtualRDFdata\n * Это позволяет визуализации использовать вычисленные подтипы\n */\n function updateNodeSubtypesCacheFromVirtualData() {\n Object.values(virtualRDFdata).forEach(vTrig => {\n Object.entries(vTrig.processSubtypes).forEach(([processUri, subtype]) => {\n if (!nodeSubtypesCache[processUri]) {\n nodeSubtypesCache[processUri] = [];\n }\n\n const fullSubtypeUri = subtype.replace('vad:', 'http://example.org/vad#');\n\n if (!nodeSubtypesCache[processUri].includes(subtype)) {\n nodeSubtypesCache[processUri].push(subtype);\n }\n if (!nodeSubtypesCache[processUri].includes(fullSubtypeUri)) {\n nodeSubtypesCache[processUri].push(fullSubtypeUri);\n }\n });\n });\n }\n\n /**\n * Получает вычисленные свойства (processSubtype) для конкретного процесса\n * @param {string} nodeUri - URI узла\n * @returns {Array} - массив вычисленных свойств [{predicate, value}]\n */\n function getComputedPropertiesForNode(nodeUri) {\n const computed = [];\n\n // Ищем processSubtype в virtualRDFdata\n Object.values(virtualRDFdata).forEach(vTrig => {\n if (vTrig.processSubtypes[nodeUri]) {\n computed.push({\n predicate: 'vad:processSubtype',\n predicateLabel: 'vad:processSubtype',\n value: vTrig.processSubtypes[nodeUri],\n valueLabel: vTrig.processSubtypes[nodeUri],\n source: 'virtualRDFdata'\n });\n }\n });\n\n return computed;\n }\n\n /**\n * Показывает virtualRDFdata в отдельном окне\n * Вызывается при нажатии кнопки \"virtualRDFdata\"\n */\n function showVirtualRDFData() {\n // Вычисляем virtualRDFdata, если еще не вычислены\n if (!trigHierarchy || Object.keys(trigHierarchy).length === 0) {\n // Нужно сначала распарсить RDF данные\n const rdfInput = document.getElementById('rdf-input');\n if (!rdfInput || !rdfInput.value.trim()) {\n alert('Нет данных для анализа. Введите RDF данные и нажмите \"Показать\" для визуализации.');\n return;\n }\n\n // Парсим данные для получения trigHierarchy\n const parser = new N3.Parser({ format: 'trig' });\n const quads = [];\n let prefixes = {};\n\n try {\n parser.parse(rdfInput.value, (error, quad, parsedPrefixes) => {\n if (error) {\n alert(`Ошибка парсинга RDF: ${error.message}`);\n return;\n }\n if (quad) {\n quads.push(quad);\n } else {\n if (parsedPrefixes) {\n prefixes = parsedPrefixes;\n }\n\n currentPrefixes = prefixes;\n currentQuads = quads;\n\n const hierarchyResult = parseTriGHierarchy(quads, prefixes);\n if (hierarchyResult.valid) {\n trigHierarchy = hierarchyResult.hierarchy;\n computeVirtualRDFdata();\n displayVirtualRDFDataWindow();\n } else {\n alert('Ошибка парсинга иерархии TriG. Убедитесь, что данные корректны.');\n }\n }\n });\n } catch (error) {\n alert(`Ошибка: ${error.message}`);\n }\n } else {\n computeVirtualRDFdata();\n displayVirtualRDFDataWindow();\n }\n }\n\n /**\n * Отображает virtualRDFdata в новом окне браузера\n */\n function displayVirtualRDFDataWindow() {\n // Формируем TriG формат для virtualRDFdata\n let trigContent = '# virtualRDFdata - вычисляемые RDF данные\\n';\n trigContent += '# Сгенерировано автоматически на основе RDF данных\\n';\n trigContent += '#\\n';\n trigContent += '# Содержит вычисленные vad:processSubtype для каждого процесса в каждой схеме\\n';\n trigContent += '#\\n';\n trigContent += '# Подтипы процессов:\\n';\n trigContent += '# - vad:DetailedChild (голубой): процесс с vad:hasTrig, дочерний TriG указывает на текущий\\n';\n trigContent += '# - vad:DetailedExternal (оранжевый): процесс с vad:hasTrig, дочерний TriG НЕ указывает на текущий\\n';\n trigContent += '# - vad:notDetailedChild (зелёный): процесс с vad:hasParentProcess, совпадающим с родителем схемы\\n';\n trigContent += '# - vad:notDetailedExternal (серый): процесс с vad:hasParentProcess, НЕ совпадающим с родителем\\n';\n trigContent += '# - vad:NotDefinedType (красноватый): процесс с vad:hasParentProcess = vad:NotDefined\\n\\n';\n\n trigContent += '@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\\n';\n trigContent += '@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\\n';\n trigContent += '@prefix vad: <http://example.org/vad#> .\\n\\n';\n\n // Генерируем содержимое для каждого виртуального TriG\n Object.entries(virtualRDFdata).forEach(([vTrigId, vTrigData]) => {\n const vTrigLabel = getPrefixedName(vTrigId, currentPrefixes);\n const originalTrigLabel = getPrefixedName(vTrigData.trigUri, currentPrefixes);\n\n trigContent += `# ============================================================================\\n`;\n trigContent += `# ${vTrigLabel} - вычисленные свойства для ${originalTrigLabel}\\n`;\n trigContent += `# ============================================================================\\n\\n`;\n\n trigContent += `${vTrigLabel} {\\n`;\n\n // Добавляем processSubtype для каждого процесса\n Object.entries(vTrigData.processSubtypes).forEach(([processUri, subtype]) => {\n const processLabel = getPrefixedName(processUri, currentPrefixes);\n trigContent += ` ${processLabel} vad:processSubtype ${subtype} .\\n`;\n });\n\n trigContent += '}\\n\\n';\n });\n\n // Создаем новое окно\n const newWindow = window.open('', '_blank', 'width=900,height=700,scrollbars=yes,resizable=yes');\n\n if (newWindow) {\n const htmlContent = '<!DOCTYPE html>' +\n '<html><head><title>virtualRDFdata</title>' +\n '<style>' +\n 'body { font-family: Consolas, Monaco, monospace; padding: 20px; background-color: #f5f5f5; margin: 0; }' +\n '.container { background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }' +\n 'h1 { color: #333; font-size: 18px; margin-top: 0; border-bottom: 2px solid #9C27B0; padding-bottom: 10px; }' +\n 'pre { white-space: pre-wrap; word-wrap: break-word; font-size: 13px; line-height: 1.5; margin: 0; color: #333; }' +\n '.copy-btn { background-color: #9C27B0; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-right: 10px; margin-bottom: 15px; }' +\n '.copy-btn:hover { background-color: #7B1FA2; }' +\n '.save-btn { background-color: #4CAF50; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-bottom: 15px; }' +\n '.save-btn:hover { background-color: #45a049; }' +\n '.comment { color: #6a9955; }' +\n '.keyword { color: #0000ff; }' +\n '.uri { color: #795E26; }' +\n '</style></head>' +\n '<body><div class=\"container\">' +\n '<h1>virtualRDFdata - Вычисляемые RDF данные</h1>' +\n '<button class=\"copy-btn\" id=\"copy-btn\">Копировать в буфер</button>' +\n '<button class=\"save-btn\" id=\"save-btn\">Сохранить как файл</button>' +\n '<pre id=\"trig-content\"></pre>' +\n '</div></body></html>';\n\n newWindow.document.write(htmlContent);\n newWindow.document.close();\n\n // Заполняем содержимое\n const preElement = newWindow.document.getElementById('trig-content');\n if (preElement) {\n preElement.textContent = trigContent;\n }\n\n // Обработчик копирования\n const copyBtn = newWindow.document.getElementById('copy-btn');\n if (copyBtn) {\n copyBtn.onclick = function() {\n newWindow.navigator.clipboard.writeText(trigContent).then(function() {\n newWindow.alert('Скопировано в буфер обмена');\n }).catch(function() {\n const textarea = newWindow.document.createElement('textarea');\n textarea.value = trigContent;\n newWindow.document.body.appendChild(textarea);\n textarea.select();\n newWindow.document.execCommand('copy');\n newWindow.document.body.removeChild(textarea);\n newWindow.alert('Скопировано в буфер обмена');\n });\n };\n }\n\n // Обработчик сохранения\n const saveBtn = newWindow.document.getElementById('save-btn');\n if (saveBtn) {\n saveBtn.onclick = function() {\n const blob = new Blob([trigContent], { type: 'text/turtle' });\n const url = URL.createObjectURL(blob);\n const a = newWindow.document.createElement('a');\n a.href = url;\n a.download = 'virtualRDFdata.ttl';\n newWindow.document.body.appendChild(a);\n a.click();\n newWindow.document.body.removeChild(a);\n URL.revokeObjectURL(url);\n };\n }\n }\n }\n\n // ============================================================================\n // ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ДЛЯ РАБОТЫ С URI\n // ============================================================================\n\n function getLocalName(uri) {\n if (typeof uri !== 'string') return String(uri);\n const hashIndex = uri.lastIndexOf('#');\n const slashIndex = uri.lastIndexOf('/');\n const splitIndex = Math.max(hashIndex, slashIndex);\n if (splitIndex !== -1 && splitIndex < uri.length - 1) {\n return uri.substring(splitIndex + 1);\n }\n return uri;\n }\n\n function getPrefixedName(uri, prefixes) {\n if (typeof uri !== 'string') return String(uri);\n for (const [prefix, namespace] of Object.entries(prefixes)) {\n if (uri.startsWith(namespace)) {\n const localName = uri.substring(namespace.length);\n return prefix + ':' + localName;\n }\n }\n return getLocalName(uri);\n }\n\n function escapeDotString(str) {\n return str.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n }\n\n function generateNodeId(value) {\n let hash = 0;\n for (let i = 0; i < value.length; i++) {\n const char = value.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash;\n }\n return 'node' + Math.abs(hash);\n }\n\n /**\n * Генерирует ID узла для VAD режима на основе RDF идентификатора.\n * Преобразует prefixed name (например, vad:Process1) в валидный DOT ID (vad_Process1).\n * @param {string} uri - URI узла\n * @param {Object} prefixes - Словарь префиксов\n * @returns {string} - Валидный DOT ID\n */\n function generateVadNodeId(uri, prefixes) {\n const prefixedName = getPrefixedName(uri, prefixes);\n // Заменяем ':' на '_' для получения валидного DOT ID\n // Также заменяем другие недопустимые символы на '_'\n return prefixedName.replace(/[:\\-\\.\\s]/g, '_');\n }\n\n function isNameOrLabelPredicate(predicateLabel) {\n const lowerPredicate = predicateLabel.toLowerCase();\n return lowerPredicate.includes('name') || lowerPredicate.includes('label');\n }\n\n function escapeHtmlLabel(str) {\n return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n }\n\n /**\n * Экранирует HTML символы для безопасного отображения\n * @param {string} text - Исходный текст\n * @returns {string} - Экранированный текст\n */\n function escapeHtml(text) {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n\n /**\n * Копирует ID объекта в буфер обмена\n * @param {string} id - ID для копирования\n * @param {HTMLElement} button - Кнопка, которая была нажата\n */\n function copyObjectId(id, button) {\n navigator.clipboard.writeText(id).then(() => {\n // Показываем подтверждение\n const originalText = button.textContent;\n button.textContent = 'Скопировано!';\n button.classList.add('copied');\n\n // Возвращаем оригинальный текст через 2 секунды\n setTimeout(() => {\n button.textContent = originalText;\n button.classList.remove('copied');\n }, 2000);\n }).catch(err => {\n console.error('Ошибка копирования:', err);\n // Fallback для старых браузеров\n const textArea = document.createElement('textarea');\n textArea.value = id;\n textArea.style.position = 'fixed';\n textArea.style.left = '-9999px';\n document.body.appendChild(textArea);\n textArea.select();\n try {\n document.execCommand('copy');\n button.textContent = 'Скопировано!';\n button.classList.add('copied');\n setTimeout(() => {\n button.textContent = 'Копировать';\n button.classList.remove('copied');\n }, 2000);\n } catch (e) {\n console.error('Fallback copy failed:', e);\n }\n document.body.removeChild(textArea);\n });\n }\n\n function wrapTextByWords(text, maxLength) {\n if (!text || text.length <= maxLength) return [text];\n const words = text.split(/\\s+/);\n const lines = [];\n let currentLine = '';\n for (const word of words) {\n if (currentLine.length === 0) {\n currentLine = word;\n } else if (currentLine.length + 1 + word.length <= maxLength) {\n currentLine += ' ' + word;\n } else {\n lines.push(currentLine);\n currentLine = word;\n }\n }\n if (currentLine.length > 0) lines.push(currentLine);\n return lines;\n }\n\n function formatLabelWithWrap(label, maxLength, isBold = false) {\n const lines = wrapTextByWords(label, maxLength);\n const needsWrap = lines.length > 1;\n if (!needsWrap) {\n if (isBold) return `<B>${escapeHtmlLabel(label)}</B>`;\n return escapeHtmlLabel(label);\n }\n let result = '';\n for (let i = 0; i < lines.length; i++) {\n if (i > 0) result += '<BR/>';\n const escapedLine = escapeHtmlLabel(lines[i]);\n if (isBold) {\n result += `<FONT POINT-SIZE=\"9\"><B>${escapedLine}</B></FONT>`;\n } else {\n result += `<FONT POINT-SIZE=\"9\">${escapedLine}</FONT>`;\n }\n }\n return result;\n }\n\n // ============================================================================\n // ГЕНЕРАЦИЯ DOT-КОДА\n // ============================================================================\n\n
[2026-01-26T19:23:56.112Z] [INFO] /**\n * Генерирует DOT-код для режима VAD\n * В этом режиме:\n * - Процессы отображаются как cds (chevron) с зеленой заливкой\n * - Под процессами показываются исполнители\n * - Связи hasNext идут с востока на запад\n * - hasParentTrig не показывается, но есть в свойствах\n */\n function rdfToDotVAD(quads, prefixes = {}) {\n buildNodeTypesCache(currentQuads, prefixes);\n nodeLabelToUri = {};\n\n // Собираем информацию о процессах и их исполнителях\n const processes = new Map(); // URI процесса -> информация\n const executorGroups = new Map(); // URI группы -> список исполнителей\n const hasNextEdges = []; // Связи hasNext между процессами\n\n // ver5t: Извлекаем информацию об именованном графе для отображения заголовка схемы\n let namedGraphUri = null;\n const graphLabels = new Map(); // URI графа -> rdfs:label\n\n // Собираем все именованные графы и их метки\n quads.forEach(quad => {\n // Получаем URI именованного графа (если есть)\n if (quad.graph && quad.graph.value && quad.graph.value !== '') {\n if (!namedGraphUri) {\n namedGraphUri = quad.graph.value; // Берём первый именованный граф\n }\n }\n });\n\n // Ищем rdfs:label для именованных графов (определяется вне графа)\n quads.forEach(quad => {\n const subjectUri = quad.subject.value;\n const predicateUri = quad.predicate.value;\n const predicateLabel = getPrefixedName(predicateUri, prefixes);\n\n if (predicateLabel === 'rdfs:label' || predicateUri === 'http://www.w3.org/2000/01/rdf-schema#label') {\n // Проверяем, является ли subject именованным графом\n if (subjectUri === namedGraphUri) {\n graphLabels.set(subjectUri, quad.object.value);\n }\n }\n });\n\n // Первый проход: собираем все объекты\n quads.forEach(quad => {\n const subjectUri = quad.subject.value;\n const predicateUri = quad.predicate.value;\n const predicateLabel = getPrefixedName(predicateUri, prefixes);\n const objectValue = quad.object.value;\n\n // Проверяем типы\n const subjectTypes = nodeTypesCache[subjectUri] || [];\n const isProcess = subjectTypes.some(t =>\n t === 'vad:TypeProcess' || t === 'http://example.org/vad#TypeProcess'\n );\n\n if (isProcess) {\n if (!processes.has(subjectUri)) {\n processes.set(subjectUri, {\n uri: subjectUri,\n label: getPrefixedName(subjectUri, prefixes),\n name: null,\n executorGroup: null,\n hasNext: [],\n hasParent: null\n });\n }\n\n const processInfo = processes.get(subjectUri);\n\n // Собираем rdfs:label для имени\n if (predicateLabel === 'rdfs:label' || predicateUri === 'http://www.w3.org/2000/01/rdf-schema#label') {\n processInfo.name = objectValue;\n }\n\n // Собираем hasExecutor\n if (predicateLabel === 'vad:hasExecutor' || predicateUri === 'http://example.org/vad#hasExecutor') {\n processInfo.executorGroup = objectValue;\n }\n\n // Собираем hasNext\n if (predicateLabel === 'vad:hasNext' || predicateUri === 'http://example.org/vad#hasNext') {\n processInfo.hasNext.push(objectValue);\n hasNextEdges.push({ from: subjectUri, to: objectValue });\n }\n\n // Собираем hasParent\n if (predicateLabel === 'vad:hasParentTrig' || predicateUri === 'http://example.org/vad#hasParentTrig') {\n processInfo.hasParent = objectValue;\n }\n }\n\n // Собираем информацию о группах исполнителей\n const isExecutorGroup = subjectTypes.some(t =>\n t === 'vad:ExecutorGroup' || t === 'http://example.org/vad#ExecutorGroup'\n );\n\n if (isExecutorGroup) {\n if (!executorGroups.has(subjectUri)) {\n executorGroups.set(subjectUri, {\n uri: subjectUri,\n label: null,\n executors: []\n });\n }\n\n const groupInfo = executorGroups.get(subjectUri);\n\n if (predicateLabel === 'rdfs:label' || predicateUri === 'http://www.w3.org/2000/01/rdf-schema#label') {\n groupInfo.label = objectValue;\n }\n\n if (predicateLabel === 'vad:includes' || predicateUri === 'http://example.org/vad#includes') {\n groupInfo.executors.push(objectValue);\n }\n }\n });\n\n // Дополняем метаданные процессов из vad:ptree (если не найдены в текущем TriG)\n processes.forEach((processInfo, processUri) => {\n if (!processInfo.name) {\n const ptreeMetadata = getProcessMetadataFromPtree(processUri, prefixes);\n if (ptreeMetadata.label) {\n processInfo.name = ptreeMetadata.label;\n }\n }\n });\n\n // Собираем имена исполнителей\n // Сначала из текущих quads (если rdfs:label есть в этом графе)\n const executorNames = new Map();\n quads.forEach(quad => {\n const subjectUri = quad.subject.value;\n const predicateUri = quad.predicate.value;\n const predicateLabel = getPrefixedName(predicateUri, prefixes);\n const objectValue = quad.object.value;\n\n const subjectTypes = nodeTypesCache[subjectUri] || [];\n const isExecutor = subjectTypes.some(t =>\n t === 'vad:TypeExecutor' || t === 'http://example.org/vad#TypeExecutor'\n );\n\n if (isExecutor && (predicateLabel === 'rdfs:label' || predicateUri === 'http://www.w3.org/2000/01/rdf-schema#label')) {\n executorNames.set(subjectUri, objectValue);\n }\n });\n\n // Дополняем имена исполнителей из vad:rtree (если не найдены в текущем TriG)\n executorGroups.forEach((groupInfo, groupUri) => {\n groupInfo.executors.forEach(executorUri => {\n if (!executorNames.has(executorUri)) {\n const nameFromRtree = getExecutorNameFromRtree(executorUri, prefixes);\n if (nameFromRtree) {\n executorNames.set(executorUri, nameFromRtree);\n }\n }\n });\n });\n\n // Генерация DOT-кода\n // Используем rankdir=TB чтобы rank=same группировал узлы горизонтально\n // А процессы идут в одной строке благодаря rank=same\n let dot = 'digraph VADGraph {\\n';\n dot += ' // VAD (Value Added Chain Diagram)\\n';\n dot += ' rankdir=TB;\\n'; // Top-to-bottom позволяет горизонтальное выравнивание через rank=same\n dot += ' node [fontname=\"Arial\"];\\n';\n dot += ' edge [fontname=\"Arial\", fontsize=10];\\n';\n dot += ' splines=spline;\\n'; // spline для лучшей маршрутизации skip-ребер\n dot += ' nodesep=0.8;\\n'; // Расстояние между узлами\n dot += ' ranksep=0.3;\\n'; // Минимальное расстояние между рангами (CDS и ExecutorGroup)\n\n // ver5t: Добавляем заголовок схемы из именованного графа (если есть)\n // Сначала проверяем graphLabels (для обычного VAD режима)\n // Затем проверяем trigHierarchy (для VAD TriG режима)\n let schemaTitle = null;\n if (namedGraphUri && graphLabels.has(namedGraphUri)) {\n schemaTitle = graphLabels.get(namedGraphUri);\n } else if (namedGraphUri && trigHierarchy && trigHierarchy[namedGraphUri] && trigHierarchy[namedGraphUri].label) {\n schemaTitle = trigHierarchy[namedGraphUri].label;\n }\n if (schemaTitle) {\n dot += ` label=\"${escapeDotString(schemaTitle)}\";\\n`;\n dot += ' labelloc=\"t\";\\n'; // Заголовок вверху\n dot += ' fontname=\"Arial\";\\n';\n dot += ' fontsize=16;\\n';\n }\n dot += '\\n';\n\n // Фильтруем процессы - показываем только те, которые не являются материнскими\n // (Process0 скрыт, потому что он не имеет hasNext и является родителем)\n const visibleProcesses = new Map();\n processes.forEach((processInfo, uri) => {\n // Показываем процесс, если он имеет hasNext или на него ссылается hasNext\n const hasOutgoingNext = processInfo.hasNext.length > 0;\n const hasIncomingNext = [...processes.values()].some(p => p.hasNext.includes(uri));\n\n if (hasOutgoingNext || hasIncomingNext) {\n visibleProcesses.set(uri, processInfo);\n }\n });\n\n // Строим порядок процессов для определения skip-ребер (ребер, которые пропускают промежуточные узлы)\n // Топологическая сортировка процессов по hasNext для определения их порядка\n const processOrder = [];\n const visited = new Set();\n const processUris = [...visibleProcesses.keys()];\n\n // Найти начальный процесс (процесс без входящих hasNext)\n const incomingCount = new Map();\n processUris.forEach(uri => incomingCount.set(uri, 0));\n hasNextEdges.forEach(edge => {\n if (visibleProcesses.has(edge.to)) {\n incomingCount.set(edge.to, (incomingCount.get(edge.to) || 0) + 1);\n }\n });\n\n // Топологическая сортировка (BFS)\n const queue = [];\n processUris.forEach(uri => {\n if (incomingCount.get(uri) === 0) {\n queue.push(uri);\n }\n });\n\n while (queue.length > 0) {\n const uri = queue.shift();\n if (!visited.has(uri)) {\n visited.add(uri);\n processOrder.push(uri);\n const processInfo = visibleProcesses.get(uri);\n if (processInfo) {\n processInfo.hasNext.forEach(nextUri => {\n if (visibleProcesses.has(nextUri)) {\n const count = incomingCount.get(nextUri) - 1;\n incomingCount.set(nextUri, count);\n if (count === 0 && !visited.has(nextUri)) {\n queue.push(nextUri);\n }\n }\n });\n }\n }\n }\n\n // Добавляем оставшиеся процессы (на случай циклов или изолированных)\n processUris.forEach(uri => {\n if (!visited.has(uri)) {\n processOrder.push(uri);\n }\n });\n\n // Создаем индекс позиции процесса для определения skip-ребер\n const processPositionIndex = new Map();\n processOrder.forEach((uri, index) => {\n processPositionIndex.set(uri, index);\n });\n\n // Добавляем узлы процессов (CDS) и ExecutorGroup как отдельные узлы\n dot += ' // Процессы VAD (cds shape) и ExecutorGroup (ellipse желтый)\\n';\n\n // Сначала добавляем все узлы CDS\n visibleProcesses.forEach((processInfo, uri) => {\n const nodeId = generateVadNodeId(uri, prefixes);\n const processName = processInfo.name || processInfo.label;\n\n // Формируем HTML label с именем процесса (БЕЗ жирного шрифта)\n const wrappedProcessName = wrapTextByWords(processName, currentMaxLabelLength);\n\n let htmlLabel = '<';\n for (let i = 0; i < wrappedProcessName.length; i++) {\n if (i > 0) htmlLabel += '<BR/>';\n htmlLabel += escapeHtmlLabel(wrappedProcessName[i]);\n }\n htmlLabel += '>';\n\n // Определяем стиль узла на основе vad:processSubtype\n // Проверяем nodeSubtypesCache для определения типа процесса\n // Поддерживаемые подтипы:\n // - DetailedChild (голубой) - детализированный подпроцесс текущей схемы\n // - DetailedExternal (оранжевый) - детализированный внешний процесс\n // - notDetailedChild (зеленый) - недетализированный подпроцесс\n // - notDetailedExternal (серый) - недетализированный внешний процесс\n // - NotDefinedType (красноватый) - тип не определен\n const nodeSubtypes = nodeSubtypesCache[uri] || [];\n\n // Получаем стиль на основе подтипа процесса (используем новую функцию)\n const nodeStyle = getProcessStyleForSubtype(nodeSubtypes);\n dot += ` ${nodeId} [label=${htmlLabel} ${nodeStyle}];\\n`;\n\n nodeLabelToUri[processInfo.label] = { uri: uri, dotId: nodeId };\n });\n\n dot += '\\n';\n\n // Добавляем ExecutorGroup узлы (желтые эллипсы)\n // ExecutorGroup объекты теперь кликабельны и показывают свойства\n dot += ' // ExecutorGroup узлы (эллипсы с желтоватой заливкой)\\n';\n visibleProcesses.forEach((processInfo, uri) => {\n const nodeId = generateVadNodeId(uri, prefixes);\n\n // Получаем список исполнителей\n let executorsList = '';\n let executorGroupUri = null;\n if (processInfo.executorGroup && executorGroups.has(processInfo.executorGroup)) {\n executorGroupUri = processInfo.executorGroup;\n const group = executorGroups.get(processInfo.executorGroup);\n const executorNamesList = group.executors.map(exUri =>\n executorNames.get(exUri) || getPrefixedName(exUri, prefixes)\n );\n executorsList = executorNamesList.join(', ');\n }\n\n if (executorsList && executorGroupUri) {\n const executorNodeId = `${nodeId}_exec`;\n const wrappedExecutors = wrapTextByWords(executorsList, currentMaxLabelLength);\n\n let execLabel = '<<FONT POINT-SIZE=\"9\">';\n for (let i = 0; i < wrappedExecutors.length; i++) {\n if (i > 0) execLabel += '<BR/>';\n execLabel += escapeHtmlLabel(wrappedExecutors[i]);\n }\n execLabel += '</FONT>>';\n\n // ExecutorGroup как эллипс с желтоватой заливкой\n dot += ` ${executorNodeId} [label=${execLabel} shape=\"ellipse\" color=\"#B8860B\" fillcolor=\"#FFFFCC\" fontname=\"Arial\" style=\"filled\"];\\n`;\n\n // Регистрируем ExecutorGroup для кликабельности (показ свойств объекта)\n const executorGroupLabel = getPrefixedName(executorGroupUri, prefixes);\n nodeLabelToUri[executorGroupLabel] = { uri: executorGroupUri, dotId: executorNodeId };\n }\n });\n\n dot += '\\n';\n\n // Собираем ID узлов для rank constraints, используя порядок из topological sort\n const cdsNodeIds = [];\n const execNodeIds = [];\n const nodeIdToUri = new Map(); // Обратное отображение для быстрого поиска\n\n // Используем отсортированный порядок процессов\n processOrder.forEach(uri => {\n if (visibleProcesses.has(uri)) {\n const processInfo = visibleProcesses.get(uri);\n const nodeId = generateVadNodeId(uri, prefixes);\n cdsNodeIds.push(nodeId);\n nodeIdToUri.set(nodeId, uri);\n\n if (processInfo.executorGroup && executorGroups.has(processInfo.executorGroup)) {\n const group = executorGroups.get(processInfo.executorGroup);\n if (group.executors.length > 0) {\n execNodeIds.push(`${nodeId}_exec`);\n }\n }\n }\n });\n\n // Разбиваем процессы на строки по currentMaxVadRowLength\n const rows = [];\n for (let i = 0; i < cdsNodeIds.length; i += currentMaxVadRowLength) {\n rows.push(cdsNodeIds.slice(i, i + currentMaxVadRowLength));\n }\n\n // Собираем соответствующие execNodeIds для каждой строки\n // Важно: execNodeIds должны соответствовать позициям cdsNodeIds\n const execRows = [];\n rows.forEach((rowCdsIds, rowIndex) => {\n const rowExecIds = [];\n rowCdsIds.forEach(cdsId => {\n const execId = `${cdsId}_exec`;\n if (execNodeIds.includes(execId)) {\n rowExecIds.push(execId);\n }\n });\n execRows.push(rowExecIds);\n });\n\n // Генерация rank constraints для каждой строки\n dot += ' // Rank constraints для CDS строк и ExecutorGroup строк\\n';\n rows.forEach((rowCdsIds, rowIndex) => {\n // CDS строка\n dot += ` { rank=same; ${rowCdsIds.join('; ')}; }\\n`;\n });\n\n // Генерация rank constraints для ExecutorGroup строк\n execRows.forEach((rowExecIds, rowIndex) => {\n if (rowExecIds.length > 0) {\n dot += ` { rank=same; ${rowExecIds.join('; ')}; }\\n`;\n }\n });\n\n // Добавляем невидимые ребра между строками для правильного разделения рядов\n // Это необходимо для того, чтобы Graphviz разместил строки одну под другой\n // FIX issue #60: Связываем ПЕРВЫЙ CDS текущей строки с ПЕРВЫМ CDS следующей строки\n // для выравнивания всех строк по левому краю (как требуется в issue #60)\n dot += '\\n // Невидимые ребра между строками для разделения рядов и выравнивания по левому краю\\n';\n for (let rowIndex = 0; rowIndex < rows.length - 1; rowIndex++) {\n const currentRowCdsIds = rows[rowIndex];\n const nextRowCdsIds = rows[rowIndex + 1];\n\n if (currentRowCdsIds.length > 0 && nextRowCdsIds.length > 0) {\n // Связываем ПЕРВЫЙ CDS текущей строки с ПЕРВЫМ CDS следующей строки\n // Это выравнивает все строки по левому краю (issue #60)\n const firstCurrentCdsId = currentRowCdsIds[0];\n const firstNextCdsId = nextRowCdsIds[0];\n dot += ` ${firstCurrentCdsId} -> ${firstNextCdsId} [style=invis weight=100 minlen=2];\\n`;\n }\n }\n dot += '\\n';\n\n // Добавляем видимые связи vad:hasExecutor между CDS и ExecutorGroup\n dot += ' // Связи vad:hasExecutor - видимые ребра от процессов к группам исполнителей\\n';\n visibleProcesses.forEach((processInfo, uri) => {\n const nodeId = generateVadNodeId(uri, prefixes);\n\n let hasExecutorGroup = false;\n if (processInfo.executorGroup && executorGroups.has(processInfo.executorGroup)) {\n const group = executorGroups.get(processInfo.executorGroup);\n hasExecutorGroup = group.executors.length > 0;\n }\n\n if (hasExecutorGroup) {\n const executorNodeId = `${nodeId}_exec`;\n // Видимая связь vad:hasExecutor (синяя пунктирная, ненаправленная)\n dot += ` ${nodeId} -> ${executorNodeId} [color=\"#1565C0\" penwidth=\"1\" style=\"dashed\" arrowhead=\"none\" weight=10];\\n`;\n }\n });\n\n dot += '\\n';\n\n // Добавляем ребра hasNext между процессами\n // Все ребра hasNext используют порты East -> West (выход справа, вход слева) согласно issue #58\n dot += ' // Связи hasNext - горизонтальный поток процессов (East -> West)\\n';\n\n // Добавляем все ребра hasNext\n hasNextEdges.forEach(edge => {\n // Проверяем, что оба процесса видимы\n if (visibleProcesses.has(edge.from) && visibleProcesses.has(edge.to)) {\n const fromId = generateVadNodeId(edge.from, prefixes);\n const toId = generateVadNodeId(edge.to, prefixes);\n\n // Все ребра hasNext используют порты East -> West (выход справа, вход слева)\n // Это обеспечивает единообразное направление связей согласно требованиям issue #58\n dot += ` ${fromId}:e -> ${toId}:w [color=\"#2E7D32\" penwidth=\"1\" style=\"solid\" arrowhead=\"vee\"];\\n`;\n }\n });\n\n dot += '}\\n';\n\n return dot;\n }\n\n function rdfToDotAggregation(quads, prefixes = {}) {\n buildNodeTypesCache(currentQuads, prefixes);\n nodeLabelToUri = {};\n\n const nodes = new Map();\n const edges = [];\n const nodeLiterals = new Map();\n\n quads.forEach(quad => {\n const subject = quad.subject;\n const predicate = quad.predicate;\n const object = quad.object;\n\n const subjectValue = subject.value;\n const predicateValue = predicate.value;\n const objectValue = object.value;\n\n const subjectLabel = getPrefixedName(subjectValue, prefixes);\n const predicateLabel = getPrefixedName(predicateValue, prefixes);\n\n if (!nodes.has(subjectValue)) {\n const nodeId = generateNodeId(subjectValue);\n nodes.set(subjectValue, {\n id: nodeId,\n label: subjectLabel,\n value: subjectValue,\n isUri: subject.termType === 'NamedNode',\n isBlank: subject.termType === 'BlankNode',\n isLiteral: false\n });\n nodeLabelToUri[subjectLabel] = { uri: subjectValue, dotId: nodeId };\n nodeLiterals.set(subjectValue, []);\n }\n\n if (object.termType === 'Literal') {\n const literals = nodeLiterals.get(subjectValue);\n literals.push({\n predicate: predicateLabel,\n value: objectValue,\n isNameLabel: isNameOrLabelPredicate(predicateLabel)\n });\n } else {\n const objectLabel = getPrefixedName(objectValue, prefixes);\n if (!nodes.has(objectValue)) {\n const nodeId = generateNodeId(objectValue);\n nodes.set(objectValue, {\n id: nodeId,\n label: objectLabel,\n value: objectValue,\n isUri: object.termType === 'NamedNode',\n isLiteral: false,\n isBlank: object.termType === 'BlankNode'\n });\n nodeLabelToUri[objectLabel] = { uri: objectValue, dotId: nodeId };\n if (!nodeLiterals.has(objectValue)) {\n nodeLiterals.set(objectValue, []);\n }\n }\n edges.push({\n from: nodes.get(subjectValue).id,\n to: nodes.get(objectValue).id,\n label: predicateLabel,\n predicateUri: predicateValue\n });\n }\n });\n\n let dot = 'digraph RDFGraph {\\n';\n dot += ' rankdir=LR;\\n';\n dot += ' node [fontname=\"Arial\", shape=\"ellipse\"];\\n';\n dot += ' edge [fontname=\"Arial\", fontsize=10];\\n';\n dot += '\\n';\n\n nodes.forEach((nodeInfo, value) => {\n const literals = nodeLiterals.get(value) || [];\n const nameLabelLiterals = literals.filter(l => l.isNameLabel);\n const otherLiterals = literals.filter(l => !l.isNameLabel);\n\n let htmlLabel = '<';\n htmlLabel += formatLabelWithWrap(nodeInfo.label, currentMaxLabelLength, true);\n\n let addedLines = 1;\n for (const lit of nameLabelLiterals) {\n if (addedLines >= MaxAggregationParams) break;\n htmlLabel += '<BR/>';\n htmlLabel += formatLabelWithWrap(lit.value, currentMaxLabelLength, false);\n addedLines++;\n }\n\n for (const lit of otherLiterals) {\n if (addedLines >= MaxAggregationParams) break;\n htmlLabel += '<BR/>';\n const fullText = lit.predicate + ': ' + lit.value;\n const wrappedLines = wrapTextByWords(fullText, currentMaxLabelLength);\n for (let j = 0; j < wrappedLines.length; j++) {\n if (j > 0) htmlLabel += '<BR/>';\n htmlLabel += `<FONT POINT-SIZE=\"8\">${escapeHtmlLabel(wrappedLines[j])}</FONT>`;\n }\n addedLines++;\n }\n\n htmlLabel += '>';\n\n const nodeStyle = getNodeStyle(nodeInfo.value, false, nodeInfo.isBlank);\n dot += ` ${nodeInfo.id} [label=${htmlLabel} ${nodeStyle}];\\n`;\n });\n\n dot += '\\n';\n\n edges.forEach(edge => {\n const edgeStyle = getEdgeStyle(edge.predicateUri, edge.label);\n dot += ` ${edge.from} -> ${edge.to} [label=\"${escapeDotString(edge.label)}\" ${edgeStyle}];\\n`;\n });\n\n dot += '}\\n';\n return dot;\n }\n\n function rdfToDot(quads, prefixes = {}) {\n if (currentMode === 'aggregation') {\n return rdfToDotAggregation(quads, prefixes);\n }\n\n if (currentMode === 'vad') {\n return rdfToDotVAD(quads, prefixes);\n }\n\n buildNodeTypesCache(currentQuads, prefixes);\n nodeLabelToUri = {};\n\n const nodes = new Map();\n const edges = [];\n\n quads.forEach(quad => {\n const subject = quad.subject;\n const predicate = quad.predicate;\n const object = quad.object;\n\n const subjectValue = subject.value;\n const predicateValue = predicate.value;\n const objectValue = object.value;\n\n const subjectLabel = getPrefixedName(subjectValue, prefixes);\n const predicateLabel = getPrefixedName(predicateValue, prefixes);\n const objectLabel = object.termType === 'Literal'\n ? `\"${objectValue}\"`\n : getPrefixedName(objectValue, prefixes);\n\n if (!nodes.has(subjectValue)) {\n const nodeId = generateNodeId(subjectValue);\n nodes.set(subjectValue, {\n id: nodeId,\n label: subjectLabel,\n value: subjectValue,\n isUri: subject.termType === 'NamedNode',\n isBlank: subject.termType === 'BlankNode',\n isLiteral: false\n });\n nodeLabelToUri[subjectLabel] = { uri: subjectValue, dotId: nodeId };\n }\n\n if (!nodes.has(objectValue)) {\n const nodeId = generateNodeId(objectValue);\n nodes.set(objectValue, {\n id: nodeId,\n label: objectLabel,\n value: objectValue,\n isUri: object.termType === 'NamedNode',\n isLiteral: object.termType === 'Literal',\n isBlank: object.termType === 'BlankNode'\n });\n nodeLabelToUri[objectLabel] = { uri: objectValue, dotId: nodeId };\n }\n\n edges.push({\n from: nodes.get(subjectValue).id,\n to: nodes.get(objectValue).id,\n label: predicateLabel,\n predicateUri: predicateValue\n });\n });\n\n let dot = 'digraph RDFGraph {\\n';\n dot += ' rankdir=LR;\\n';\n dot += ' node [fontname=\"Arial\"];\\n';\n dot += ' edge [fontname=\"Arial\", fontsize=10];\\n';\n dot += '\\n';\n\n nodes.forEach((nodeInfo, value) => {\n const nodeStyle = getNodeStyle(nodeInfo.value, nodeInfo.isLiteral, nodeInfo.isBlank);\n\n if (currentMode === 'notation' && nodeInfo.label.length > currentMaxLabelLength) {\n const wrappedLabel = formatLabelWithWrap(nodeInfo.label, currentMaxLabelLength, false);\n dot += ` ${nodeInfo.id} [label=<${wrappedLabel}> ${nodeStyle}];\\n`;\n } else {\n dot += ` ${nodeInfo.id} [label=\"${escapeDotString(nodeInfo.label)}\" ${nodeStyle}];\\n`;\n }\n });\n\n dot += '\\n';\n\n edges.forEach(edge => {\n const edgeStyle = getEdgeStyle(edge.predicateUri, edge.label);\n dot += ` ${edge.from} -> ${edge.to} [label=\"${escapeDotString(edge.label)}\" ${edgeStyle}];\\n`;\n });\n\n dot += '}\\n';\n return dot;\n }\n\n // ============================================================================\n // ФУНКЦИИ ОТОБРАЖЕНИЯ\n // ============================================================================\n\n function showLoading() {\n const output = document.getElementById('output');\n const resultContainer = document.getElementById('result-container');\n\n resultContainer.style.display = 'block';\n output.innerHTML = `\n <div class=\"loading\">\n <div class=\"spinner\"></div>\n <p>Обработка RDF данных...</p>\n </div>\n `;\n\n document.getElementById('export-buttons').style.display = 'none';\n document.getElementById('zoom-controls').style.display = 'none';\n document.getElementById('prefixes-panel').style.display = 'none';\n document.getElementById('legend-panel').style.display = 'none';\n // filter-panel removed in minimization\n }\n\n /**\n * Улучшает сообщение об ошибке парсинга, добавляя содержимое проблемной строки\n * @param {string} errorMessage - Исходное сообщение об ошибке\n * @param {string} rdfInput - Исходные RDF данные\n * @returns {string} - Улучшенное сообщение об ошибке\n */\n function enhanceParseError(errorMessage, rdfInput) {\n // Ищем номер строки в сообщении об ошибке (паттерн \"on line N\" или \"line N\")\n const lineMatch = errorMessage.match(/(?:on\\s+)?line\\s+(\\d+)/i);\n if (lineMatch && rdfInput) {\n const lineNumber = parseInt(lineMatch[1], 10);\n const lines = rdfInput.split('\\n');\n if (lineNumber > 0 && lineNumber <= lines.length) {\n const problemLine = lines[lineNumber - 1];\n return `${errorMessage}\\nСтрока ${lineNumber}: ${problemLine}`;\n }\n }\n return errorMessage;\n }\n\n function showError(message) {\n const output = document.getElementById('output');\n const resultContainer = document.getElementById('result-container');\n const vadTrigOutput = document.getElementById('vad-trig-output');\n const vadTrigContainer = document.getElementById('vad-trig-container');\n\n resultContainer.style.display = 'block';\n // Заменяем переносы строк на <br> для корректного отображения в HTML\n const formattedMessage = message.replace(/\\n/g, '<br>');\n const errorHtml = `<div class=\"error\"><strong>Ошибка:</strong> ${formattedMessage}</div>`;\n\n // Если активен режим VAD-TriG, показываем ошибку также в его контейнере\n const isVadTrigMode = vadTrigContainer && vadTrigContainer.style.display !== 'none';\n if (isVadTrigMode && vadTrigOutput) {\n vadTrigOutput.innerHTML = errorHtml;\n }\n\n // Всегда показываем ошибку в основном output (для не-VAD-TriG режимов или при переключении)\n output.innerHTML = errorHtml;\n\n // Скрываем VAD-TriG панели при ошибке, чтобы показать основной output\n toggleVADTriGPanels(false);\n\n document.getElementById('export-buttons').style.display = 'none';\n document.getElementById('zoom-controls').style.display = 'none';\n document.getElementById('prefixes-panel').style.display = 'none';\n document.getElementById('legend-panel').style.display = 'none';\n // filter-panel removed in minimization\n }\n\n function showValidationError(message) {\n const output = document.getElementById('output');\n const resultContainer = document.getElementById('result-container');\n const vadTrigOutput = document.getElementById('vad-trig-output');\n const vadTrigContainer = document.getElementById('vad-trig-container');\n\n resultContainer.style.display = 'block';\n const errorHtml = `<div class=\"validation-error\">${message}</div>`;\n\n // Если активен режим VAD-TriG, показываем ошибку также в его контейнере\n const isVadTrigMode = vadTrigContainer && vadTrigContainer.style.display !== 'none';\n if (isVadTrigMode && vadTrigOutput) {\n vadTrigOutput.innerHTML = errorHtml;\n }\n\n // Всегда показываем ошибку в основном output (для не-VAD-TriG режимов или при переключении)\n output.innerHTML = errorHtml;\n\n // Скрываем VAD-TriG панели при ошибке, чтобы показать основной output\n toggleVADTriGPanels(false);\n\n document.getElementById('export-buttons').style.display = 'none';\n document.getElementById('zoom-controls').style.display = 'none';\n document.getElementById('prefixes-panel').style.display = 'none';\n document.getElementById('legend-panel').style.display = 'none';\n // filter-panel removed in minimization\n }\n\n function displayPrefixes(prefixes) {\n const prefixesPanel = document.getElementById('prefixes-panel');\n const prefixesContent = document.getElementById('prefixes-content');\n\n const prefixEntries = Object.entries(prefixes);\n if (prefixEntries.length === 0) {\n prefixesPanel.style.display = 'none';\n return;\n }\n\n prefixEntries.sort((a, b) => a[0].localeCompare(b[0]));\n\n let html = '';\n for (const [prefix, namespace] of prefixEntries) {\n html += `<div class=\"prefix-line\">`;\n html += `<span class=\"prefix-name\">@prefix ${prefix}:</span> `;\n html += `<a href=\"${namespace}\" class=\"prefix-url\" target=\"_blank\">&lt;${namespace}&gt;</a> .`;\n html += `</div>`;\n }\n\n prefixesContent.innerHTML = html;\n prefixesPanel.style.display = 'block';\n }\n\n function displayLegend() {\n const legendPanel = document.getElementById('legend-panel');\n const legendContent = document.getElementById('legend-content');\n\n let html = '';\n\n // Используем только VAD стили (режим VAD TriG)\n let nodeStylesSource, edgeStylesSource;\n nodeStylesSource = VADNodeStyles;\n edgeStylesSource = VADEdgeStyles;\n\n html += '<div class=\"legend-section\">';\n html += '<h4>Стили узлов (Node Styles)</h4>';\n\n for (const [styleName, styleConfig] of Object.entries(nodeStylesSource)) {\n const fillColorMatch = styleConfig.dot.match(/fillcolor=\"([^\"]+)\"/);\n const borderColorMatch = styleConfig.dot.match(/color=\"([^\"]+)\"/);\n const shapeMatch = styleConfig.dot.match(/shape=\"([^\"]+)\"/);\n\n const fillColor = fillColorMatch ? fillColorMatch[1] : '#ffffff';\n const borderColor = borderColorMatch ? borderColorMatch[1] : '#000000';\n const shape = shapeMatch ? shapeMatch[1] : 'ellipse';\n\n let shapeStyle = '';\n if (shape === 'box' || shape === 'note') {\n shapeStyle = 'border-radius: 0;';\n } else if (shape === 'octagon') {\n shapeStyle = 'border-radius: 0; clip-path: polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%);';\n } else if (shape === 'cds') {\n shapeStyle = 'border-radius: 0; clip-path: polygon(0% 0%, 85% 0%, 100% 50%, 85% 100%, 0% 100%, 15% 50%);';\n } else {\n shapeStyle = 'border-radius: 50%;';\n }\n\n html += `<div class=\"legend-item\">`;\n html += `<div class=\"legend-shape\" style=\"background-color: ${fillColor}; border-color: ${borderColor}; ${shapeStyle}\"></div>`;\n html += `<span class=\"legend-label\">${styleConfig.label}</span>`;\n html += `</div>`;\n }\n\n html += '</div>';\n\n html += '<div class=\"legend-section\">';\n html += '<h4>Стили ребер (Edge Styles)</h4>';\n\n for (const [styleName, styleConfig] of Object.entries(edgeStylesSource)) {\n const colorMatch = styleConfig.dot.match(/color=\"([^\"]+)\"/);\n const penwidthMatch = styleConfig.dot.match(/penwidth=\"([^\"]+)\"/);\n const lineStyleMatch = styleConfig.dot.match(/style=\"([^\"]+)\"/);\n\n const color = colorMatch ? colorMatch[1] : '#666666';\n const penwidth = penwidthMatch ? parseInt(penwidthMatch[1]) : 1;\n const lineStyle = lineStyleMatch ? lineStyleMatch[1] : 'solid';\n\n let borderStyle = 'solid';\n if (lineStyle === 'dashed') borderStyle = 'dashed';\n if (lineStyle === 'dotted') borderStyle = 'dotted';\n\n html += `<div class=\"legend-item\">`;\n html += `<span class=\"legend-line\" style=\"background-color: ${color}; height: ${penwidth + 1}px; border-bottom: ${penwidth}px ${borderStyle} ${color}; background: none;\"></span>`;\n html += `<span class=\"legend-label\">${styleConfig.label}</span>`;\n html += `</div>`;\n }\n\n html += '</div>';\n\n legendContent.innerHTML = html;\n legendPanel.style.display = 'block';\n }\n\n // ============================================================================\n // ФУНКЦИИ ДЛЯ SPARQL SMART DESIGN\n // ============================================================================\n\n /**\n * Показывает сообщение в панели Smart Design\n * @param {string} message - Текст сообщения\n * @param {string} type - Тип сообщения: 'error', 'success', 'info'\n */\n function showSmartDesignMessage(message, type) {\n const messageDiv = document.getElementById('smart-design-message');\n if (messageDiv) {\n messageDiv.textContent = message;\n messageDiv.className = 'smart-design-message ' + type;\n messageDiv.style.display = 'block';\n // Сообщение остается видимым до следующего действия пользователя\n // (не скрываем автоматически по таймеру)\n }\n }\n\n /**\n * Скрывает сообщение в панели Smart Design\n */\n function hideSmartDesignMessage() {\n const messageDiv = document.getElementById('smart-design-message');\n if (messageDiv) {\n messageDiv.style.display = 'none';\n }\n }\n\n /**\n * Собирает все уникальные TriG графы из текущих данных\n * @returns {Array} - Массив объектов {uri, label}\n */\n function getAllTriGs() {\n const trigs = [];\n const seen = new Set();\n\n // Собираем из trigHierarchy (если есть)\n for (const [uri, info] of Object.entries(trigHierarchy)) {\n if (!seen.has(uri)) {\n seen.add(uri);\n const label = info.label || getPrefixedName(uri, currentPrefixes);\n trigs.push({ uri: uri, label: label });\n }\n }\n\n // Также проверяем allTrigGraphs\n for (const trigUri of allTrigGraphs) {\n if (!seen.has(trigUri)) {\n seen.add(trigUri);\n const label = getPrefixedName(trigUri, currentPrefixes);\n trigs.push({ uri: trigUri, label: label });\n }\n }\n\n return trigs.sort((a, b) => a.label.localeCompare(b.label));\n }\n\n /**\n * Собирает все уникальные субъекты (объекты) из текущих RDF данных\n * @returns {Array} - Массив объектов {uri, label}\n */\n function getAllSubjects() {\n const subjects = [];\n const seen = new Set();\n\n currentQuads.forEach(quad => {\n const uri = quad.subject.value;\n if (!seen.has(uri)) {\n seen.add(uri);\n const label = getPrefixedName(uri, currentPrefixes);\n subjects.push({ uri: uri, label: label });\n }\n });\n\n return subjects.sort((a, b) => a.label.localeCompare(b.label));\n }\n\n /**\n * Собирает все уникальные предикаты из текущих RDF данных\n * @returns {Array} - Массив объектов {uri, label}\n */\n function getAllPredicates() {\n const predicates = [];\n const seen = new Set();\n\n currentQuads.forEach(quad => {\n const uri = quad.predicate.value;\n if (!seen.has(uri)) {\n seen.add(uri);\n const label = getPrefixedName(uri, currentPrefixes);\n predicates.push({ uri: uri, label: label });\n }\n });\n\n return predicates.sort((a, b) => a.label.localeCompare(b.label));\n }\n\n /**\n * Собирает все уникальные объекты (исключая литералы) из текущих RDF данных\n * @returns {Array} - Массив объектов {uri, label}\n */\n function getAllObjects() {\n const objects = [];\n const seen = new Set();\n\n currentQuads.forEach(quad => {\n // Исключаем литералы согласно требованиям\n if (quad.object.termType !== 'Literal') {\n const uri = quad.object.value;\n if (!seen.has(uri)) {\n seen.add(uri);\n const label = getPrefixedName(uri, currentPrefixes);\n objects.push({ uri: uri, label: label });\n }\n }\n });\n\n return objects.sort((a, b) => a.label.localeCompare(b.label));\n }\n\n /**\n * Проверяет, существует ли TriG с указанным именем в RDF данных\n * @param {string} name - Имя TriG для проверки\n * @returns {boolean}\n */\n function trigExists(name) {\n const trigs = getAllTriGs();\n const normalizedName = name.toLowerCase();\n return trigs.some(t => {\n const label = t.label.toLowerCase();\n const localName = getLocalName(t.uri).toLowerCase();\n return label === normalizedName || localName === normalizedName || t.uri === name;\n });\n }\n\n /**\n * Проверяет, существует ли субъект с указанным именем в RDF данных\n * @param {string} name - Имя субъекта для проверки\n * @returns {boolean}\n */\n function subjectExists(name) {\n const subjects = getAllSubjects();\n const normalizedName = name.toLowerCase();\n return subjects.some(s => {\n const label = s.label.toLowerCase();\n const localName = getLocalName(s.uri).toLowerCase();\n return label === normalizedName || localName === normalizedName || s.uri === name;\n });\n }\n\n /**\n * Проверяет, существует ли предикат с указанным именем в RDF данных\n * @param {string} name - Имя предиката для проверки\n * @returns {boolean}\n */\n function predicateExists(name) {\n const predicates = getAllPredicates();\n const normalizedName = name.toLowerCase();\n return predicates.some(p => {\n const label = p.label.toLowerCase();\n const localName = getLocalName(p.uri).toLowerCase();\n return label === normalizedName || localName === normalizedName || p.uri === name;\n });\n }\n\n /**\n * Заполняет выпадающие списки в панели Smart Design\n */\n function populateSmartDesignDropdowns() {\n const trigSelect = document.getElementById('smart-design-trig');\n const subjectSelect = document.getElementById('smart-design-subject');\n const subjectTypeSelect = document.getElementById('smart-design-subject-type');\n const predicateSelect = document.getElementById('smart-design-predicate');\n const objectSelect = document.getElementById('smart-design-object');\n\n if (!trigSelect || !subjectSelect || !predicateSelect || !objectSelect) return;\n\n // Очищаем и заполняем TriG (без опции \"New\" - для создания новых TriG есть отдельная кнопка)\n trigSelect.innerHTML = '<option value=\"\">-- Выберите TriG --</option>';\n const trigs = getAllTriGs();\n trigs.forEach(t => {\n const option = document.createElement('option');\n option.value = t.uri;\n // Отображаем id с label в скобках (при наличии)\n const id = getPrefixedName(t.uri, currentPrefixes);\n const displayText = t.label && t.label !== id ? `${id} (${t.label})` : id;\n option.textContent = displayText;\n trigSelect.appendChild(option);\n });\n\n // Очищаем и заполняем Subject\n subjectSelect.innerHTML = '<option value=\"\">-- Выберите Subject --</option>';\n subjectSelect.innerHTML += '<option value=\"__NEW__\">New (создать новый)</option>';\n const subjects = getAllSubjects();\n subjects.forEach(s => {\n const option = document.createElement('option');\n option.value = s.uri;\n option.textContent = s.label;\n subjectSelect.appendChild(option);\n });\n\n // Очищаем и заполняем Subject Type\n if (subjectTypeSelect) {\n populateSubjectTypeDropdown();\n }\n\n // Очищаем и заполняем Predicate (без опции \"New\" - предикаты берутся только из онтологии)\n predicateSelect.innerHTML = '<option value=\"\">-- Выберите Predicate --</option>';\n const predicates = getAllPredicates();\n predicates.forEach(p => {\n const option = document.createElement('option');\n option.value = p.uri;\n option.textContent = p.label;\n predicateSelect.appendChild(option);\n });\n\n // Очищаем и заполняем Object (без литералов)\n objectSelect.innerHTML = '<option value=\"\">-- Выберите Object --</option>';\n objectSelect.innerHTML += '<option value=\"__NEW__\">New (создать новый литерал)</option>';\n const objects = getAllObjects();\n objects.forEach(o => {\n const option = document.createElement('option');\n option.value = o.uri;\n option.textContent = o.label;\n objectSelect.appendChild(option);\n });\n\n // Деактивируем Subject/Predicate/Object поля до выбора TriG\n updateSmartDesignFieldsState();\n }\n\n /**\n * Заполняет выпадающий список Subject Type\n * Типы зависят от выбранного TriG\n */\n function populateSubjectTypeDropdown() {\n const subjectTypeSelect = document.getElementById('smart-design-subject-type');\n const trigSelect = document.getElementById('smart-design-trig');\n\n if (!subjectTypeSelect) return;\n\n subjectTypeSelect.innerHTML = '<option value=\"\">-- Выберите тип Subject --</option>';\n\n // Определяем доступные типы в зависимости от выбранного TriG\n const trigValue = trigSelect ? trigSelect.value : '';\n const isPtree = trigValue === 'vad:ptree' || trigValue === 'http://example.org/vad#ptree' || trigValue.endsWith('#ptree');\n const isRtree = trigValue === 'vad:rtree' || trigValue === 'http://example.org/vad#rtree' || trigValue.endsWith('#rtree');\n\n let availableTypes = [];\n\n // Используем данные из Tech appendix, если они загружены\n if (techAppendixData && techAppendixData.predicateGroups) {\n if (isPtree) {\n // В ptree: ConceptProcessPredicate, ConceptTreePredicate\n availableTypes = [\n { value: 'vad:TypeProcess', label: 'vad:TypeProcess (концепт процесса)' },\n { value: 'vad:ProcessTree', label: 'vad:ProcessTree (дерево процессов)' }\n ];\n } else if (isRtree) {\n // В rtree: ConceptExecutorPredicate, ExecutorTreePredicate\n availableTypes = [\n { value: 'vad:TypeExecutor', label: 'vad:TypeExecutor (концепт исполнителя)' },\n { value: 'vad:ExecutorTree', label: 'vad:ExecutorTree (дерево исполнителей)' }\n ];\n } else if (trigValue) {\n // В VADProcessDia: IndividProcessPredicate, ExecutorGroupPredicate, DiagramPredicate\n availableTypes = [\n { value: 'vad:TypeProcess', label: 'vad:TypeProcess (индивид процесса)' },\n { value: 'vad:ExecutorGroup', label: 'vad:ExecutorGroup (группа исполнителей)' },\n { value: 'vad:VADProcessDia', label: 'vad:VADProcessDia (диаграмма)' }\n ];\n }\n } else {\n // Fallback: исходная логика, если Tech appendix не загружен\n if (isPtree) {\n availableTypes = [\n { value: 'vad:TypeProcess', label: 'vad:TypeProcess' },\n { value: 'vad:ProcessTree', label: 'vad:ProcessTree' }\n ];\n } else if (isRtree) {\n availableTypes = [\n { value: 'vad:TypeExecutor', label: 'vad:TypeExecutor' },\n
[2026-01-26T19:23:56.114Z] [INFO] { value: 'vad:ExecutorTree', label: 'vad:ExecutorTree' }\n ];\n } else if (trigValue) {\n availableTypes = [\n { value: 'vad:TypeProcess', label: 'vad:TypeProcess' },\n { value: 'vad:ExecutorGroup', label: 'vad:ExecutorGroup' },\n { value: 'vad:VADProcessDia', label: 'vad:VADProcessDia' }\n ];\n }\n }\n\n availableTypes.forEach(type => {\n const option = document.createElement('option');\n option.value = type.value;\n option.textContent = type.label;\n subjectTypeSelect.appendChild(option);\n });\n }\n\n /**\n * Получает допустимые предикаты для выбранного Subject Type в зависимости от контекста TriG\n * @param {string} subjectType - Тип субъекта (например, 'vad:Process')\n * @param {string} trigContext - Контекст TriG ('ptree', 'rtree', 'vadProcessDia')\n * @returns {Array} - Массив допустимых предикатов\n */\n function getPredicatesForSubjectType(subjectType, trigContext) {\n const typeConfig = TYPE_PREDICATE_MAP[subjectType];\n if (!typeConfig) return [];\n\n let predicates = [];\n\n if (trigContext === 'ptree' && typeConfig.ptree) {\n predicates = typeConfig.ptree;\n } else if (trigContext === 'rtree' && typeConfig.rtree) {\n predicates = typeConfig.rtree;\n } else if (trigContext === 'vadProcessDia' && typeConfig.vadProcessDia) {\n predicates = typeConfig.vadProcessDia;\n }\n\n return predicates;\n }\n\n /**\n * Определяет контекст TriG по его URI\n * @param {string} trigUri - URI TriG графа\n * @returns {string} - 'ptree', 'rtree' или 'vadProcessDia'\n */\n function getTrigContext(trigUri) {\n if (!trigUri) return '';\n if (trigUri === 'vad:ptree' || trigUri === 'http://example.org/vad#ptree' || trigUri.endsWith('#ptree')) {\n return 'ptree';\n }\n if (trigUri === 'vad:rtree' || trigUri === 'http://example.org/vad#rtree' || trigUri.endsWith('#rtree')) {\n return 'rtree';\n }\n return 'vadProcessDia';\n }\n\n /**\n * Обновляет выпадающий список Predicate на основе выбранного Subject Type\n * Автогенерируемые предикаты (isSubprocessTrig, hasExecutor) отображаются как disabled\n */\n function updatePredicateBySubjectType() {\n const subjectTypeSelect = document.getElementById('smart-design-subject-type');\n const predicateSelect = document.getElementById('smart-design-predicate');\n const trigSelect = document.getElementById('smart-design-trig');\n\n if (!subjectTypeSelect || !predicateSelect || !trigSelect) return;\n\n const subjectType = subjectTypeSelect.value;\n const trigUri = trigSelect.value;\n const trigContext = getTrigContext(trigUri);\n\n // Сохраняем текущее выбранное значение\n const currentPredicateValue = predicateSelect.value;\n\n // Всегда показываем допустимые предикаты для выбранного типа\n if (!subjectType) {\n // Если тип не выбран - показываем все предикаты\n predicateSelect.innerHTML = '<option value=\"\">-- Выберите Predicate --</option>';\n const allPredicates = getAllPredicates();\n allPredicates.forEach(p => {\n const option = document.createElement('option');\n option.value = p.uri;\n option.textContent = p.label;\n predicateSelect.appendChild(option);\n });\n } else {\n // Показываем допустимые предикаты с учетом автогенерируемых\n const allowedPredicates = getPredicatesForSubjectType(subjectType, trigContext);\n\n predicateSelect.innerHTML = '<option value=\"\">-- Выберите Predicate --</option>';\n\n allowedPredicates.forEach(pred => {\n const option = document.createElement('option');\n // Пробуем развернуть в полный URI\n let fullUri = pred;\n for (const [prefix, namespace] of Object.entries(currentPrefixes)) {\n if (pred.startsWith(prefix + ':')) {\n fullUri = namespace + pred.substring(prefix.length + 1);\n break;\n }\n }\n option.value = fullUri;\n\n // Проверяем, является ли предикат автогенерируемым\n const isAutoGen = isAutoGeneratedPredicate(pred);\n if (isAutoGen) {\n option.textContent = pred + ' (автогенерируемый)';\n option.disabled = true;\n option.style.color = '#999';\n } else {\n option.textContent = pred;\n }\n\n predicateSelect.appendChild(option);\n });\n }\n\n // Восстанавливаем выбор если значение все еще доступно и не disabled\n if (currentPredicateValue) {\n const matchingOption = Array.from(predicateSelect.options).find(o => o.value === currentPredicateValue && !o.disabled);\n if (matchingOption) {\n predicateSelect.value = currentPredicateValue;\n } else {\n predicateSelect.value = '';\n }\n } else {\n predicateSelect.value = '';\n }\n }\n\n /**\n * Предикаты, требующие фильтрации Object по типу vad:Process (из ptree)\n */\n const PROCESS_OBJECT_PREDICATES = [\n 'vad:hasNext',\n 'http://example.org/vad#hasNext'\n ];\n\n /**\n * Проверяет, требует ли предикат фильтрации Object до типа Process\n * @param {string} predicateUri - URI или prefixed name предиката\n * @returns {boolean}\n */\n function isProcessObjectPredicate(predicateUri) {\n return PROCESS_OBJECT_PREDICATES.some(allowed =>\n predicateUri === allowed ||\n predicateUri.endsWith('#hasNext')\n );\n }\n\n /**\n * Обновляет список Object в зависимости от выбранного Predicate\n * В режиме Filter: если выбран vad:hasNext, показываем только объекты типа vad:Process из ptree\n */\n function updateObjectsByPredicate() {\n const predicateSelect = document.getElementById('smart-design-predicate');\n const objectSelect = document.getElementById('smart-design-object');\n\n if (!predicateSelect || !objectSelect) return;\n\n const predicateValue = predicateSelect.value;\n\n // Сохраняем текущее выбранное значение\n const currentObjectValue = objectSelect.value;\n\n // В режиме 'full' не фильтруем объекты\n if (smartDesignMode === 'full' || !predicateValue) {\n // Восстанавливаем полный список объектов\n objectSelect.innerHTML = '<option value=\"\">-- Выберите Object --</option>';\n objectSelect.innerHTML += '<option value=\"__NEW__\">New (создать новый литерал)</option>';\n const allObjects = getAllObjects();\n allObjects.forEach(o => {\n const option = document.createElement('option');\n option.value = o.uri;\n option.textContent = o.label;\n objectSelect.appendChild(option);\n });\n } else if (isProcessObjectPredicate(predicateValue)) {\n // Режим 'filtered' и предикат требует Object типа Process\n objectSelect.innerHTML = '<option value=\"\">-- Выберите Object --</option>';\n // Для hasNext не показываем опцию \"New (создать новый литерал)\" т.к. объект должен быть Process\n\n // Получаем только объекты типа vad:Process из ptree\n const processObjects = getProcessSubjects();\n processObjects.forEach(p => {\n const option = document.createElement('option');\n option.value = p.uri;\n option.textContent = p.label;\n objectSelect.appendChild(option);\n });\n } else {\n // Режим 'filtered' но предикат не требует специальной фильтрации\n objectSelect.innerHTML = '<option value=\"\">-- Выберите Object --</option>';\n objectSelect.innerHTML += '<option value=\"__NEW__\">New (создать новый литерал)</option>';\n const allObjects = getAllObjects();\n allObjects.forEach(o => {\n const option = document.createElement('option');\n option.value = o.uri;\n option.textContent = o.label;\n objectSelect.appendChild(option);\n });\n }\n\n // Восстанавливаем выбор если значение все еще доступно\n if (currentObjectValue && Array.from(objectSelect.options).some(o => o.value === currentObjectValue)) {\n objectSelect.value = currentObjectValue;\n } else {\n objectSelect.value = '';\n }\n }\n\n /**\n * Переключает режим работы Smart Design (filtered/full)\n */\n function toggleSmartDesignMode() {\n const toggleBtn = document.getElementById('smart-design-mode-toggle');\n\n if (smartDesignMode === 'filtered') {\n smartDesignMode = 'full';\n if (toggleBtn) toggleBtn.textContent = 'Режим: Полный';\n } else {\n smartDesignMode = 'filtered';\n if (toggleBtn) toggleBtn.textContent = 'Режим: Фильтр';\n }\n\n // Обновляем список предикатов\n updatePredicateBySubjectType();\n // Обновляем список объектов\n updateObjectsByPredicate();\n }\n\n /**\n * Обновляет состояние полей Subject/SubjectType/Predicate/Object (активные только после выбора TriG)\n */\n function updateSmartDesignFieldsState() {\n const trigSelect = document.getElementById('smart-design-trig');\n const subjectSelect = document.getElementById('smart-design-subject');\n const subjectTypeSelect = document.getElementById('smart-design-subject-type');\n const predicateSelect = document.getElementById('smart-design-predicate');\n const objectSelect = document.getElementById('smart-design-object');\n\n if (!trigSelect || !subjectSelect || !predicateSelect || !objectSelect) return;\n\n const trigSelected = trigSelect.value && trigSelect.value !== '';\n const newTrigActive = document.getElementById('new-trig-modal')?.style.display === 'block';\n\n // Если активно окно New TriG, деактивируем все поля\n if (newTrigActive) {\n trigSelect.disabled = true;\n subjectSelect.disabled = true;\n if (subjectTypeSelect) subjectTypeSelect.disabled = true;\n predicateSelect.disabled = true;\n objectSelect.disabled = true;\n } else {\n trigSelect.disabled = false;\n subjectSelect.disabled = !trigSelected;\n if (subjectTypeSelect) subjectTypeSelect.disabled = !trigSelected;\n predicateSelect.disabled = !trigSelected;\n objectSelect.disabled = !trigSelected;\n }\n }\n\n /**\n * Проверяет, является ли выбранный TriG графом vad:ptree\n * @returns {boolean}\n */\n function isSelectedTrigPtree() {\n const trigSelect = document.getElementById('smart-design-trig');\n if (!trigSelect || !trigSelect.value) return false;\n const trigValue = trigSelect.value;\n return trigValue === 'vad:ptree' ||\n trigValue === 'http://example.org/vad#ptree' ||\n trigValue.endsWith('#ptree');\n }\n\n /**\n * Получает субъекты типа vad:Process для фильтрации в режиме vad:ptree\n * @returns {Array} - Массив объектов {uri, label}\n */\n function getProcessSubjects() {\n const subjects = [];\n const seen = new Set();\n\n currentQuads.forEach(quad => {\n const uri = quad.subject.value;\n if (!seen.has(uri) && isSubjectVadProcess(uri)) {\n seen.add(uri);\n const label = getPrefixedName(uri, currentPrefixes);\n subjects.push({ uri: uri, label: label });\n }\n });\n\n return subjects.sort((a, b) => a.label.localeCompare(b.label));\n }\n\n /**\n * Получает субъекты определённого типа для фильтрации в режиме Filter\n * @param {string} typeValue - Тип субъекта (prefixed name или полный URI)\n * @returns {Array} - Массив объектов {uri, label}\n */\n function getSubjectsByType(typeValue) {\n const subjects = [];\n const seen = new Set();\n\n // Преобразуем typeValue в полный URI для сравнения\n let typeUri = typeValue;\n for (const [prefix, namespace] of Object.entries(currentPrefixes)) {\n if (typeValue.startsWith(prefix + ':')) {\n typeUri = namespace + typeValue.substring(prefix.length + 1);\n break;\n }\n }\n\n currentQuads.forEach(quad => {\n const uri = quad.subject.value;\n if (seen.has(uri)) return;\n\n const subjectTypes = nodeTypesCache[uri] || [];\n const hasType = subjectTypes.some(t =>\n t === typeValue || t === typeUri\n );\n\n if (hasType) {\n seen.add(uri);\n const label = getPrefixedName(uri, currentPrefixes);\n subjects.push({ uri: uri, label: label });\n }\n });\n\n return subjects.sort((a, b) => a.label.localeCompare(b.label));\n }\n\n /**\n * Обновляет содержимое выпадающего списка Subject на основе выбранного Subject Type\n * в режиме Filter (smartDesignMode === 'filtered')\n */\n function updateSubjectsBySubjectType() {\n const subjectSelect = document.getElementById('smart-design-subject');\n const subjectTypeSelect = document.getElementById('smart-design-subject-type');\n const trigSelect = document.getElementById('smart-design-trig');\n\n if (!subjectSelect || !subjectTypeSelect) return;\n\n const selectedType = subjectTypeSelect.value;\n const trigUri = trigSelect ? trigSelect.value : '';\n if (!selectedType) return; // Если тип не выбран, не фильтруем\n\n // Сохраняем текущее выбранное значение\n const currentSubjectValue = subjectSelect.value;\n\n // Очищаем и заполняем Subject\n subjectSelect.innerHTML = '<option value=\"\">-- Выберите Subject --</option>';\n subjectSelect.innerHTML += '<option value=\"__NEW__\">New (создать новый)</option>';\n\n // Используем SPARQL-based подход для заполнения субъектов\n // в зависимости от выбранного типа и контекста TriG\n const trigContext = getTrigContext(trigUri);\n let subjects = [];\n\n if (selectedType === 'vad:TypeProcess') {\n if (trigContext === 'ptree') {\n // В ptree показываем концепты процессов (Process rdf:type vad:TypeProcess)\n subjects = funSPARQLvalues(SPARQL_QUERIES.PROCESS_CONCEPTS_IN_PTREE, 'process');\n } else {\n // В VADProcessDia показываем индивиды процессов из текущего TriG\n subjects = getProcessIndividualsInTriG(trigUri);\n }\n } else if (selectedType === 'vad:ExecutorGroup') {\n // Группы исполнителей из текущего TriG\n subjects = getExecutorGroupsInTriG(trigUri);\n } else if (selectedType === 'vad:TypeExecutor') {\n // Концепты исполнителей из rtree\n subjects = funSPARQLvalues(SPARQL_QUERIES.EXECUTOR_CONCEPTS_IN_RTREE, 'executor');\n } else if (selectedType === 'vad:ProcessTree' || selectedType === 'vad:ExecutorTree' || selectedType === 'vad:VADProcessDia') {\n // Для структурных типов используем старый подход\n subjects = getSubjectsByType(selectedType);\n }\n\n // Преобразуем URI в отображаемый формат\n subjects.forEach(uri => {\n const option = document.createElement('option');\n if (typeof uri === 'object' && uri.uri) {\n option.value = uri.uri;\n option.textContent = uri.label || getPrefixedName(uri.uri, currentPrefixes);\n } else {\n option.value = uri;\n option.textContent = getPrefixedName(uri, currentPrefixes);\n }\n subjectSelect.appendChild(option);\n });\n\n // Восстанавливаем выбор если значение все еще доступно\n if (currentSubjectValue && Array.from(subjectSelect.options).some(o => o.value === currentSubjectValue)) {\n subjectSelect.value = currentSubjectValue;\n } else {\n subjectSelect.value = '';\n }\n }\n\n /**\n * Получает предикаты из PTREE_PREDICATES для фильтрации в режиме vad:ptree\n * @returns {Array} - Массив объектов {uri, label}\n */\n function getPtreePredicates() {\n const predicates = [];\n const seen = new Set();\n\n // Добавляем только предикаты, которые есть в PTREE_PREDICATES и присутствуют в данных\n currentQuads.forEach(quad => {\n const uri = quad.predicate.value;\n const prefixedName = getPrefixedName(uri, currentPrefixes);\n if (!seen.has(uri) && (isPtreePredicate(uri) || isPtreePredicate(prefixedName))) {\n seen.add(uri);\n predicates.push({ uri: uri, label: prefixedName });\n }\n });\n\n // Также добавляем предикаты из PTREE_PREDICATES, которых может не быть в данных\n PTREE_PREDICATES.forEach(pred => {\n if (!seen.has(pred) && !pred.startsWith('http')) {\n // Это prefixed name, проверяем есть ли такой в списке\n const alreadyInList = predicates.some(p => p.label === pred);\n if (!alreadyInList) {\n // Пробуем развернуть в полный URI\n let fullUri = pred;\n for (const [prefix, namespace] of Object.entries(currentPrefixes)) {\n if (pred.startsWith(prefix + ':')) {\n fullUri = namespace + pred.substring(prefix.length + 1);\n break;\n }\n }\n if (!seen.has(fullUri)) {\n seen.add(fullUri);\n predicates.push({ uri: fullUri, label: pred });\n }\n }\n }\n });\n\n return predicates.sort((a, b) => a.label.localeCompare(b.label));\n }\n\n /**\n * Обновляет содержимое выпадающих списков Subject и Predicate\n * в зависимости от выбранного TriG (vad:ptree или другой)\n */\n function updateSubjectPredicateDropdowns() {\n const subjectSelect = document.getElementById('smart-design-subject');\n const subjectTypeSelect = document.getElementById('smart-design-subject-type');\n const predicateSelect = document.getElementById('smart-design-predicate');\n\n if (!subjectSelect || !predicateSelect) return;\n\n const isPtree = isSelectedTrigPtree();\n\n // Сохраняем текущие выбранные значения (если возможно)\n const currentSubjectValue = subjectSelect.value;\n const currentPredicateValue = predicateSelect.value;\n\n // Очищаем и заполняем Subject\n subjectSelect.innerHTML = '<option value=\"\">-- Выберите Subject --</option>';\n subjectSelect.innerHTML += '<option value=\"__NEW__\">New (создать новый)</option>';\n\n const subjects = isPtree ? getProcessSubjects() : getAllSubjects();\n subjects.forEach(s => {\n const option = document.createElement('option');\n option.value = s.uri;\n option.textContent = s.label;\n subjectSelect.appendChild(option);\n });\n\n // Сбрасываем Subject Type при смене TriG\n if (subjectTypeSelect) {\n subjectTypeSelect.value = '';\n }\n\n // Обновляем Predicate с учетом режима и Subject Type\n updatePredicateBySubjectType();\n\n // Восстанавливаем выбор если значение все еще доступно\n if (currentSubjectValue && Array.from(subjectSelect.options).some(o => o.value === currentSubjectValue)) {\n subjectSelect.value = currentSubjectValue;\n } else {\n subjectSelect.value = '';\n }\n if (currentPredicateValue && Array.from(predicateSelect.options).some(o => o.value === currentPredicateValue)) {\n predicateSelect.value = currentPredicateValue;\n } else {\n predicateSelect.value = '';\n }\n }\n\n /**\n * Запрашивает у пользователя новое имя и проверяет его уникальность\n * @param {string} fieldName - Название поля (TriG, Subject, Predicate, Object)\n * @param {Function} existsChecker - Функция проверки существования\n * @param {boolean} allowDuplicate - Разрешить дубликаты (для Object)\n * @returns {string|null} - Введенное имя или null если отменено\n */\n function promptForNewValue(fieldName, existsChecker, allowDuplicate = false) {\n const name = prompt(`Введите имя для нового ${fieldName}:`);\n if (!name || name.trim() === '') {\n return null;\n }\n\n const trimmedName = name.trim();\n\n if (!allowDuplicate && existsChecker(trimmedName)) {\n showSmartDesignMessage(`Ошибка: ${fieldName} с именем \"${trimmedName}\" уже существует в RDF данных`, 'error');\n return null;\n }\n\n return trimmedName;\n }\n\n /**\n * Обработчик изменения выбора в выпадающем списке\n * @param {HTMLSelectElement} selectElement - Элемент select\n * @param {string} fieldName - Название поля\n * @param {Function} existsChecker - Функция проверки существования\n * @param {boolean} allowDuplicate - Разрешить дубликаты\n */\n function handleSelectChange(selectElement, fieldName, existsChecker, allowDuplicate = false) {\n if (selectElement.value === '__NEW__') {\n const newValue = promptForNewValue(fieldName, existsChecker, allowDuplicate);\n if (newValue) {\n // Добавляем новый элемент в список и выбираем его\n const option = document.createElement('option');\n option.value = '__CUSTOM__:' + newValue;\n option.textContent = newValue + ' (новый)';\n selectElement.insertBefore(option, selectElement.options[2]); // После \"New\"\n selectElement.value = option.value;\n hideSmartDesignMessage();\n } else {\n // Сбрасываем выбор\n selectElement.value = '';\n }\n }\n }\n\n /**\n * Специализированный обработчик для поля Subject с учётом логики концепт/индивид\n * @param {HTMLSelectElement} selectElement - Элемент select\n */\n function handleSubjectSelectChange(selectElement) {\n const trigSelect = document.getElementById('smart-design-trig');\n const trigContext = getTrigContext(trigSelect ? trigSelect.value : '');\n\n if (selectElement.value === '__NEW__') {\n if (trigContext === 'ptree') {\n // В ptree можно создавать новые концепты Process\n const newValue = promptForNewValue('Process (концепт)', subjectExists, false);\n if (newValue) {\n const option = document.createElement('option');\n option.value = '__CUSTOM__:' + newValue;\n option.textContent = newValue + ' (новый концепт)';\n selectElement.insertBefore(option, selectElement.options[2]);\n selectElement.value = option.value;\n hideSmartDesignMessage();\n showSmartDesignMessage('Создаётся новый концепт Process в ptree. Не забудьте добавить rdf:type, rdfs:label и другие обязательные свойства.', 'info');\n } else {\n selectElement.value = '';\n }\n } else if (trigContext === 'rtree') {\n // В rtree можно создавать новые концепты Executor\n const newValue = promptForNewValue('Executor (концепт)', subjectExists, false);\n if (newValue) {\n const option = document.createElement('option');\n option.value = '__CUSTOM__:' + newValue;\n option.textContent = newValue + ' (новый концепт)';\n selectElement.insertBefore(option, selectElement.options[2]);\n selectElement.value = option.value;\n hideSmartDesignMessage();\n showSmartDesignMessage('Создаётся новый концепт Executor в rtree. Не забудьте добавить rdf:type и rdfs:label.', 'info');\n } else {\n selectElement.value = '';\n }\n } else if (trigContext === 'vadProcessDia') {\n // В VADProcessDia можно только выбирать существующие концепты из ptree\n // Показываем диалог выбора концепта\n showConceptSelectionDialog(selectElement);\n } else {\n // Нет выбранного TriG - стандартное поведение\n handleSelectChange(selectElement, 'Subject', subjectExists, false);\n }\n }\n }\n\n /**\n * Показывает диалог выбора концепта из ptree для создания индивида в VADProcessDia\n * @param {HTMLSelectElement} selectElement - Элемент select для Subject\n */\n function showConceptSelectionDialog(selectElement) {\n // Получаем список концептов Process из ptree\n const processConcepts = getProcessSubjects();\n\n if (processConcepts.length === 0) {\n showSmartDesignMessage('В ptree нет концептов Process. Сначала создайте концепт процесса в ptree.', 'error');\n selectElement.value = '';\n return;\n }\n\n // Создаём список для выбора\n const conceptNames = processConcepts.map(c => c.label).join('\\n');\n const selectedConcept = prompt(\n 'В VADProcessDia нельзя создавать новые имена - только индивиды существующих концептов.\\n\\n' +\n 'Выберите концепт из ptree (введите имя):\\n\\n' +\n conceptNames\n );\n\n if (selectedConcept && selectedConcept.trim()) {\n const trimmed = selectedConcept.trim();\n // Ищем соответствующий концепт\n const matchingConcept = processConcepts.find(c =>\n c.label === trimmed ||\n c.label.toLowerCase() === trimmed.toLowerCase() ||\n c.uri.endsWith('#' + trimmed) ||\n c.uri.endsWith('/' + trimmed)\n );\n\n if (matchingConcept) {\n // Выбираем существующий концепт\n selectElement.value = matchingConcept.uri;\n hideSmartDesignMessage();\n showSmartDesignMessage(\n `Выбран концепт \"${matchingConcept.label}\". Для создания индивида добавьте vad:isSubprocessTrig первым.`,\n 'info'\n );\n } else {\n showSmartDesignMessage(`Концепт \"${trimmed}\" не найден в ptree. Сначала создайте его там.`, 'error');\n selectElement.value = '';\n }\n } else {\n selectElement.value = '';\n }\n }\n\n /**\n * Проверяет, существует ли концепт Process в ptree\n * @param {string} processUri - URI процесса для проверки\n * @returns {boolean} - true если концепт существует в ptree\n */\n function isProcessConceptInPtree(processUri) {\n const processConcepts = getProcessSubjects();\n return processConcepts.some(c => c.uri === processUri);\n }\n\n // Добавляем обработчики событий для выпадающих списков Smart Design\n document.addEventListener('DOMContentLoaded', function() {\n const trigSelect = document.getElementById('smart-design-trig');\n const subjectSelect = document.getElementById('smart-design-subject');\n const subjectTypeSelect = document.getElementById('smart-design-subject-type');\n const predicateSelect = document.getElementById('smart-design-predicate');\n const objectSelect = document.getElementById('smart-design-object');\n\n if (trigSelect) {\n trigSelect.addEventListener('change', function() {\n // TriG теперь не имеет опции __NEW__, просто обновляем состояние полей\n updateSmartDesignFieldsState();\n // Перезаполняем Subject Type в зависимости от выбранного TriG\n populateSubjectTypeDropdown();\n // Перезаполняем Subject и Predicate в зависимости от выбранного TriG (vad:ptree или нет)\n updateSubjectPredicateDropdowns();\n });\n }\n\n if (subjectSelect) {\n subjectSelect.addEventListener('change', function() {\n // Используем специализированный обработчик для Subject с логикой концепт/индивид\n handleSubjectSelectChange(this);\n // При выборе существующего Subject, автоматически определяем его тип\n autoDetectSubjectType();\n });\n }\n\n if (subjectTypeSelect) {\n subjectTypeSelect.addEventListener('change', function() {\n // При изменении Subject Type, обновляем список допустимых Subject и Predicate\n updateSubjectsBySubjectType();\n updatePredicateBySubjectType();\n });\n }\n\n if (predicateSelect) {\n predicateSelect.addEventListener('change', function() {\n // При изменении Predicate, обновляем список допустимых Object\n updateObjectsByPredicate();\n });\n }\n\n if (objectSelect) {\n objectSelect.addEventListener('change', function() {\n // Для Object разрешаем дубликаты, новые значения - литералы\n handleSelectChange(this, 'Object', () => false, true);\n });\n }\n });\n\n /**\n * Автоматически определяет тип выбранного Subject и устанавливает его в Subject Type\n */\n function autoDetectSubjectType() {\n const subjectSelect = document.getElementById('smart-design-subject');\n const subjectTypeSelect = document.getElementById('smart-design-subject-type');\n\n if (!subjectSelect || !subjectTypeSelect) return;\n\n const subjectUri = subjectSelect.value;\n if (!subjectUri || subjectUri === '__NEW__' || subjectUri.startsWith('__CUSTOM__:')) {\n // Для нового Subject не меняем Subject Type\n return;\n }\n\n // Получаем типы данного Subject из кэша\n const types = nodeTypesCache[subjectUri] || [];\n\n // Проверяем каждый известный тип\n for (const type of types) {\n const prefixedType = getPrefixedName(type, currentPrefixes);\n // Ищем соответствующий тип в выпадающем списке\n const options = Array.from(subjectTypeSelect.options);\n const matchingOption = options.find(o =>\n o.value === prefixedType || o.value === type\n );\n if (matchingOption) {\n subjectTypeSelect.value = matchingOption.value;\n // Обновляем список Predicate\n updatePredicateBySubjectType();\n return;\n }\n }\n }\n\n /**\n * Получает значение из выпадающего списка (с учетом кастомных значений)\n * @param {string} selectId - ID элемента select\n * @returns {string|null} - URI или кастомное значение\n */\n function getSmartDesignValue(selectId) {\n const select = document.getElementById(selectId);\n if (!select || !select.value || select.value === '__NEW__') {\n return null;\n }\n\n if (select.value.startsWith('__CUSTOM__:')) {\n return select.value.substring('__CUSTOM__:'.length);\n }\n\n return select.value;\n }\n\n /**\n * Проверяет, является ли значение в выпадающем списке кастомным (новым литералом)\n * @param {string} selectId - ID элемента select\n * @returns {boolean} - true если это кастомное значение\n */\n function isSmartDesignValueCustom(selectId) {\n const select = document.getElementById(selectId);\n if (!select || !select.value) {\n return false;\n }\n return select.value.startsWith('__CUSTOM__:');\n }\n\n /**\n * Форматирует URI или значение для SPARQL запроса\n * @param {string} value - Значение для форматирования\n * @param {boolean} isLiteral - Является ли значение литералом (для новых Object)\n * @returns {string} - Форматированное значение\n */\n function formatForSparql(value, isLiteral = false) {\n if (!value) return '';\n\n // Если это литерал, заключаем в кавычки\n if (isLiteral) {\n // Экранируем специальные символы в строке\n const escaped = value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n return `\"${escaped}\"`;\n }\n\n // Если это полный URI\n if (value.startsWith('http://') || value.startsWith('https://')) {\n return `<${value}>`;\n }\n\n // Если это префиксированное имя\n if (value.includes(':')) {\n return value;\n }\n\n // Если это кастомное значение без префикса, пробуем использовать vad: префикс\n if (currentPrefixes['vad']) {\n return `vad:${value}`;\n }\n\n // В крайнем случае создаем URI\n return `<http://example.org/vad#${value}>`;\n }\n\n /**\n * Проверяет, существует ли триплет в RDF данных\n * @param {string} subjectUri - URI субъекта\n * @param {string} predicateUri - URI предиката\n * @param {string} objectUri - URI объекта\n * @param {string} graphUri - URI графа (опционально)\n * @returns {boolean}\n */\n function tripleExists(subjectUri, predicateUri, objectUri, graphUri = null) {\n return currentQuads.some(quad => {\n const subjectMatch = quad.subject.value === subjectUri;\n const predicateMatch = quad.predicate.value === predicateUri;\n const objectMatch = quad.object.value === objectUri;\n const graphMatch = !graphUri || (quad.graph && quad.graph.value === graphUri);\n return subjectMatch && predicateMatch && objectMatch && graphMatch;\n });\n }\n\n /**\n * Генерирует SPARQL INSERT запрос\n * @param {string} trigUri - URI TriG графа\n * @param {string} subjectUri - URI субъекта\n * @param {string} predicateUri - URI предиката\n * @param {string} objectUri - URI объекта\n * @param {boolean} isObjectLiteral - Является ли Object литералом\n * @returns {string} - SPARQL INSERT запрос\n */\n function generateInsertSparql(trigUri, subjectUri, predicateUri, objectUri, isObjectLiteral = false) {\n const prefixDeclarations = generateSparqlPrefixes(currentPrefixes);\n\n const subject = formatForSparql(subjectUri);\n const predicate = formatForSparql(predicateUri);\n const object = formatForSparql(objectUri, isObjectLiteral);\n const graph = formatForSparql(trigUri);\n\n return `${prefixDeclarations}INSERT DATA {\n GRAPH ${graph} {\n ${subject} ${predicate} ${object} .\n }\n}`;\n }\n\n /**\n * Генерирует SPARQL DELETE запрос\n * @param {string} trigUri - URI TriG графа\n * @param {string} subjectUri - URI субъекта\n * @param {string} predicateUri - URI предиката\n * @param {string} objectUri - URI объекта\n * @param {boolean} isObjectLiteral - Является ли Object литералом\n * @returns {string} - SPARQL DELETE запрос\n */\n function generateDeleteSparql(trigUri, subjectUri, predicateUri, objectUri, isObjectLiteral = false) {\n const prefixDeclarations = generateSparqlPrefixes(currentPrefixes);\n\n const subject = formatForSparql(subjectUri);\n const predicate = formatForSparql(predicateUri);\n const object = formatForSparql(objectUri, isObjectLiteral);\n const graph = formatForSparql(trigUri);\n\n return `${prefixDeclarations}DELETE DATA {\n GRAPH ${graph} {\n ${subject} ${predicate} ${object} .\n }\n}`;\n }\n\n /**\n * Форматирует URI или значение для SPARQL запроса в префиксной форме (prefix:name)\n * @param {string} value - Значение для форматирования\n * @param {boolean} isLiteral - Является ли значение литералом (для новых Object)\n * @returns {string} - Форматированное значение в префиксной форме\n */\n function formatForSparqlWithPrefix(value, isLiteral = false) {\n if (!value) return '';\n\n // Если это литерал, заключаем в кавычки\n if (isLiteral) {\n // Экранируем специальные символы в строке\n const escaped = value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n return `\"${escaped}\"`;\n }\n\n // Если это полный URI - преобразуем в префиксную форму\n if (value.startsWith('http://') || value.startsWith('https://')) {\n const prefixed = getPrefixedName(value, currentPrefixes);\n // Если удалось преобразовать в префиксную форму\n if (prefixed !== value && prefixed.includes(':')) {\n return prefixed;\n }\n // Если не удалось - возвращаем в угловых скобках\n return `<${value}>`;\n }\n\n // Если это уже префиксированное имя\n if (value.includes(':')) {\n return value;\n }\n\n // Если это кастомное значение без префикса, пробуем использовать vad: префикс\n if (currentPrefixes['vad']) {\n return `vad:${value}`;\n }\n\n // В крайнем случае создаем URI\n return `<http://example.org/vad#${value}>`;\n }\n\n /**\n * Генерирует SPARQL INSERT запрос с использованием префиксов (prefix:name)\n * @param {string} trigUri - URI TriG графа\n * @param {string} subjectUri - URI субъекта\n * @param {string} predicateUri - URI предиката\n * @param {string} objectUri - URI объекта\n * @param {boolean} isObjectLiteral - Является ли Object литералом\n * @returns {string} - SPARQL INSERT запрос с префиксами\n */\n function generateInsertSparqlWithPrefix(trigUri, subjectUri, predicateUri, objectUri, isObjectLiteral = false) {\n const prefixDeclarations = generateSparqlPrefixes(currentPrefixes);\n\n const subject = formatForSparqlWithPrefix(subjectUri);\n const predicate = formatForSparqlWithPrefix(predicateUri);\n const object = formatForSparqlWithPrefix(objectUri, isObjectLiteral);\n const graph = formatForSparqlWithPrefix(trigUri);\n\n return `${prefixDeclarations}INSERT DATA {\n GRAPH ${graph} {\n ${subject} ${predicate} ${object} .\n }\n}`;\n }\n\n /**\n * Обработчик кнопки \"Создать SPARQL (prefix)\" - генерирует INSERT SPARQL запрос с использованием префиксов\n */\n function smartDesignCreateWithPrefix() {\n hideSmartDesignMessage();\n\n // Сбрасываем флаг New TriG, т.к. создаем обычный SPARQL запрос\n isNewTrigQuery = false;\n updateSimpleTripleButtonState();\n\n const trigValue = getSmartDesignValue('smart-design-trig');\n const subjectValue = getSmartDesignValue('smart-design-subject');\n const predicateValue = getSmartDesignValue('smart-design-predicate');\n const objectValue = getSmartDesignValue('smart-design-object');\n const isObjectLiteral = isSmartDesignValueCustom('smart-design-object');\n\n if (!trigValue || !subjectValue || !predicateValue || !objectValue) {\n showSmartDesignMessage('Ошибка: Заполните все четыре поля (TriG, Subject, Predicate, Object)', 'error');\n return;\n }\n\n // Проверка на дубликаты (поддерживает как полные URI, так и prefixed names)\n const duplicate = findDuplicateTriple(subjectValue, predicateValue, objectValue);\n if (duplicate) {\n showSmartDesignMessage(`Ошибка: Триплет уже существует в графе \"${duplicate.graphLabel}\"`, 'error');\n return;\n }\n\n // Проверка: если TriG НЕ vad:ptree, но субъект vad:Process и предикат из PTREE_PREDICATES - ошибка\n const isPtreeTrig = trigValue === 'vad:ptree' ||\n trigValue === 'http://example.org/vad#ptree' ||\n trigValue.endsWith('#ptree');\n\n if (!isPtreeTrig) {\n // Преобразуем subjectValue в URI для проверки типа\n let subjectUri = subjectValue;\n for (const [prefix, namespace] of Object.entries(currentPrefixes)) {\n if (subjectValue.startsWith(prefix + ':')) {\n subjectUri = namespace + subjectValue.substring(prefix.length + 1);\n break;\n }\n }\n\n const subjectIsProcess = isSubjectVadProcess(subjectUri);\n const predicateIsPtree = isPtreePredicate(predicateValue);\n\n if (subjectIsProcess && predicateIsPtree) {\n const ptreePredicatesList = PTREE_PREDICATES\n .filter(p => !p.startsWith('http'))\n .join(', ');\n showSmartDesignMessage(\n `Ошибка: Для субъекта типа vad:Process с предикатом из списка PTREE_PREDICATES ` +\n `(${ptreePredicatesList}) триплет должен быть добавлен в граф vad:ptree, а не в \"${getPrefixedName(trigValue, currentPrefixes) || trigValue}\"`,\n 'error'\n );\n return;\n }\n }\n\n // Проверяем, что триплет добавляется в правильный граф (без автоматического перенаправления)\n // Если субъект является vad:TypeProcess и предикат из PTREE_PREDICATES или rdf:type vad:TypeProcess,\n // триплет должен быть добавлен только в vad:ptree\n const expectedTargetGraph = determineTargetGraph(subjectValue, predicateValue, trigValue, objectValue);\n\n // Нормализуем оба значения к prefixed name для корректного сравнения\n const trigLabel = getPrefixedName(trigValue, currentPrefixes) || trigValue;\n const expectedLabel = getPrefixedName(expectedTargetGraph, currentPrefixes) || expectedTargetGraph;\n\n if (expectedLabel !== trigLabel) {\n showSmartDesignMessage(\n `Ошибка: Данный триплет должен быть добавлен в граф \"${expectedLabel}\", а не в \"${trigLabel}\". ` +\n `Пожалуйста, выберите корректный TriG.`,\n 'error'\n );\n return;\n }\n\n // Генерируем SPARQL с использованием префиксов\n const sparqlQuery = generateInsertSparqlWithPrefix(trigValue, subjectValue, predicateValue, objectValue, isObjectLiteral);\n\n const resultTextarea = document.getElementById('result-sparql-query');\n if (resultTextarea) {\n resultTextarea.value = sparqlQuery;\n }\n\n // Формируем сообщение об успешной генерации\n showSmartDesignMessage('INSERT SPARQL запрос с prefix:name успешно сгенерирован.', 'success');\n }\n\n /**\n * Обработчик кнопки \"Создать\" - генерирует INSERT SPARQL запрос\n */\n function smartDesignCreate() {\n hideSmartDesignMessage();\n\n // Сбрасываем флаг New TriG, т.к. создаем обычный SPARQL запрос\n isNewTrigQuery = false;\n updateSimpleTripleButtonState();\n\n const trigValue = getSmartDesignValue('smart-design-trig');\n const subjectValue = getSmartDesignValue('smart-design-subject');\n const predicateValue = getSmartDesignValue('smart-design-predicate');\n const objectValue = getSmartDesignValue('smart-design-object');\n const isObjectLiteral = isSmartDesignValueCustom('smart-design-object');\n\n if (!trigValue || !subjectValue || !predicateValue || !objectValue) {\n showSmartDesignMessage('Ошибка: Заполните все четыре поля (TriG, Subject, Predicate, Object)', 'error');\n return;\n }\n\n // Проверка на дубликаты (поддерживает как полные URI, так и prefixed names)\n const duplicate = findDuplicateTriple(subjectValue, predicateValue, objectValue);\n if (duplicate) {\n showSmartDesignMessage(`Ошибка: Триплет уже существует в графе \"${duplicate.graphLabel}\"`, 'error');\n return;\n }\n\n // Проверка: если TriG НЕ vad:ptree, но субъект vad:Process и предикат из PTREE_PREDICATES - ошибка\n const isPtreeTrig = trigValue === 'vad:ptree' ||\n trigValue === 'http://example.org/vad#ptree' ||\n trigValue.endsWith('#ptree');\n\n if (!isPtreeTrig) {\n // Преобразуем subjectValue в URI для проверки типа\n let subjectUri = subjectValue;\n for (const [prefix, namespace] of Object.entries(currentPrefixes)) {\n if (subjectValue.startsWith(prefix + ':')) {\n subjectUri = namespace + subjectValue.substring(prefix.length + 1);\n break;\n }\n }\n\n const subjectIsProcess = isSubjectVadProcess(subjectUri);\n const predicateIsPtree = isPtreePredicate(predicateValue);\n\n if (subjectIsProcess && predicateIsPtree) {\n const ptreePredicatesList = PTREE_PREDICATES\n .filter(p => !p.startsWith('http'))\n .join(', ');\n showSmartDesignMessage(\n `Ошибка: Для субъекта типа vad:Process с предикатом из списка PTREE_PREDICATES ` +\n `(${ptreePredicatesList}) триплет должен быть добавлен в граф vad:ptree, а не в \"${getPrefixedName(trigValue, currentPrefixes) || trigValue}\"`,\n 'error'\n );\n return;\n }\n }\n\n // Проверяем, что триплет добавляется в правильный граф (без автоматического перенаправления)\n // Если субъект является vad:TypeProcess и предикат из PTREE_PREDICATES или rdf:type vad:TypeProcess,\n // триплет должен быть добавлен только в vad:ptree\n const expectedTargetGraph = determineTargetGraph(subjectValue, predicateValue, trigValue, objectValue);\n\n // Нормализуем оба значения к prefixed name для корректного сравнения\n const trigLabel = getPrefixedName(trigValue, currentPrefixes) || trigValue;\n const expectedLabel = getPrefixedName(expectedTargetGraph, currentPrefixes) || expectedTargetGraph;\n\n if (expectedLabel !== trigLabel) {\n showSmartDesignMessage(\n `Ошибка: Данный триплет должен быть добавлен в граф \"${expectedLabel}\", а не в \"${trigLabel}\". ` +\n `Пожалуйста, выберите корректный TriG.`,\n 'error'\n );\n return;\n }\n\n const sparqlQuery = generateInsertSparql(trigValue, subjectValue, predicateValue, objectValue, isObjectLiteral);\n\n const resultTextarea = document.getElementById('result-sparql-query');\n if (resultTextarea) {\n resultTextarea.value = sparqlQuery;\n }\n\n // Формируем сообщение об успешной генерации\n let successMessage = 'INSERT SPARQL запрос успешно сгенерирован';\n showSmartDesignMessage(successMessage, 'success');\n }\n\n /**\n * Обработчик кнопки \"Удалить триплет\" - генерирует DELETE SPARQL запрос с проверкой существования\n */\n function smartDesignDelete() {\n hideSmartDesignMessage();\n\n // Сбрасываем флаг New TriG, т.к. создаем обычный SPARQL запрос\n isNewTrigQuery = false;\n updateSimpleTripleButtonState();\n\n const trigValue = getSmartDesignValue('smart-design-trig');\n const subjectValue = getSmartDesignValue('smart-design-subject');\n const predicateValue = getSmartDesignValue('smart-design-predicate');\n const objectValue = getSmartDesignValue('smart-design-object');\n const isObjectLiteral = isSmartDesignValueCustom('smart-design-object');\n\n if (!trigValue || !subjectValue || !predicateValue || !objectValue) {\n showSmartDesignMessage('Ошибка: Заполните все четыре поля (TriG, Subject, Predicate, Object)', 'error');\n return;\n }\n\n // Проверяем существование триплета\n // Нужно преобразовать значения в полные URI для проверки\n let subjectUri = subjectValue;\n let predicateUri = predicateValue;\n let objectUri = objectValue;\n let graphUri = trigValue;\n\n // Если значение содержит префикс, преобразуем в полный URI\n for (const [prefix, namespace] of Object.entries(currentPrefixes)) {\n if (subjectValue.startsWith(prefix + ':')) {\n subjectUri = namespace + subjectValue.substring(prefix.length + 1);\n }\n if (predicateValue.startsWith(prefix + ':')) {\n predicateUri = namespace + predicateValue.substring(prefix.length + 1);\n }\n if (objectValue.startsWith(prefix + ':')) {\n objectUri = namespace + objectValue.substring(prefix.length + 1);\n }\n if (trigValue.startsWith(prefix + ':')) {\n graphUri = namespace + trigValue.substring(prefix.length + 1);\n }\n }\n\n // Проверяем существование триплета в данных\n const exists = tripleExists(subjectUri, predicateUri, objectUri, graphUri);\n\n if (!exists) {\n showSmartDesignMessage('Ошибка: Указанный триплет (утверждение) не найден в RDF данных', 'error');\n return;\n }\n\n const sparqlQuery = generateDeleteSparql(trigValue, subjectValue, predicateValue, objectValue, isObjectLiteral);\n\n const resultTextarea = document.getElementById('result-sparql-query');\n if (resultTextarea) {\n resultTextarea.value = sparqlQuery;\n }\n\n showSmartDesignMessage('DELETE SPARQL запрос успешно сгенерирован', 'success');\n }\n\n /**\n * Обработчик кнопки \"Очистить\" - очищает все поля\n */\n function smartDesignClear() {\n hideSmartDesignMessage();\n\n const trigSelect = document.getElementById('smart-design-trig');\n const subjectSelect = document.getElementById('smart-design-subject');\n const predicateSelect = document.getElementById('smart-design-predicate');\n const objectSelect = document.getElementById('smart-design-object');\n const resultTextarea = document.getElementById('result-sparql-query');\n\n if (trigSelect) trigSelect.value = '';\n if (subjectSelect) subjectSelect.value = '';\n if (predicateSelect) predicateSelect.value = '';\n if (objectSelect) objectSelect.value = '';\n if (resultTextarea) resultTextarea.value = '';\n\n // Сбрасываем флаг New TriG\n isNewTrigQuery = false;\n updateSimpleTripleButtonState();\n\n // Перезаполняем выпадающие списки (удаляем добавленные кастомные значения)\n populateSmartDesignDropdowns();\n\n showSmartDesignMessage('Все поля очищены', 'info');\n }\n\n /**\n * Обработчик кнопки \"Применить SPARQL\" - применяет запрос к RDF данным\n */\n async function smartDesignApply() {\n hideResultSparqlMessage();\n\n const resultTextarea = document.getElementById('result-sparql-query');\n if (!resultTextarea || !resultTextarea.value.trim()) {\n showResultSparqlMessage('Ошибка: Сначала сгенерируйте SPARQL запрос (нажмите \"Создать SPARQL\" или \"Удалить триплет\")', 'error');\n return;\n }\n\n const sparqlQuery = resultTextarea.value.trim();\n const rdfInput = document.getElementById('rdf-input');\n\n if (!rdfInput) {\n showResultSparqlMessage('Ошибка: Не найдено поле RDF данных', 'error');\n return;\n }\n\n // Определяем тип запроса (INSERT или DELETE)\n const isInsert = sparqlQuery.includes('INSERT DATA');\n const isDelete = sparqlQuery.includes('DELETE DATA');\n\n if (!isInsert && !isDelete) {\n showResultSparqlMessage('Ошибка: Неизвестный тип SPARQL запроса', 'error');\n return;\n }\n\n // Паттерн для извлечения URI/префиксного имени (поддерживает <uri> и prefix:name)\n const uriPattern = '(?:<[^>]+>|[a-zA-Z_][a-zA-Z0-9_]*:[a-zA-Z0-9_-]+)';\n // Паттерн для извлечения объекта (может быть URI, префиксное имя или литерал в кавычках)\n const objectPattern = '(?:<[^>]+>|[a-zA-Z_][a-zA-Z0-9_]*:[a-zA-Z0-9_-]+|\"[^\"]*\")';\n\n // Извлекаем GRAPH и триплеты из запроса\n const graphMatch = sparqlQuery.match(new RegExp(`GRAPH\\\\s+(${uriPattern})\\\\s*\\\\{([\\\\s\\\\S]*?)\\\\}`, 'i'));\n\n
[2026-01-26T19:23:56.117Z] [INFO] if (!graphMatch) {\n showResultSparqlMessage('Ошибка: Не удалось распознать GRAPH в SPARQL запросе', 'error');\n return;\n }\n\n const graphRef = graphMatch[1];\n const graphBody = graphMatch[2];\n\n // Извлекаем триплеты из тела GRAPH\n const triplePattern = new RegExp(`(${uriPattern})\\\\s+(${uriPattern})\\\\s+(${objectPattern})\\\\s*\\\\.`, 'g');\n const triples = [];\n let tripleMatch;\n while ((tripleMatch = triplePattern.exec(graphBody)) !== null) {\n triples.push({\n subject: tripleMatch[1],\n predicate: tripleMatch[2],\n object: tripleMatch[3]\n });\n }\n\n if (triples.length === 0) {\n showResultSparqlMessage('Ошибка: Не удалось распознать триплеты в SPARQL запросе', 'error');\n return;\n }\n\n // Конвертируем URI/префикс в префиксную форму для RDF текста\n function toPrefixed(ref) {\n if (ref.startsWith('<') && ref.endsWith('>')) {\n // Полный URI - конвертируем в префиксную форму\n const cleanUri = ref.slice(1, -1);\n return getPrefixedName(cleanUri, currentPrefixes);\n }\n // Уже в префиксной форме или литерал\n return ref;\n }\n\n const graphPrefixed = toPrefixed(graphRef);\n\n // Получаем текущий RDF текст\n let currentRdfText = rdfInput.value;\n\n if (isInsert) {\n // Проверка на дубликаты для каждого триплета (кроме создания нового TriG)\n if (!isNewTrigQuery) {\n const duplicateTriples = [];\n for (const triple of triples) {\n const subjectPrefixed = toPrefixed(triple.subject);\n const predicatePrefixed = toPrefixed(triple.predicate);\n const objectPrefixed = toPrefixed(triple.object);\n\n const duplicate = findDuplicateTriple(subjectPrefixed, predicatePrefixed, objectPrefixed);\n if (duplicate) {\n duplicateTriples.push({\n triple: `${subjectPrefixed} ${predicatePrefixed} ${objectPrefixed}`,\n graph: duplicate.graphLabel\n });\n }\n }\n\n // Если есть дубликаты, показываем ошибку\n if (duplicateTriples.length > 0) {\n const duplicateMessages = duplicateTriples.map(d => `\"${d.triple}\" (в графе \"${d.graph}\")`).join(', ');\n showResultSparqlMessage(`Ошибка: Обнаружены дубликаты триплетов: ${duplicateMessages}`, 'error');\n return;\n }\n }\n\n // Формируем триплеты для вставки\n const triplesForInsert = triples.map(t => {\n const subjectPrefixed = toPrefixed(t.subject);\n const predicatePrefixed = toPrefixed(t.predicate);\n const objectPrefixed = toPrefixed(t.object);\n return ` ${subjectPrefixed} ${predicatePrefixed} ${objectPrefixed} .`;\n }).join('\\n');\n\n // Ищем секцию графа и добавляем триплеты В КОНЕЦ блока (перед закрывающей скобкой)\n let graphEndPattern = new RegExp(`(${escapeRegex(graphPrefixed)}\\\\s*\\\\{[^}]*)(\\\\s*\\\\})`, 's');\n let graphTextMatch = currentRdfText.match(graphEndPattern);\n\n if (!graphTextMatch) {\n // Попробуем найти по полному URI (если в RDF тексте используется полный URI)\n graphEndPattern = new RegExp(`(${escapeRegex(graphRef)}\\\\s*\\\\{[^}]*)(\\\\s*\\\\})`, 's');\n graphTextMatch = currentRdfText.match(graphEndPattern);\n }\n\n if (graphTextMatch) {\n // Добавляем триплеты в КОНЕЦ существующего графа (перед закрывающей скобкой)\n currentRdfText = currentRdfText.replace(graphEndPattern, `$1\\n${triplesForInsert}$2`);\n } else {\n // Создаем новый граф с триплетами\n const newGraph = `\\n\\n${graphPrefixed} {\\n${triplesForInsert}\\n}`;\n currentRdfText += newGraph;\n }\n\n rdfInput.value = currentRdfText;\n\n // Переразбираем RDF данные для обновления currentQuads и кэша типов\n // Это позволит корректно обнаруживать дубликаты при последующих операциях\n await reparseRdfData();\n\n // Сбрасываем флаг New TriG после успешного добавления,\n // чтобы при повторном нажатии работала проверка на дубликаты\n isNewTrigQuery = false;\n updateSimpleTripleButtonState();\n\n showResultSparqlMessage('Триплеты успешно добавлены в RDF данные. Нажмите \"Показать\" для обновления графа.', 'success');\n\n } else if (isDelete) {\n // Удаляем триплеты из RDF данных\n let wasDeleted = false;\n let newRdfText = currentRdfText;\n\n for (const triple of triples) {\n const subjectPrefixed = toPrefixed(triple.subject);\n const predicatePrefixed = toPrefixed(triple.predicate);\n const objectPrefixed = toPrefixed(triple.object);\n\n // Пробуем найти и удалить в префиксной форме\n let deletePattern = new RegExp(`\\\\s*${escapeRegex(subjectPrefixed)}\\\\s+${escapeRegex(predicatePrefixed)}\\\\s+${escapeRegex(objectPrefixed)}\\\\s*[;.]`, 'g');\n const prevText = newRdfText;\n newRdfText = newRdfText.replace(deletePattern, '');\n\n if (newRdfText === prevText) {\n // Попробуем с исходными значениями (на случай, если в RDF текст попали полные URI)\n deletePattern = new RegExp(`\\\\s*${escapeRegex(triple.subject)}\\\\s+${escapeRegex(triple.predicate)}\\\\s+${escapeRegex(triple.object)}\\\\s*[;.]`, 'g');\n newRdfText = newRdfText.replace(deletePattern, '');\n }\n\n if (newRdfText !== prevText) {\n wasDeleted = true;\n }\n }\n\n if (!wasDeleted) {\n showResultSparqlMessage('Предупреждение: Триплеты не найдены в текстовом представлении RDF данных', 'info');\n } else {\n rdfInput.value = newRdfText;\n\n // Переразбираем RDF данные для обновления currentQuads и кэша типов\n await reparseRdfData();\n\n showResultSparqlMessage('Триплеты успешно удалены из RDF данных. Нажмите \"Показать\" для обновления графа.', 'success');\n }\n }\n\n // Поля Smart Design и Result in SPARQL не очищаются после применения\n // Очистка происходит только по кнопке \"Очистить\"\n }\n\n /**\n * Переразбирает RDF данные из текстового поля и обновляет currentQuads и nodeTypesCache.\n * Это необходимо после добавления или удаления триплетов, чтобы проверка на дубликаты\n * работала корректно для последующих операций.\n * @returns {Promise<boolean>} - true если парсинг прошел успешно\n */\n async function reparseRdfData() {\n try {\n const rdfInput = document.getElementById('rdf-input');\n if (!rdfInput || !rdfInput.value.trim()) return false;\n\n const inputFormat = document.getElementById('input-format').value;\n const parser = new N3.Parser({ format: inputFormat });\n const quads = [];\n let prefixes = {};\n\n await new Promise((resolve, reject) => {\n parser.parse(rdfInput.value, (error, quad, parsedPrefixes) => {\n if (error) {\n reject(error);\n return;\n }\n if (quad) {\n quads.push(quad);\n } else {\n if (parsedPrefixes) {\n prefixes = parsedPrefixes;\n }\n resolve();\n }\n });\n });\n\n // Обновляем глобальные переменные\n currentQuads = quads;\n currentPrefixes = prefixes;\n\n // Перестраиваем кэш типов узлов\n buildNodeTypesCache(currentQuads, currentPrefixes);\n\n // Обновляем иерархию TriG если в режиме VAD TriG\n if (currentMode === 'vad-trig') {\n const hierarchyResult = parseTriGHierarchy(quads, prefixes);\n if (hierarchyResult.valid) {\n trigHierarchy = hierarchyResult.hierarchy;\n }\n }\n\n return true;\n } catch (error) {\n console.error('Ошибка при переразборе RDF данных:', error);\n return false;\n }\n }\n\n /**\n * Экранирует специальные символы регулярного выражения\n * @param {string} str - Строка для экранирования\n * @returns {string} - Экранированная строка\n */\n function escapeRegex(str) {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n }\n\n /**\n * LITERAL_PREDICATES - Предикаты, для которых объект должен быть литералом (единственное значение)\n * При добавлении нового значения для этих предикатов, старое значение должно быть заменено\n */\n const LITERAL_PREDICATES = [\n 'rdfs:label',\n 'http://www.w3.org/2000/01/rdf-schema#label',\n 'dcterms:description',\n 'http://purl.org/dc/terms/description',\n 'dcterms:title',\n 'http://purl.org/dc/terms/title',\n 'rdfs:comment',\n 'http://www.w3.org/2000/01/rdf-schema#comment'\n ];\n\n /**\n * Проверяет, является ли предикат литеральным (требующим единственного значения)\n * @param {string} predicateUri - URI или prefixed name предиката\n * @returns {boolean}\n */\n function isLiteralPredicate(predicateUri) {\n return LITERAL_PREDICATES.some(allowed =>\n predicateUri === allowed || predicateUri.endsWith('#' + allowed.split(':')[1])\n );\n }\n\n /**\n * Находит существующее значение литерального предиката для субъекта\n * @param {string} subjectValue - URI или prefixed name субъекта\n * @param {string} predicateValue - URI или prefixed name предиката\n * @returns {Object|null} - { value: string, graphUri: string } если найдено, иначе null\n */\n function findExistingLiteralValue(subjectValue, predicateValue) {\n // Преобразуем prefixed names в полные URI\n let subjectUri = subjectValue;\n let predicateUri = predicateValue;\n\n for (const [prefix, namespace] of Object.entries(currentPrefixes)) {\n if (subjectValue.startsWith(prefix + ':')) {\n subjectUri = namespace + subjectValue.substring(prefix.length + 1);\n }\n if (predicateValue.startsWith(prefix + ':')) {\n predicateUri = namespace + predicateValue.substring(prefix.length + 1);\n }\n }\n\n const subjectPrefixed = getPrefixedName(subjectUri, currentPrefixes);\n const predicatePrefixed = getPrefixedName(predicateUri, currentPrefixes);\n\n for (const quad of currentQuads) {\n const qSubjectUri = quad.subject.value;\n const qPredicateUri = quad.predicate.value;\n const qSubjectPrefixed = getPrefixedName(qSubjectUri, currentPrefixes);\n const qPredicatePrefixed = getPrefixedName(qPredicateUri, currentPrefixes);\n\n const subjectMatch = (subjectUri === qSubjectUri) || (subjectPrefixed === qSubjectPrefixed);\n const predicateMatch = (predicateUri === qPredicateUri) || (predicatePrefixed === qPredicatePrefixed);\n\n if (subjectMatch && predicateMatch && quad.object.termType === 'Literal') {\n const graphUri = quad.graph ? quad.graph.value : null;\n return { value: quad.object.value, graphUri: graphUri };\n }\n }\n\n return null;\n }\n\n /**\n * Показывает сообщение в панели Result in SPARQL\n * @param {string} message - Текст сообщения\n * @param {string} type - Тип: 'success', 'error', 'info'\n */\n function showResultSparqlMessage(message, type = 'info') {\n const messageDiv = document.getElementById('result-sparql-message');\n if (messageDiv) {\n messageDiv.textContent = message;\n messageDiv.className = 'smart-design-message ' + type;\n messageDiv.style.display = 'block';\n }\n }\n\n /**\n * Скрывает сообщение в панели Result in SPARQL\n */\n function hideResultSparqlMessage() {\n const messageDiv = document.getElementById('result-sparql-message');\n if (messageDiv) {\n messageDiv.style.display = 'none';\n }\n }\n\n /**\n * Обработчик кнопки \"Применить как Shorthand Triple\"\n * Добавляет триплет в shorthand форме (через запятую)\n */\n function smartDesignApplyShorthand() {\n const resultTextarea = document.getElementById('result-sparql-query');\n if (!resultTextarea || !resultTextarea.value.trim()) {\n showResultSparqlMessage('Ошибка: Сначала сгенерируйте SPARQL запрос (нажмите \"Создать SPARQL\" или \"Удалить\")', 'error');\n return;\n }\n\n const sparqlQuery = resultTextarea.value.trim();\n const rdfInput = document.getElementById('rdf-input');\n\n if (!rdfInput) {\n showResultSparqlMessage('Ошибка: Не найдено поле RDF данных', 'error');\n return;\n }\n\n // Определяем тип запроса (только INSERT поддерживается для shorthand)\n const isInsert = sparqlQuery.includes('INSERT DATA');\n const isDelete = sparqlQuery.includes('DELETE DATA');\n\n if (isDelete) {\n showResultSparqlMessage('Ошибка: Shorthand Triple режим не поддерживает DELETE операции. Используйте \"Применить как Simple Triple\".', 'error');\n return;\n }\n\n if (!isInsert) {\n showResultSparqlMessage('Ошибка: Неизвестный тип SPARQL запроса', 'error');\n return;\n }\n\n // Паттерн для извлечения URI/префиксного имени\n const uriPattern = '(?:<[^>]+>|[a-zA-Z_][a-zA-Z0-9_]*:[a-zA-Z0-9_.-]+)';\n const objectPattern = '(?:<[^>]+>|[a-zA-Z_][a-zA-Z0-9_]*:[a-zA-Z0-9_.-]+|\"[^\"]*\")';\n\n // Извлекаем GRAPH и триплеты из запроса\n const graphMatch = sparqlQuery.match(new RegExp(`GRAPH\\\\s+(${uriPattern})\\\\s*\\\\{([\\\\s\\\\S]*?)\\\\}`, 'i'));\n\n if (!graphMatch) {\n showResultSparqlMessage('Ошибка: Не удалось распознать GRAPH в SPARQL запросе', 'error');\n return;\n }\n\n const graphRef = graphMatch[1];\n const graphBody = graphMatch[2];\n\n // Извлекаем триплеты из тела GRAPH\n const triplePattern = new RegExp(`(${uriPattern})\\\\s+(${uriPattern})\\\\s+(${objectPattern})\\\\s*\\\\.`, 'g');\n const triples = [];\n let tripleMatch;\n while ((tripleMatch = triplePattern.exec(graphBody)) !== null) {\n triples.push({\n subject: tripleMatch[1],\n predicate: tripleMatch[2],\n object: tripleMatch[3]\n });\n }\n\n if (triples.length === 0) {\n showResultSparqlMessage('Ошибка: Не удалось распознать триплеты в SPARQL запросе', 'error');\n return;\n }\n\n // Конвертируем URI/префикс в префиксную форму\n function toPrefixed(ref) {\n if (ref.startsWith('<') && ref.endsWith('>')) {\n const cleanUri = ref.slice(1, -1);\n return getPrefixedName(cleanUri, currentPrefixes);\n }\n return ref;\n }\n\n // Проверка на дубликаты для каждого триплета\n const duplicateTriples = [];\n for (const triple of triples) {\n const subjectPrefixed = toPrefixed(triple.subject);\n const predicatePrefixed = toPrefixed(triple.predicate);\n const objectPrefixed = toPrefixed(triple.object);\n\n const duplicate = findDuplicateTriple(subjectPrefixed, predicatePrefixed, objectPrefixed);\n if (duplicate) {\n duplicateTriples.push({\n triple: `${subjectPrefixed} ${predicatePrefixed} ${objectPrefixed}`,\n graph: duplicate.graphLabel\n });\n }\n }\n\n // Если есть дубликаты, показываем ошибку\n if (duplicateTriples.length > 0) {\n const duplicateMessages = duplicateTriples.map(d => `\"${d.triple}\" (в графе \"${d.graph}\")`).join(', ');\n showResultSparqlMessage(`Ошибка: Обнаружены дубликаты триплетов: ${duplicateMessages}`, 'error');\n return;\n }\n\n const graphPrefixed = toPrefixed(graphRef);\n let currentRdfText = rdfInput.value;\n let successCount = 0;\n let replaceMessages = [];\n\n // Специальная обработка для создания нового TriG в shorthand формате\n if (isNewTrigQuery) {\n // Группируем триплеты по субъекту для shorthand записи\n const triplesBySubject = new Map();\n for (const triple of triples) {\n const subjectPrefixed = toPrefixed(triple.subject);\n const predicatePrefixed = toPrefixed(triple.predicate);\n const objectPrefixed = toPrefixed(triple.object);\n\n if (!triplesBySubject.has(subjectPrefixed)) {\n triplesBySubject.set(subjectPrefixed, []);\n }\n triplesBySubject.get(subjectPrefixed).push({ predicate: predicatePrefixed, object: objectPrefixed });\n }\n\n // Формируем shorthand запись для каждого субъекта\n let shorthandTriples = [];\n for (const [subject, predicateObjects] of triplesBySubject) {\n if (predicateObjects.length === 1) {\n // Один предикат-объект - простая запись\n shorthandTriples.push(` ${subject} ${predicateObjects[0].predicate} ${predicateObjects[0].object} .`);\n } else {\n // Несколько предикатов-объектов - shorthand через точку с запятой\n const firstPO = predicateObjects[0];\n let shorthandLine = ` ${subject} ${firstPO.predicate} ${firstPO.object}`;\n for (let i = 1; i < predicateObjects.length; i++) {\n shorthandLine += ` ;\\n ${predicateObjects[i].predicate} ${predicateObjects[i].object}`;\n }\n shorthandLine += ' .';\n shorthandTriples.push(shorthandLine);\n }\n }\n\n // Создаем новый граф с shorthand записями\n const newGraph = `\\n\\n${graphPrefixed} {\\n${shorthandTriples.join('\\n')}\\n}`;\n currentRdfText += newGraph;\n rdfInput.value = currentRdfText;\n\n // Переразбираем RDF данные для обновления currentQuads и кэша типов\n // Это позволит корректно обнаруживать дубликаты при последующих операциях\n reparseRdfData();\n\n // Сбрасываем флаг New TriG\n isNewTrigQuery = false;\n updateSimpleTripleButtonState();\n\n showResultSparqlMessage(`Новый TriG \"${graphPrefixed}\" успешно создан в shorthand формате. Нажмите \"Показать\" для обновления графа.`, 'success');\n return;\n }\n\n for (const triple of triples) {\n const subjectPrefixed = toPrefixed(triple.subject);\n const predicatePrefixed = toPrefixed(triple.predicate);\n const objectPrefixed = toPrefixed(triple.object);\n\n // Проверяем, является ли предикат литеральным\n const isLiteralPred = isLiteralPredicate(predicatePrefixed);\n\n if (isLiteralPred) {\n // Для литеральных предикатов - ищем и заменяем существующее значение\n const existingValue = findExistingLiteralValue(subjectPrefixed, predicatePrefixed);\n if (existingValue) {\n // Нужно удалить старое значение и добавить новое в shorthand стиле\n const oldValueEscaped = escapeRegex(`\"${existingValue.value}\"`);\n\n // Паттерн 1: Простой триплет \"subject predicate value\"\n let replacePattern = new RegExp(\n `(${escapeRegex(subjectPrefixed)}\\\\s+${escapeRegex(predicatePrefixed)}\\\\s+)${oldValueEscaped}`,\n 'g'\n );\n let newText = currentRdfText.replace(replacePattern, `$1${objectPrefixed}`);\n\n // Паттерн 2: Shorthand формат - предикат после точки с запятой \"... ; predicate value\"\n if (newText === currentRdfText) {\n replacePattern = new RegExp(\n `(;\\\\s*${escapeRegex(predicatePrefixed)}\\\\s+)${oldValueEscaped}`,\n 'g'\n );\n newText = currentRdfText.replace(replacePattern, `$1${objectPrefixed}`);\n }\n\n if (newText !== currentRdfText) {\n currentRdfText = newText;\n replaceMessages.push(`Прежнее значение \"${existingValue.value}\" заменено`);\n successCount++;\n continue;\n }\n }\n }\n\n // Сначала извлекаем содержимое целевого графа для проверки\n const graphContentPattern = new RegExp(`(${escapeRegex(graphPrefixed)}\\\\s*\\\\{)([\\\\s\\\\S]*?)(\\\\})`, 's');\n const graphContentMatch = currentRdfText.match(graphContentPattern);\n\n if (!graphContentMatch) {\n // Граф не найден - создаем новый граф с триплетом\n const newGraph = `\\n\\n${graphPrefixed} {\\n ${subjectPrefixed} ${predicatePrefixed} ${objectPrefixed} .\\n}`;\n currentRdfText += newGraph;\n successCount++;\n continue;\n }\n\n const graphContent = graphContentMatch[2];\n\n // Ищем существующий триплет с тем же subject и predicate В ПРЕДЕЛАХ ЦЕЛЕВОГО ГРАФА для добавления через запятую\n const subjectPredicateInGraphPattern = new RegExp(\n `(${escapeRegex(subjectPrefixed)}\\\\s+${escapeRegex(predicatePrefixed)}\\\\s+)([^;.]+)([;.])`,\n 'g'\n );\n\n const existingMatchInGraph = graphContent.match(subjectPredicateInGraphPattern);\n if (existingMatchInGraph) {\n // Добавляем новый объект через запятую В ПРЕДЕЛАХ ЦЕЛЕВОГО ГРАФА\n const newGraphContent = graphContent.replace(\n subjectPredicateInGraphPattern,\n `$1$2, ${objectPrefixed}$3`\n );\n currentRdfText = currentRdfText.replace(graphContentPattern, `$1${newGraphContent}$3`);\n successCount++;\n } else {\n // Ищем существующий субъект для добавления нового предиката В ПРЕДЕЛАХ ЦЕЛЕВОГО ГРАФА\n const subjectInGraphPattern = new RegExp(\n `(${escapeRegex(subjectPrefixed)}\\\\s+[^}]*?)([;.])(?=\\\\s|$|\\\\n)`,\n 's'\n );\n const subjectMatchInGraph = graphContent.match(subjectInGraphPattern);\n\n if (subjectMatchInGraph) {\n // Добавляем новый предикат и объект В ПРЕДЕЛАХ ЦЕЛЕВОГО ГРАФА\n const newGraphContent = graphContent.replace(\n subjectInGraphPattern,\n `$1 ;\\n ${predicatePrefixed} ${objectPrefixed}$2`\n );\n currentRdfText = currentRdfText.replace(graphContentPattern, `$1${newGraphContent}$3`);\n successCount++;\n } else {\n // Создаем новый субъект в целевом графе\n const newTriple = `\\n ${subjectPrefixed} ${predicatePrefixed} ${objectPrefixed} .`;\n const newGraphContent = graphContent + newTriple;\n currentRdfText = currentRdfText.replace(graphContentPattern, `$1${newGraphContent}$3`);\n successCount++;\n }\n }\n }\n\n if (successCount > 0) {\n rdfInput.value = currentRdfText;\n let message = `Shorthand Triple: ${successCount} значение(й) успешно добавлено.`;\n if (replaceMessages.length > 0) {\n message += ' ' + replaceMessages.join('. ') + '.';\n }\n message += ' Нажмите \"Показать\" для обновления графа.';\n showResultSparqlMessage(message, 'success');\n } else {\n showResultSparqlMessage('Не удалось добавить значения в shorthand формате', 'error');\n }\n\n // Поля Smart Design и Result in SPARQL не очищаются после применения\n // Очистка происходит только по кнопке \"Очистить\"\n }\n\n /**\n * Открывает модальное окно для создания нового TriG\n */\n function openNewTrigModal() {\n const modal = document.getElementById('new-trig-modal');\n const parentSelect = document.getElementById('new-trig-parent');\n\n // Заполняем список родителей существующими TriG\n parentSelect.innerHTML = '<option value=\"\">-- Выберите родителя (опционально) --</option>';\n const trigs = getAllTriGs();\n trigs.forEach(t => {\n const option = document.createElement('option');\n option.value = t.uri;\n const id = getPrefixedName(t.uri, currentPrefixes);\n const displayText = t.label && t.label !== id ? `${id} (${t.label})` : id;\n option.textContent = displayText;\n parentSelect.appendChild(option);\n });\n\n // Очищаем поля ввода\n document.getElementById('new-trig-id').value = '';\n document.getElementById('new-trig-label').value = '';\n\n // Очищаем выбор концепта процесса\n const processConceptSelect = document.getElementById('new-trig-process-concept');\n if (processConceptSelect) {\n processConceptSelect.value = '';\n }\n\n // Заполняем выпадающий список концептов процессов\n populateNewTrigProcessConcepts();\n\n modal.style.display = 'block';\n\n // Деактивируем основные поля Smart Design\n updateSmartDesignFieldsState();\n }\n\n /**\n * Закрывает модальное окно создания нового TriG\n */\n function closeNewTrigModal() {\n const modal = document.getElementById('new-trig-modal');\n modal.style.display = 'none';\n\n // Активируем основные поля Smart Design\n updateSmartDesignFieldsState();\n }\n\n /**\n * Создает новый TriG контейнер и генерирует SPARQL запрос\n * Использует выбранный концепт процесса для генерации ID\n */\n function createNewTrig() {\n const processConcept = document.getElementById('new-trig-process-concept').value;\n const trigId = document.getElementById('new-trig-id').value.trim();\n const label = document.getElementById('new-trig-label').value.trim();\n const parentUri = document.getElementById('new-trig-parent').value;\n\n // Проверяем, что выбран концепт процесса\n if (!processConcept) {\n alert('Ошибка: Выберите концепт процесса');\n return;\n }\n\n // Проверяем, что ID заполнен\n if (!trigId) {\n alert('Ошибка: ID нового TriG не может быть пустым');\n return;\n }\n\n // Получаем локальное имя процесса из URI\n // processConcept может быть в форме \"vad:ProcessX\" или \"<http://...#ProcessX>\"\n let processLocalName;\n if (processConcept.startsWith('vad:')) {\n processLocalName = processConcept.substring(4);\n } else if (processConcept.includes('#')) {\n processLocalName = processConcept.split('#').pop().replace('>', '');\n } else if (processConcept.includes('/')) {\n processLocalName = processConcept.split('/').pop().replace('>', '');\n } else {\n processLocalName = processConcept;\n }\n\n // Проверяем, не существует ли уже такой TriG\n if (trigExists(trigId)) {\n alert(`Ошибка: TriG с ID \"${trigId}\" уже существует. Выберите другой концепт процесса.`);\n return;\n }\n\n // Формируем URI нового TriG\n let trigUri;\n if (currentPrefixes['vad']) {\n trigUri = `vad:${trigId}`;\n } else {\n trigUri = `<http://example.org/vad#${trigId}>`;\n }\n\n // Генерируем SPARQL для создания TriG с нужными предикатами\n const prefixDeclarations = generateSparqlPrefixes(currentPrefixes);\n\n let triples = [];\n\n // Всегда добавляем rdf:type VADProcessDia (тип схемы процесса)\n triples.push(` ${trigUri} rdf:type vad:VADProcessDia .`);\n\n // rdfs:label (обязательно)\n if (label) {\n const escapedLabel = label.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n triples.push(` ${trigUri} rdfs:label \"${escapedLabel}\" .`);\n }\n\n // vad:hasParentTrig (опционально)\n if (parentUri) {\n // Преобразуем URI родителя в префиксную форму\n const parentPrefixed = getPrefixedName(parentUri, currentPrefixes);\n triples.push(` ${trigUri} vad:hasParentTrig ${parentPrefixed} .`);\n }\n\n // Добавляем автогенерируемые триплы для индивидуального процесса\n // Получаем полный URI триГа для функции generateAutoTriples\n const fullTrigUri = currentPrefixes['vad']\n ? currentPrefixes['vad'] + trigId\n : `http://example.org/vad#${trigId}`;\n\n // Получаем полный URI процесса\n const processUri = currentPrefixes['vad']\n ? currentPrefixes['vad'] + processLocalName\n : processConcept.replace(/^<|>$/g, '');\n\n // Получаем автоматические триплеты (возвращается строка)\n const autoTriplesStr = generateAutoTriples(processUri, fullTrigUri);\n // Добавляем автотриплеты напрямую в массив (они уже отформатированы)\n if (autoTriplesStr && autoTriplesStr.trim().length > 0) {\n triples.push(autoTriplesStr);\n }\n\n // Формируем URI процесса в префиксной форме\n const processPrefixed = getPrefixedName(processUri, currentPrefixes);\n\n // Генерируем комбинированный SPARQL запрос:\n // 1. DELETE vad:hasParentProcess из ptree (если есть) - правило взаимоисключения\n // 2. INSERT vad:hasTrig в ptree\n // 3. INSERT содержимое нового TriG графа\n const sparqlQuery = `${prefixDeclarations}# Правило: vad:hasTrig и vad:hasParentProcess - взаимоисключающие\n# При создании нового TriG для процесса удаляем vad:hasParentProcess\n\nDELETE {\n GRAPH vad:ptree {\n ${processPrefixed} vad:hasParentProcess ?oldParent .\n }\n}\nWHERE {\n GRAPH vad:ptree {\n ${processPrefixed} vad:hasParentProcess ?oldParent .\n }\n};\n\n# Добавляем связь процесса с новым TriG в дереве процессов\nINSERT DATA {\n GRAPH vad:ptree {\n ${processPrefixed} vad:hasTrig ${trigUri} .\n }\n};\n\n# Создаём новый TriG граф\nINSERT DATA {\n GRAPH ${trigUri} {\n${triples.join('\\n')}\n }\n}`;\n\n // Записываем SPARQL в поле результата\n const resultTextarea = document.getElementById('result-sparql-query');\n if (resultTextarea) {\n resultTextarea.value = sparqlQuery;\n }\n\n // Устанавливаем флаг, что это запрос New TriG\n isNewTrigQuery = true;\n updateSimpleTripleButtonState();\n\n closeNewTrigModal();\n showSmartDesignMessage('SPARQL запрос для создания нового TriG сгенерирован. Нажмите \"Применить как Simple Triple\" (отдельные строки) или \"Применить как Shorthand Triple\" (составная запись через ;).', 'success');\n }\n\n /**\n * Обновляет состояние кнопки \"Применить как Simple Triple\"\n * Обе кнопки активны для создания нового TriG\n */\n function updateSimpleTripleButtonState() {\n const simpleTripleBtn = document.querySelector('.apply-btn[onclick=\"smartDesignApply()\"]');\n if (simpleTripleBtn) {\n // Кнопка Simple Triple всегда активна\n simpleTripleBtn.disabled = false;\n simpleTripleBtn.style.opacity = '1';\n simpleTripleBtn.style.cursor = 'pointer';\n simpleTripleBtn.title = '';\n }\n }\n\n // ============================================================================\n // ФУНКЦИИ ДЛЯ УДАЛЕНИЯ TriG (Del TriG)\n // ============================================================================\n\n /**\n * Открывает модальное окно удаления TriG\n */\n function openDelTrigModal() {\n const modal = document.getElementById('del-trig-modal');\n const trigSelect = document.getElementById('del-trig-select');\n const warningDiv = document.getElementById('del-trig-warning');\n const confirmBtn = document.getElementById('del-trig-confirm-btn');\n\n // Очищаем предыдущее состояние\n warningDiv.style.display = 'none';\n confirmBtn.disabled = true;\n\n // Заполняем список TriG для удаления (только VADProcessDia)\n trigSelect.innerHTML = '<option value=\"\">-- Выберите TriG --</option>';\n\n const trigs = getAllTriGs();\n trigs.forEach(t => {\n // Пропускаем специальные графы (ptree, rtree)\n if (t.uri === PTREE_GRAPH_URI || t.uri === RTREE_GRAPH_URI) return;\n\n const option = document.createElement('option');\n option.value = t.uri;\n const id = getPrefixedName(t.uri, currentPrefixes);\n const displayText = t.label && t.label !== id ? `${id} (${t.label})` : id;\n option.textContent = displayText;\n trigSelect.appendChild(option);\n });\n\n // Очищаем информационный блок\n document.getElementById('del-trig-info').innerHTML = 'Выберите TriG для просмотра информации';\n\n modal.style.display = 'block';\n }\n\n /**\n * Закрывает модальное окно удаления TriG\n */\n function closeDelTrigModal() {\n const modal = document.getElementById('del-trig-modal');\n modal.style.display = 'none';\n }\n\n /**\n * Обновляет информацию о выбранном TriG для удаления\n */\n function updateDelTrigInfo() {\n const trigUri = document.getElementById('del-trig-select').value;\n const infoDiv = document.getElementById('del-trig-info');\n const warningDiv = document.getElementById('del-trig-warning');\n const confirmBtn = document.getElementById('del-trig-confirm-btn');\n\n if (!trigUri) {\n infoDiv.innerHTML = 'Выберите TriG для просмотра информации';\n warningDiv.style.display = 'none';\n confirmBtn.disabled = true;\n return;\n }\n\n // Получаем информацию о TriG\n const graphInfo = trigHierarchy[trigUri];\n if (!graphInfo) {\n infoDiv.innerHTML = 'Ошибка: TriG не найден';\n warningDiv.style.display = 'none';\n confirmBtn.disabled = true;\n return;\n }\n\n const trigLabel = getPrefixedName(trigUri, currentPrefixes);\n\n // Проверяем, есть ли дочерние TriG\n const childTrigs = [];\n Object.entries(trigHierarchy).forEach(([uri, info]) => {\n if (info.hasParent === trigUri) {\n childTrigs.push({\n uri: uri,\n label: getPrefixedName(uri, currentPrefixes)\n });\n }\n });\n\n // Формируем информацию\n let infoHtml = `<strong>ID:</strong> ${trigLabel}<br>`;\n infoHtml += `<strong>Label:</strong> ${graphInfo.label || 'не указан'}<br>`;\n infoHtml += `<strong>Parent:</strong> ${graphInfo.hasParent ? getPrefixedName(graphInfo.hasParent, currentPrefixes) : 'не указан'}<br>`;\n infoHtml += `<strong>Процессов:</strong> ${graphInfo.processes ? graphInfo.processes.length : 0}`;\n\n infoDiv.innerHTML = infoHtml;\n\n // Проверяем наличие дочерних TriG\n if (childTrigs.length > 0) {\n const childLabels = childTrigs.map(c => c.label).join(', ');\n warningDiv.innerHTML = `<strong>⚠️ ОШИБКА:</strong> Удаление невозможно!<br><br>` +\n `Данный TriG (${trigLabel}) является родительским для следующих TriG:<br>` +\n `<strong>${childLabels}</strong><br><br>` +\n `Сначала измените ссылку vad:hasParentTrig у дочерних TriG или удалите их.`;\n warningDiv.style.display = 'block';\n confirmBtn.disabled = true;\n } else {\n warningDiv.style.display = 'none';\n confirmBtn.disabled = false;\n }\n }\n\n /**\n * Генерирует SPARQL запрос для удаления TriG\n * При удалении:\n * 1. Проверяет наличие дочерних TriG (если есть - ошибка)\n * 2. Удаляет vad:hasTrig из ptree\n * 3. Добавляет vad:hasParentProcess к процессу (ссылается на родителя родительского TriG)\n * 4. Удаляет граф TriG\n * 5. Пересчитывает virtualRDFdata\n */\n function deleteTriG() {\n const trigUri = document.getElementById('del-trig-select').value;\n\n if (!trigUri) {\n alert('Ошибка: Выберите TriG для удаления');\n return;\n }\n\n const graphInfo = trigHierarchy[trigUri];\n if (!graphInfo) {\n alert('Ошибка: TriG не найден');\n return;\n }\n\n // Получаем процесс, который определяется этим TriG\n let definingProcess = null;\n graphInfo.quads.forEach(quad => {\n const predicateLabel = getPrefixedName(quad.predicate.value, currentPrefixes);\n if (predicateLabel === 'vad:definesProcess' || quad.predicate.value === 'http://example.org/vad#definesProcess') {\n definingProcess = quad.object.value;\n }\n });\n\n if (!definingProcess) {\n alert('Ошибка: Не найден процесс, определяемый этим TriG (vad:definesProcess)');\n return;\n }\n\n // Получаем родительский TriG\n const parentTrigUri = graphInfo.hasParent;\n if (!parentTrigUri || parentTrigUri === 'http://example.org/vad#root') {\n // Если родитель root, устанавливаем hasParentProcess = NotDefined\n // (или можно запретить удаление корневых TriG)\n }\n\n // Получаем процесс-владельца родительского TriG (для hasParentProcess)\n let parentDefiningProcess = null;\n if (parentTrigUri && parentTrigUri !== 'http://example.org/vad#root') {\n const parentGraphInfo = trigHierarchy[parentTrigUri];\n if (parentGraphInfo) {\n parentGraphInfo.quads.forEach(quad => {\n const predicateLabel = getPrefixedName(quad.predicate.value, currentPrefixes);\n if (predicateLabel === 'vad:definesProcess' || quad.predicate.value === 'http://example.org/vad#definesProcess') {\n parentDefiningProcess = quad.object.value;\n }\n });\n }\n }\n\n // Если не нашли родительский процесс, используем NotDefined\n if (!parentDefiningProcess) {\n parentDefiningProcess = 'http://example.org/vad#NotDefined';\n }\n\n // Формируем SPARQL запрос\n const trigLabel = getPrefixedName(trigUri, currentPrefixes);\n const processLabel = getPrefixedName(definingProcess, currentPrefixes);\n const parentProcessLabel = getPrefixedName(parentDefiningProcess, currentPrefixes);\n\n const prefixDeclarations = generateSparqlPrefixes(currentPrefixes);\n\n const sparqlQuery = `${prefixDeclarations}# Правило: При удалении TriG возвращаем vad:hasParentProcess\n# Удаляем TriG \"${trigLabel}\" и обновляем связи процесса\n\n# 1. Удаляем vad:hasTrig из ptree\nDELETE {\n GRAPH vad:ptree {\n ${processLabel} vad:hasTrig ${trigLabel} .\n }\n}\nWHERE {\n GRAPH vad:ptree {\n ${processLabel} vad:hasTrig ${trigLabel} .\n }\n};\n\n# 2. Добавляем vad:hasParentProcess к процессу\nINSERT DATA {\n GRAPH vad:ptree {\n ${processLabel} vad:hasParentProcess ${parentProcessLabel} .\n }\n};\n\n# 3. Удаляем содержимое TriG графа\n# ПРИМЕЧАНИЕ: DROP GRAPH не поддерживается напрямую в N3.js/Comunica\n# Вместо этого удаляем все триплеты из графа\nDELETE {\n GRAPH ${trigLabel} {\n ?s ?p ?o .\n }\n}\nWHERE {\n GRAPH ${trigLabel} {\n ?s ?p ?o .\n }\n}`;\n\n // Записываем SPARQL в поле результата\n const resultTextarea = document.getElementById('result-sparql-query');\n if (resultTextarea) {\n resultTextarea.value = sparqlQuery;\n }\n\n closeDelTrigModal();\n showSmartDesignMessage(\n `SPARQL запрос для удаления TriG \"${trigLabel}\" сгенерирован.\\n` +\n `После применения необходимо пересчитать virtualRDFdata.`,\n 'warning'\n );\n }\n\n /**\n * Открывает RDF данные в отдельном окне браузера\n */\n function showRdfInSeparateWindow() {\n const rdfInput = document.getElementById('rdf-input');\n if (!rdfInput) return;\n\n const rdfContent = rdfInput.value;\n\n // Создаем новое окно\n const newWindow = window.open('', '_blank', 'width=800,height=600,scrollbars=yes,resizable=yes');\n\n if (newWindow) {\n // Формируем HTML для нового окна\n const htmlContent = '<!DOCTYPE html>' +\n '<html><head><title>RDF данные</title>' +\n '<style>' +\n 'body { font-family: Consolas, Monaco, monospace; padding: 20px; background-color: #f5f5f5; margin: 0; }' +\n '.container { background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }' +\n 'h1 { color: #333; font-size: 18px; margin-top: 0; border-bottom: 2px solid #4CAF50; padding-bottom: 10px; }' +\n 'pre { white-space: pre-wrap; word-wrap: break-word; font-size: 13px; line-height: 1.5; margin: 0; color: #333; }' +\n '.copy-btn { background-color: #4CAF50; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-bottom: 15px; }' +\n '.copy-btn:hover { background-color: #45a049; }' +\n '</style></head>' +\n '<body><div class=\"container\">' +\n '<h1>RDF данные</h1>' +\n '<button class=\"copy-btn\" id=\"copy-btn\">Копировать в буфер</button>' +\n '<pre id=\"rdf-content\"></pre>' +\n '</div></body></html>';\n\n newWindow.document.write(htmlContent);\n newWindow.document.close();\n\n // Заполняем содержимое после записи документа\n const preElement = newWindow.document.getElementById('rdf-content');\n if (preElement) {\n preElement.textContent = rdfContent;\n }\n\n // Добавляем обработчик для кнопки копирования\n const copyBtn = newWindow.document.getElementById('copy-btn');\n if (copyBtn) {\n copyBtn.onclick = function() {\n newWindow.navigator.clipboard.writeText(rdfContent).then(function() {\n newWindow.alert('Скопировано в буфер обмена');\n }).catch(function() {\n // Fallback для старых браузеров\n const textarea = newWindow.document.createElement('textarea');\n textarea.value = rdfContent;\n newWindow.document.body.appendChild(textarea);\n textarea.select();\n newWindow.document.execCommand('copy');\n newWindow.document.body.removeChild(textarea);\n newWindow.alert('Скопировано в буфер обмена');\n });\n };\n }\n } else {\n alert('Не удалось открыть новое окно. Проверьте настройки блокировки всплывающих окон.');\n }\n }\n\n // Закрытие модального окна при клике вне его\n window.addEventListener('click', function(event) {\n const modal = document.getElementById('new-trig-modal');\n if (event.target === modal) {\n closeNewTrigModal();\n }\n });\n\n function updateModeDescription() {\n const mode = document.getElementById('visualization-mode').value;\n const description = document.getElementById('mode-description');\n const maxVadRowLengthGroup = document.getElementById('max-vad-row-length-group');\n const sparqlModeSelect = document.getElementById('sparql-mode');\n const smartDesignOption = sparqlModeSelect ? sparqlModeSelect.querySelector('option[value=\"smart-design\"]') : null;\n\n const descriptions = {\n 'notation': 'С выделением типов объектов и предикатов цветом и формами',\n 'base': 'Базовый режим без специальных стилей',\n 'aggregation': 'Литералы агрегируются в узел субъекта',\n 'vad': 'VAD: процессы как cds-фигуры с исполнителями',\n 'vad-trig': 'VAD TriG: иерархия TriG с деревом и свойствами'\n };\n\n description.textContent = descriptions[mode] || '';\n\n // Показываем/скрываем параметр \"Макс. длина VAD\" для режимов VAD и VAD TriG\n if (mode === 'vad' || mode === 'vad-trig') {\n maxVadRowLengthGroup.style.display = 'block';\n } else {\n maxVadRowLengthGroup.style.display = 'none';\n }\n\n // Показываем/скрываем опцию \"SPARQL Smart Design\" в зависимости от режима визуализации\n if (smartDesignOption) {\n if (mode === 'vad-trig') {\n smartDesignOption.style.display = '';\n } else {\n smartDesignOption.style.display = 'none';\n // Если был выбран smart-design, сбрасываем на 'no'\n if (sparqlModeSelect.value === 'smart-design') {\n sparqlModeSelect.value = 'no';\n }\n }\n }\n\n // Обновляем состояние SPARQL панели\n toggleSparqlPanel();\n }\n\n function toggleSparqlPanel() {\n const sparqlMode = document.getElementById('sparql-mode').value;\n const visualizationMode = document.getElementById('visualization-mode').value;\n const sparqlPanel = document.getElementById('sparql-panel');\n const smartDesignContainer = document.getElementById('smart-design-container');\n\n // Скрываем оба панели по умолчанию\n sparqlPanel.classList.remove('visible');\n smartDesignContainer.classList.remove('visible');\n\n if (sparqlMode === 'smart-design') {\n // SPARQL Smart Design отображается только в режиме VAD TriG\n if (visualizationMode === 'vad-trig') {\n smartDesignContainer.classList.add('visible');\n // В режиме SPARQL Smart Design всегда также отображается окно SPARQL запрос\n sparqlPanel.classList.add('visible');\n // Заполняем выпадающие списки при активации Smart Design\n populateSmartDesignDropdowns();\n // Показываем справочные сообщения при открытии панели\n showSmartDesignMessage('Выберите TriG, затем Subject, Predicate и Object. Нажмите \"Создать SPARQL\" для генерации запроса.', 'info');\n showResultSparqlMessage('После генерации SPARQL запроса нажмите \"Применить как Simple Triple\" или \"Применить как Shorthand Triple\" для добавления в RDF данные.', 'info');\n } else {\n // Показываем сообщение, что Smart Design доступен только в VAD TriG\n showSmartDesignMessage('SPARQL Smart Design доступен только в режиме \"Режим VAD TriG\"', 'info');\n }\n }\n }\n\n // ============================================================================\n // ОСНОВНАЯ ФУНКЦИЯ ВИЗУАЛИЗАЦИИ\n // ============================================================================\n\n async function visualize() {\n const rdfInput = document.getElementById('rdf-input').value.trim();\n const inputFormat = document.getElementById('input-format').value;\n const outputFormat = document.getElementById('output-format').value;\n const layoutEngine = document.getElementById('layout-engine').value;\n const visualizationMode = document.getElementById('visualization-mode').value;\n\n const maxLabelLengthInput = document.getElementById('max-label-length');\n const maxLabelLengthValue = parseInt(maxLabelLengthInput.value, 10);\n if (!isNaN(maxLabelLengthValue) && maxLabelLengthValue >= 5 && maxLabelLengthValue <= 200) {\n currentMaxLabelLength = maxLabelLengthValue;\n } else {\n currentMaxLabelLength = DEFAULT_MAX_LABEL_LENGTH;\n maxLabelLengthInput.value = DEFAULT_MAX_LABEL_LENGTH;\n }\n\n // Чтение параметра \"Макс. длина VAD\" для режима VAD\n const maxVadRowLengthInput = document.getElementById('max-vad-row-length');\n const maxVadRowLengthValue = parseInt(maxVadRowLengthInput.value, 10);\n if (!isNaN(maxVadRowLengthValue) && maxVadRowLengthValue >= 2 && maxVadRowLengthValue <= 20) {\n currentMaxVadRowLength = maxVadRowLengthValue;\n } else {\n currentMaxVadRowLength = DEFAULT_MAX_VAD_ROW_LENGTH;\n maxVadRowLengthInput.value = DEFAULT_MAX_VAD_ROW_LENGTH;\n }\n\n currentMode = visualizationMode;\n\n if (!rdfInput) {\n showError('Пожалуйста, введите RDF данные');\n return;\n }\n\n showLoading();\n\n const button = document.getElementById('visualize-btn');\n button.disabled = true;\n button.textContent = 'Обработка...';\n\n try {\n const parser = new N3.Parser({ format: inputFormat });\n const quads = [];\n let prefixes = {};\n\n await new Promise((resolve, reject) => {\n parser.parse(rdfInput, (error, quad, parsedPrefixes) => {\n if (error) {\n reject(error);\n return;\n }\n if (quad) {\n quads.push(quad);\n } else {\n if (parsedPrefixes) {\n prefixes = parsedPrefixes;\n }\n resolve();\n }\n });\n });\n\n currentPrefixes = prefixes;\n currentQuads = quads;\n currentStore = null;\n\n if (quads.length === 0) {\n showError('Не найдено RDF триплетов в данных');\n return;\n }\n\n // Валидация для режима VAD\n if (currentMode === 'vad') {\n const validation = validateVAD(quads, prefixes);\n if (!validation.valid) {\n showValidationError(formatVADErrors(validation.errors));\n button.disabled = false;\n button.textContent = 'Показать';\n return;\n }\n }\n\n // Обработка режима VAD TriG\n if (currentMode === 'vad-trig') {\n // Парсим иерархию TriG графов\n const hierarchyResult = parseTriGHierarchy(quads, prefixes);\n\n if (!hierarchyResult.valid) {\n showValidationError(formatVADTriGErrors(hierarchyResult.errors));\n toggleVADTriGPanels(false);\n button.disabled = false;\n button.textContent = 'Показать';\n return;\n }\n\n // Сохраняем иерархию\n trigHierarchy = hierarchyResult.hierarchy;\n\n // Выбираем корневой TriG для начального отображения\n // Используем первый из корневых TriG, если есть\n selectedTrigUri = hierarchyResult.rootTrigUris.length > 0 ? hierarchyResult.rootTrigUris[0] : null;\n\n // Показываем панели VAD TriG\n toggleVADTriGPanels(true);\n\n // Отображаем дерево TriG (передаём массив всех корневых TriG)\n displayTriGTree(trigHierarchy, hierarchyResult.rootTrigUris, prefixes);\n\n // Отобр�
[2026-01-26T19:23:56.120Z] [INFO] �жаем свойства корневого TriG\n if (selectedTrigUri) {\n displayTriGProperties(selectedTrigUri, trigHierarchy, prefixes);\n }\n\n // Валидация VAD для квадов корневого графа\n const rootGraphInfo = trigHierarchy[selectedTrigUri];\n if (rootGraphInfo) {\n const validation = validateVAD(rootGraphInfo.quads, prefixes);\n if (!validation.valid) {\n showValidationError(formatVADErrors(validation.errors));\n button.disabled = false;\n button.textContent = 'Показать';\n return;\n }\n }\n\n // Продолжаем с квадами только из выбранного графа\n activeFilters = [...getFilterConfig('vad').hiddenPredicates];\n\n const filteredQuads = rootGraphInfo.quads.filter(quad => {\n const predicateUri = quad.predicate.value;\n const predicateLabel = getPrefixedName(predicateUri, prefixes);\n return !isPredicateHidden(predicateUri, predicateLabel);\n });\n\n // Временно переключаемся в режим VAD для генерации DOT\n const originalQuads = currentQuads;\n currentQuads = rootGraphInfo.quads;\n currentMode = 'vad';\n\n const dotCode = rdfToDot(filteredQuads, prefixes);\n currentDotCode = dotCode;\n console.log('VAD TriG - Сгенерированный DOT-код:', dotCode);\n\n currentQuads = originalQuads;\n currentMode = 'vad-trig';\n\n const viz = await Viz.instance();\n const svgString = viz.renderString(dotCode, {\n format: 'svg',\n engine: layoutEngine\n });\n\n // В режиме VAD TriG используем специальный контейнер vad-trig-output\n const output = document.getElementById('vad-trig-output');\n currentScale = 1.0;\n applyZoom();\n\n if (outputFormat === 'svg') {\n output.innerHTML = svgString;\n currentSvgElement = output.querySelector('svg');\n document.getElementById('export-buttons').style.display = 'block';\n document.getElementById('vad-trig-zoom-controls').style.display = 'flex';\n } else if (outputFormat === 'png') {\n const pngDataUrl = await svgToPng(svgString);\n output.innerHTML = `<img src=\"${pngDataUrl}\" alt=\"RDF Graph\" style=\"max-width: 100%;\">`;\n const tempDiv = document.createElement('div');\n tempDiv.innerHTML = svgString;\n currentSvgElement = tempDiv.querySelector('svg');\n document.getElementById('export-buttons').style.display = 'block';\n document.getElementById('vad-trig-zoom-controls').style.display = 'flex';\n }\n\n displayLegend();\n displayPrefixes(prefixes);\n displayFilters();\n addNodeClickHandlers();\n closeAllPropertiesPanels();\n\n // Обновляем SPARQL запрос для выбранного TriG\n updateSparqlQueryForTriG();\n\n // Обновляем выпадающие списки Smart Design, если режим активен\n if (document.getElementById('sparql-mode').value === 'smart-design') {\n populateSmartDesignDropdowns();\n }\n\n console.log(`VAD TriG: Обработано ${quads.length} триплетов, отображается граф ${getPrefixedName(selectedTrigUri, prefixes)}`);\n\n button.disabled = false;\n button.textContent = 'Показать';\n return;\n }\n\n // Скрываем панели VAD TriG для других режимов\n toggleVADTriGPanels(false);\n\n activeFilters = [...getFilterConfig(currentMode).hiddenPredicates];\n\n const filteredQuads = quads.filter(quad => {\n const predicateUri = quad.predicate.value;\n const predicateLabel = getPrefixedName(predicateUri, prefixes);\n return !isPredicateHidden(predicateUri, predicateLabel);\n });\n\n const dotCode = rdfToDot(filteredQuads, prefixes);\n currentDotCode = dotCode;\n console.log('Сгенерированный DOT-код:', dotCode);\n\n const viz = await Viz.instance();\n const svgString = viz.renderString(dotCode, {\n format: 'svg',\n engine: layoutEngine\n });\n\n const output = document.getElementById('output');\n currentScale = 1.0;\n applyZoom();\n\n if (outputFormat === 'svg') {\n output.innerHTML = svgString;\n currentSvgElement = output.querySelector('svg');\n document.getElementById('export-buttons').style.display = 'block';\n document.getElementById('zoom-controls').style.display = 'flex';\n } else if (outputFormat === 'png') {\n const pngDataUrl = await svgToPng(svgString);\n output.innerHTML = `<img src=\"${pngDataUrl}\" alt=\"RDF Graph\" style=\"max-width: 100%;\">`;\n const tempDiv = document.createElement('div');\n tempDiv.innerHTML = svgString;\n currentSvgElement = tempDiv.querySelector('svg');\n document.getElementById('export-buttons').style.display = 'block';\n document.getElementById('zoom-controls').style.display = 'flex';\n }\n\n if (currentMode !== 'base') {\n displayLegend();\n } else {\n document.getElementById('legend-panel').style.display = 'none';\n }\n\n displayPrefixes(prefixes);\n displayFilters();\n addNodeClickHandlers();\n closeAllPropertiesPanels();\n\n console.log(`Обработано ${quads.length} триплетов`);\n\n } catch (error) {\n console.error('Ошибка визуализации:', error);\n const enhancedMessage = enhanceParseError(error.message, rdfInput);\n showError(enhancedMessage);\n } finally {\n button.disabled = false;\n button.textContent = 'Показать';\n }\n }\n\n // ============================================================================\n // ФУНКЦИИ ЭКСПОРТА\n // ============================================================================\n\n function svgToPng(svgString) {\n return new Promise((resolve, reject) => {\n const tempDiv = document.createElement('div');\n tempDiv.innerHTML = svgString;\n const svgElement = tempDiv.querySelector('svg');\n\n let width = parseInt(svgElement.getAttribute('width')) || 800;\n let height = parseInt(svgElement.getAttribute('height')) || 600;\n\n const widthStr = svgElement.getAttribute('width') || '';\n const heightStr = svgElement.getAttribute('height') || '';\n\n if (widthStr.includes('pt')) {\n width = Math.ceil(parseFloat(widthStr) * 1.33);\n }\n if (heightStr.includes('pt')) {\n height = Math.ceil(parseFloat(heightStr) * 1.33);\n }\n\n const canvas = document.createElement('canvas');\n canvas.width = width * 2;\n canvas.height = height * 2;\n const ctx = canvas.getContext('2d');\n ctx.scale(2, 2);\n ctx.fillStyle = 'white';\n ctx.fillRect(0, 0, width, height);\n\n const img = new Image();\n const svgBlob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' });\n const url = URL.createObjectURL(svgBlob);\n\n img.onload = function() {\n ctx.drawImage(img, 0, 0, width, height);\n URL.revokeObjectURL(url);\n resolve(canvas.toDataURL('image/png'));\n };\n\n img.onerror = function() {\n URL.revokeObjectURL(url);\n reject(new Error('Ошибка при конвертации SVG в PNG'));\n };\n\n img.src = url;\n });\n }\n\n function downloadSVG() {\n if (!currentSvgElement) {\n alert('Сначала визуализируйте RDF данные');\n return;\n }\n\n const svgData = new XMLSerializer().serializeToString(currentSvgElement);\n const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });\n\n const downloadLink = document.createElement('a');\n downloadLink.href = URL.createObjectURL(svgBlob);\n downloadLink.download = 'rdf-graph.svg';\n\n document.body.appendChild(downloadLink);\n downloadLink.click();\n document.body.removeChild(downloadLink);\n URL.revokeObjectURL(downloadLink.href);\n }\n\n async function downloadPNG() {\n if (!currentSvgElement) {\n alert('Сначала визуализируйте RDF данные');\n return;\n }\n\n try {\n const svgData = new XMLSerializer().serializeToString(currentSvgElement);\n const pngDataUrl = await svgToPng(svgData);\n\n const downloadLink = document.createElement('a');\n downloadLink.href = pngDataUrl;\n downloadLink.download = 'rdf-graph.png';\n\n document.body.appendChild(downloadLink);\n downloadLink.click();\n document.body.removeChild(downloadLink);\n\n } catch (error) {\n console.error('Ошибка при скачивании PNG:', error);\n alert('Ошибка при создании PNG файла');\n }\n }\n\n /**\n * Маппинг внутренних форматов на сокращенные имена для URL\n */\n const formatMapping = {\n 'turtle': 'ttl',\n 'n-triples': 'nt',\n 'n-quads': 'nq',\n 'trig': 'trig'\n };\n\n /**\n * Открывает визуализацию в новом окне через внешний LDF сервис\n * Формирует URL с параметрами: rdf=данные&from=формат&to=png\n */\n function openInNewWindowLdfFi() {\n // Получаем входные данные\n const rdfInput = document.getElementById('rdf-input').value.trim();\n const inputFormat = document.getElementById('input-format').value;\n\n // Проверяем, что данные введены\n if (!rdfInput) {\n alert('Пожалуйста, введите RDF данные');\n return;\n }\n\n // Получаем формат для параметра URL\n const fromFormat = formatMapping[inputFormat] || 'ttl';\n\n // Кодируем RDF данные для URL\n // Заменяем пробелы на + для совместимости с LDF сервисом\n const encodedRdf = encodeURIComponent(rdfInput).replace(/%20/g, '+');\n\n // Формируем URL для внешнего сервиса\n const serviceUrl = `https://www.ldf.fi/service/rdf-grapher?rdf=${encodedRdf}&from=${fromFormat}&to=png`;\n\n // Открываем в новом окне\n window.open(serviceUrl, '_blank');\n }\n\n /**\n * Открывает визуализацию в новом окне через GitHub Pages (без внешнего сервиса)\n * Формирует URL с данными в хеше: #rdf=данные&from=формат&to=формат&mode=режим\n * Использует URL fragment (hash) вместо query params для избежания ошибки URI Too Long\n */\n function openInNewWindowGitHub() {\n // Получаем входные данные\n const rdfInput = document.getElementById('rdf-input').value.trim();\n const inputFormat = document.getElementById('input-format').value;\n const outputFormat = document.getElementById('output-format').value;\n const visualizationMode = document.getElementById('visualization-mode').value;\n\n // Проверяем, что данные введены\n if (!rdfInput) {\n alert('Пожалуйста, введите RDF данные');\n return;\n }\n\n // Получаем формат для параметра URL\n const fromFormat = formatMapping[inputFormat] || 'ttl';\n\n // Кодируем RDF данные для URL\n const encodedRdf = encodeURIComponent(rdfInput);\n\n // Определяем базовый URL динамически на основе текущего расположения HTML файла\n // Это позволяет корректно работать как на GitHub Pages, так и локально\n let baseUrl;\n // Используем текущую директорию HTML файла как базовый URL\n baseUrl = window.location.origin + window.location.pathname.replace(/\\/[^\\/]*$/, '/');\n\n // Формируем URL с данными в хеше (избегает ошибки URI Too Long)\n const hashParams = `rdf=${encodedRdf}&from=${fromFormat}&to=${outputFormat}&mode=${visualizationMode}`;\n const serviceUrl = `${baseUrl}#${hashParams}`;\n\n // Открываем в новом окне\n window.open(serviceUrl, '_blank');\n }\n\n /**\n * Открывает DOT-код в GraphvizOnline для интерактивного редактирования\n * Использует хеш URL для передачи DOT-кода\n */\n function openInNewWindowGraphvizOnline() {\n // Проверяем, что DOT-код был сгенерирован\n if (!currentDotCode) {\n alert('Сначала визуализируйте RDF данные');\n return;\n }\n\n // Кодируем DOT-код для URL\n const encodedDot = encodeURIComponent(currentDotCode);\n\n // Формируем URL с DOT-кодом в хеше\n const graphvizUrl = `https://dreampuf.github.io/GraphvizOnline/#${encodedDot}`;\n\n // Открываем в новом окне\n window.open(graphvizUrl, '_blank');\n }\n\n // ============================================================================\n // SPARQL ФУНКЦИИ\n // ============================================================================\n\n async function initSparqlEngine() {\n if (!currentStore) {\n currentStore = new N3.Store();\n currentQuads.forEach(quad => currentStore.addQuad(quad));\n }\n\n if (!comunicaEngine) {\n if (typeof Comunica !== 'undefined' && Comunica.QueryEngine) {\n comunicaEngine = new Comunica.QueryEngine();\n }\n }\n\n return { store: currentStore, engine: comunicaEngine };\n }\n\n async function executeSparqlQuery() {\n const queryInput = document.getElementById('sparql-query');\n const resultsDiv = document.getElementById('sparql-results');\n const resultsContent = document.getElementById('sparql-results-content');\n\n const query = queryInput.value.trim();\n if (!query) {\n resultsContent.innerHTML = '<div class=\"sparql-error\">Введите SPARQL запрос</div>';\n resultsDiv.style.display = 'block';\n return;\n }\n\n if (currentQuads.length === 0) {\n resultsContent.innerHTML = '<div class=\"sparql-error\">Сначала визуализируйте RDF данные</div>';\n resultsDiv.style.display = 'block';\n return;\n }\n\n resultsContent.innerHTML = '<div class=\"sparql-loading\"><div class=\"spinner\"></div><p>Выполнение запроса...</p></div>';\n resultsDiv.style.display = 'block';\n\n try {\n const { store, engine } = await initSparqlEngine();\n\n if (!engine) {\n throw new Error('SPARQL движок не инициализирован');\n }\n\n const bindingsStream = await engine.queryBindings(query, {\n sources: [store]\n });\n\n const bindings = await bindingsStream.toArray();\n\n if (bindings.length === 0) {\n resultsContent.innerHTML = '<p>Запрос не вернул результатов</p>';\n return;\n }\n\n const variables = [...bindings[0].keys()].map(k => k.value);\n\n let html = '<table class=\"sparql-results-table\">';\n html += '<thead><tr>';\n variables.forEach(v => {\n html += `<th>?${v}</th>`;\n });\n html += '</tr></thead>';\n html += '<tbody>';\n\n let rowIndex = 0;\n bindings.forEach(binding => {\n // Собираем URI из этой строки для навигации к узлу\n const uris = [];\n variables.forEach(v => {\n const term = binding.get(v);\n if (term && term.termType !== 'Literal') {\n uris.push(term.value);\n }\n });\n\n // Кодируем URI в base64 для передачи через data-атрибут\n const urisData = btoa(encodeURIComponent(JSON.stringify(uris)));\n\n html += `<tr class=\"clickable\" data-row-index=\"${rowIndex}\" data-uris=\"${urisData}\" onclick=\"highlightNodeFromSparqlResult(this)\">`;\n variables.forEach(v => {\n const term = binding.get(v);\n if (term) {\n const value = term.value;\n const isLiteral = term.termType === 'Literal';\n const displayValue = isLiteral ? `\"${value}\"` : getPrefixedName(value, currentPrefixes);\n const cssClass = isLiteral ? 'literal' : 'uri';\n html += `<td class=\"${cssClass}\">${escapeHtml(displayValue)}</td>`;\n } else {\n html += '<td>-</td>';\n }\n });\n html += '</tr>';\n rowIndex++;\n });\n\n html += '</tbody></table>';\n html += `<p style=\"margin-top: 10px; font-size: 12px; color: #666;\">Найдено результатов: ${bindings.length}</p>`;\n\n resultsContent.innerHTML = html;\n\n } catch (error) {\n console.error('Ошибка SPARQL:', error);\n resultsContent.innerHTML = `<div class=\"sparql-error\">Ошибка: ${error.message}</div>`;\n }\n }\n\n /**\n * Подсвечивает узел на графе при клике на строку в результатах SPARQL\n * и открывает панель свойств для первого найденного URI\n *\n * @param {HTMLElement} rowElement - Элемент строки таблицы\n */\n function highlightNodeFromSparqlResult(rowElement) {\n // Снимаем подсветку со всех строк\n const allRows = document.querySelectorAll('.sparql-results-table tr.highlighted');\n allRows.forEach(row => row.classList.remove('highlighted'));\n\n // Подсвечиваем текущую строку\n rowElement.classList.add('highlighted');\n\n // Получаем URI из данных строки (base64 закодированный JSON)\n const urisData = rowElement.getAttribute('data-uris');\n if (!urisData) return;\n\n let uris;\n try {\n // Декодируем base64 и затем URL-декодируем\n uris = JSON.parse(decodeURIComponent(atob(urisData)));\n } catch (e) {\n console.error('Ошибка парсинга URI:', e);\n return;\n }\n\n if (uris.length === 0) return;\n\n // Снимаем выделение с предыдущего узла\n if (selectedNodeElement) {\n selectedNodeElement.classList.remove('selected');\n selectedNodeElement = null;\n }\n\n // Ищем узлы на графе по URI (в обоих контейнерах: regular и VAD TriG)\n const regularOutput = document.getElementById('output');\n const vadTrigOutput = document.getElementById('vad-trig-output');\n const regularSvg = regularOutput ? regularOutput.querySelector('svg') : null;\n const vadTrigSvg = vadTrigOutput ? vadTrigOutput.querySelector('svg') : null;\n\n // Collect all nodes from both SVG containers\n let allNodes = [];\n if (regularSvg) {\n allNodes = allNodes.concat(Array.from(regularSvg.querySelectorAll('.node')));\n }\n if (vadTrigSvg) {\n allNodes = allNodes.concat(Array.from(vadTrigSvg.querySelectorAll('.node')));\n }\n\n if (allNodes.length === 0) return;\n\n let foundNode = null;\n let foundUri = null;\n let foundLabel = null;\n\n // Пробуем найти узел для первого URI\n for (const uri of uris) {\n for (const [label, info] of Object.entries(nodeLabelToUri)) {\n if (info.uri === uri) {\n // Нашли маппинг, ищем узел по dotId\n for (const node of allNodes) {\n const titleElement = node.querySelector('title');\n if (titleElement && titleElement.textContent === info.dotId) {\n foundNode = node;\n foundUri = uri;\n foundLabel = label;\n break;\n }\n }\n if (foundNode) break;\n }\n }\n if (foundNode) break;\n }\n\n if (foundNode) {\n // Выделяем найденный узел\n foundNode.classList.add('selected');\n selectedNodeElement = foundNode;\n\n // Прокручиваем к узлу\n foundNode.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' });\n\n // Открываем панель свойств для найденного URI\n if (foundUri) {\n const displayLabel = foundLabel || getPrefixedName(foundUri, currentPrefixes);\n showNodeProperties(foundUri, displayLabel);\n }\n } else if (uris.length > 0) {\n // Если узел не найден на графе, все равно открываем панель свойств для первого URI\n const uri = uris[0];\n const displayLabel = getPrefixedName(uri, currentPrefixes);\n showNodeProperties(uri, displayLabel);\n }\n }\n\n function resetSparqlQuery() {\n // В режиме VAD TriG используем запрос с GRAPH clause\n if (currentMode === 'vad-trig' && selectedTrigUri) {\n document.getElementById('sparql-query').value = getSparqlQueryForTriG(selectedTrigUri);\n } else {\n document.getElementById('sparql-query').value = defaultSparqlQuery;\n }\n document.getElementById('sparql-results').style.display = 'none';\n }\n\n /**\n * Парсит URL параметры и возвращает объект с параметрами\n * Поддерживает параметры как в query string (?...), так и в hash (#...)\n * Hash имеет приоритет над query string для избежания ошибки URI Too Long\n *\n * Поддерживаемые параметры:\n * - rdf: RDF данные (URL-encoded)\n * - from: входной формат (ttl, nt, nq, trig)\n * - to: выходной формат (svg, png)\n * - mode: режим визуализации (notation, base, aggregation, vad)\n *\n * @returns {Object} - Объект с параметрами\n */\n function parseUrlParams() {\n // Сначала пробуем получить параметры из hash (приоритет)\n let urlParams;\n if (window.location.hash && window.location.hash.length > 1) {\n // Убираем # и парсим как URLSearchParams\n urlParams = new URLSearchParams(window.location.hash.substring(1));\n } else {\n // Fallback на query string для обратной совместимости\n urlParams = new URLSearchParams(window.location.search);\n }\n\n const params = {};\n\n // Маппинг обратный: параметры URL -> внутренние форматы\n const reverseFormatMapping = {\n 'ttl': 'turtle',\n 'turtle': 'turtle',\n 'nt': 'n-triples',\n 'n-triples': 'n-triples',\n 'nq': 'n-quads',\n 'n-quads': 'n-quads',\n 'trig': 'trig'\n };\n\n // Получаем RDF данные из параметра\n if (urlParams.has('rdf')) {\n params.rdf = urlParams.get('rdf');\n }\n\n // Получаем входной формат\n if (urlParams.has('from')) {\n const fromParam = urlParams.get('from');\n params.from = reverseFormatMapping[fromParam] || 'turtle';\n }\n\n // Получаем выходной формат\n if (urlParams.has('to')) {\n const toParam = urlParams.get('to');\n if (toParam === 'png' || toParam === 'svg') {\n params.to = toParam;\n }\n }\n\n // Получаем режим визуализации\n if (urlParams.has('mode')) {\n const modeParam = urlParams.get('mode');\n const validModes = ['notation', 'base', 'aggregation', 'vad', 'vad-trig'];\n if (validModes.includes(modeParam)) {\n params.mode = modeParam;\n }\n }\n\n return params;\n }\n\n // Инициализация при загрузке страницы\n document.addEventListener('DOMContentLoaded', function() {\n updateModeDescription();\n\n // Парсим URL параметры\n const params = parseUrlParams();\n\n // Если есть RDF данные в URL, заполняем форму и визуализируем\n if (params.rdf) {\n console.log('Обнаружены RDF данные в URL, загружаем...');\n\n // Заполняем текстовое поле RDF данными\n document.getElementById('rdf-input').value = params.rdf;\n\n // Устанавливаем входной формат, если указан\n if (params.from) {\n document.getElementById('input-format').value = params.from;\n }\n\n // Устанавливаем выходной формат, если указан\n if (params.to) {\n document.getElementById('output-format').value = params.to;\n }\n\n // Устанавливаем режим визуализации, если указан\n if (params.mode) {\n document.getElementById('visualization-mode').value = params.mode;\n updateModeDescription();\n }\n\n // Небольшая задержка для уверенности, что все элементы загружены\n setTimeout(function() {\n visualize();\n }, 100);\n }\n });\n\n // ==============================================================================\n // МОДУЛЬ SPARQL ЗАПРОСОВ ДЛЯ SMART DESIGN\n // ==============================================================================\n // Данный модуль содержит функции и запросы для динамического формирования\n // справочников в окне Smart Design на основе RDF-данных и технологических\n // объектов из vad-basic-ontology_tech_Appendix.ttl\n // ==============================================================================\n\n /**\n * Хранилище загруженных технологических объектов из vad-basic-ontology_tech_Appendix.ttl\n * Загружается при старте приложения\n */\n let techAppendixData = {\n loaded: false,\n predicateGroups: {}, // Группы предикатов по типам объектов\n autoGeneratedPredicates: {}, // Автоматически генерируемые предикаты\n contextTriGTypes: {} // Связь технологических объектов с контекстами\n };\n\n /**\n * Выполняет SPARQL-подобный запрос и возвращает значения указанной переменной\n *\n * Эта функция выполняет упрощенный SPARQL SELECT запрос, анализируя текущие квады (currentQuads).\n * Поддерживает основные паттерны SPARQL, включая GRAPH блоки для запросов по именованным графам.\n * Используется для динамического формирования справочников в окне Smart Design.\n *\n * @param {string} sparqlQuery - SPARQL SELECT запрос\n * @param {string} variableName - Имя переменной для извлечения (без '?'). По умолчанию 'value'\n * @returns {Array<{uri: string, label: string}>} Массив объектов с URI и метками результатов\n *\n * @example\n * // Получить все процессы типа TypeProcess\n * const processes = funSPARQLvalues('SELECT ?p WHERE { ?p rdf:type vad:TypeProcess }', 'p');\n * // Результат: [{ uri: 'http://example.org/vad#Process1', label: 'Process 1' }, ...]\n *\n * @example\n * // Получить процессы с метками из ptree\n * const processesWithLabels = funSPARQLvalues(`\n * SELECT ?process ?label WHERE {\n * GRAPH vad:ptree {\n * ?process rdf:type vad:TypeProcess .\n * ?process rdfs:label ?label .\n * }\n * }\n * `, 'process');\n *\n * @see SPARQL_QUERIES - Коллекция готовых SPARQL запросов\n * @see currentQuads - Глобальный массив квадов для поиска\n * @see currentPrefixes - Глобальный объект префиксов для преобразования URI\n */\n function funSPARQLvalues(sparqlQuery, variableName = 'value') {\n const results = [];\n\n // Если нет текущего store, возвращаем пустой массив\n if (!currentStore || currentQuads.length === 0) {\n console.log('funSPARQLvalues: No data in store');\n return results;\n }\n\n try {\n // Простая реализация через анализ текущих квадов\n // Для сложных SPARQL запросов нужно использовать Comunica\n // Но для базовых SELECT запросов можно обойтись анализом квадов\n\n // Парсим тип запроса\n const selectMatch = sparqlQuery.match(/SELECT\\s+(\\?[\\w]+(?:\\s+\\?[\\w]+)*)/i);\n if (!selectMatch) {\n console.log('funSPARQLvalues: Not a SELECT query');\n return results;\n }\n\n const variables = selectMatch[1].split(/\\s+/).map(v => v.substring(1));\n\n // Парсим WHERE условие (простой парсинг)\n const whereMatch = sparqlQuery.match(/WHERE\\s*\\{([^}]+)\\}/i);\n if (!whereMatch) {\n console.log('funSPARQLvalues: No WHERE clause found');\n return results;\n }\n\n const whereClause = whereMatch[1].trim();\n\n // Парсим triple patterns (упрощенно)\n const triplePatterns = parseTriplePatterns(whereClause);\n\n // Выполняем запрос через сопоставление паттернов\n const bindings = executeSimpleSelect(triplePatterns, variables);\n\n // Формируем результаты\n const seen = new Set();\n bindings.forEach(binding => {\n const valueVar = variableName in binding ? variableName : variables[0];\n const value = binding[valueVar];\n if (value && !seen.has(value)) {\n seen.add(value);\n const label = binding['label'] || getPrefixedName(value, currentPrefixes);\n results.push({\n uri: value,\n label: label\n });\n }\n });\n\n } catch (error) {\n console.error('funSPARQLvalues error:', error);\n }\n\n return results;\n }\n\n /**\n * Парсит простые triple patterns из WHERE клаузы\n * @param {string} whereClause - Строка с triple patterns\n * @returns {Array} - Массив паттернов {subject, predicate, object, graph}\n */\n function parseTriplePatterns(whereClause) {\n const patterns = [];\n\n // Упрощенный парсинг: разбиваем по точкам и точкам с запятыми\n const statements = whereClause.split(/\\s*\\.\\s*/).filter(s => s.trim());\n\n statements.forEach(statement => {\n // Проверяем GRAPH блок\n const graphMatch = statement.match(/GRAPH\\s+(\\S+)\\s*\\{([^}]+)\\}/i);\n if (graphMatch) {\n const graphUri = resolveValue(graphMatch[1]);\n const innerStatements = graphMatch[2].split(/\\s*\\.\\s*/).filter(s => s.trim());\n innerStatements.forEach(inner => {\n const parts = inner.trim().split(/\\s+/);\n if (parts.length >= 3) {\n patterns.push({\n subject: resolveValue(parts[0]),\n predicate: resolveValue(parts[1]),\n object: resolveValue(parts.slice(2).join(' ')),\n graph: graphUri\n });\n }\n });\n } else {\n const parts = statement.trim().split(/\\s+/);\n if (parts.length >= 3) {\n patterns.push({\n subject: resolveValue(parts[0]),\n predicate: resolveValue(parts[1]),\n object: resolveValue(parts.slice(2).join(' ')),\n graph: null\n });\n }\n }\n });\n\n return patterns;\n }\n\n /**\n * Разрешает значение (prefix:local -> полный URI, или оставляет переменную)\n */\n function resolveValue(value) {\n if (!value) return null;\n value = value.trim();\n\n // Если переменная\n if (value.startsWith('?')) {\n return { type: 'variable', name: value.substring(1) };\n }\n\n // Если полный URI в угловых скобках\n if (value.startsWith('<') && value.endsWith('>')) {\n return { type: 'uri', value: value.slice(1, -1) };\n }\n\n // Если prefixed name\n const colonIndex = value.indexOf(':');\n if (colonIndex > 0) {\n const prefix = value.substring(0, colonIndex);\n const local = value.substring(colonIndex + 1);\n const namespace = currentPrefixes[prefix];\n if (namespace) {\n return { type: 'uri', value: namespace + local };\n }\n }\n\n // Если литерал в кавычках\n if (value.startsWith('\"') || value.startsWith(\"'\")) {\n return { type: 'literal', value: value.replace(/^[\"']|[\"']$/g, '') };\n }\n\n // Иначе считаем что это prefixed name без разрешения\n return { type: 'uri', value: value };\n }\n\n /**\n * Выполняет простой SELECT запрос через сопоставление паттернов с квадами\n */\n function executeSimpleSelect(patterns, variables) {\n const bindings = [{}];\n\n patterns.forEach(pattern => {\n const newBindings = [];\n\n bindings.forEach(binding => {\n // Фильтруем квады по паттерну\n currentQuads.forEach(quad => {\n const match = matchQuadToPattern(quad, pattern, binding);\n if (match) {\n newBindings.push({...binding, ...match});\n }\n });\n });\n\n // Заменяем bindings новыми\n bindings.length = 0;\n bindings.push(...newBindings);\n });\n\n return bindings;\n }\n\n /**\n * Сопоставляет квад с паттерном\n */\n function matchQuadToPattern(quad, pattern, currentBinding) {\n const newBinding = {};\n\n // Проверяем граф\n if (pattern.graph) {\n if (pattern.graph.type === 'variable') {\n const boundValue = currentBinding[pattern.graph.name];\n if (boundValue && boundValue !== quad.graph.value) return null;\n newBinding[pattern.graph.name] = quad.graph.value;\n } else if (pattern.graph.type === 'uri') {\n if (quad.graph.value !== pattern.graph.value) return null;\n }\n }\n\n // Проверяем субъект\n if (pattern.subject) {\n if (pattern.subject.type === 'variable') {\n const boundValue = currentBinding[pattern.subject.name];\n if (boundValue && boundValue !== quad.subject.value) return null;\n newBinding[pattern.subject.name] = quad.subject.value;\n } else if (pattern.subject.type === 'uri') {\n if (quad.subject.value !== pattern.subject.value) return null;\n }\n }\n\n // Проверяем предикат\n if (pattern.predicate) {\n if (pattern.predicate.type === 'variable') {\n const boundValue = currentBinding[pattern.predicate.name];\n if (boundValue && boundValue !== quad.predicate.value) return null;\n newBinding[pattern.predicate.name] = quad.predicate.value;\n } else if (pattern.predicate.type === 'uri') {\n if (quad.predicate.value !== pattern.predicate.value) return null;\n }\n }\n\n // Проверяем объект\n if (pattern.object) {\n if (pattern.object.type === 'variable') {\n const boundValue = currentBinding[pattern.object.name];\n const quadObjectValue = quad.object.value;\n if (boundValue && boundValue !== quadObjectValue) return null;\n newBinding[pattern.object.name] = quadObjectValue;\n } else if (pattern.object.type === 'uri') {\n if (quad.object.value !== pattern.object.value) return null;\n } else if (pattern.object.type === 'literal') {\n if (quad.object.value !== pattern.object.value) return null;\n }\n }\n\n return newBinding;\n }\n\n // ==============================================================================\n // SPARQL ЗАПРОСЫ ДЛЯ SMART DESIGN\n // ==============================================================================\n // SPARQL запросы перенесены в отдельный файл sparql-queries.js\n\n // ==============================================================================\n // ФУНКЦИИ КОПИРОВАНИЯ В БУФЕР ОБМЕНА\n // ==============================================================================\n\n /**\n * Копирует значение выбранного поля в буфер обмена\n * @param {string} selectId - ID элемента select\n */\n async function copyFieldValue(selectId) {\n const select = document.getElementById(selectId);\n if (!select) return;\n\n const value = select.value;\n if (!value || value === '' || value === '__NEW__') {\n showSmartDesignMessage('Выберите значение для копирования', 'info');\n return;\n }\n\n // Получаем текст выбранной опции\n const selectedOption = select.options[select.selectedIndex];\n const textToCopy = selectedOption ? selectedOption.textContent : value;\n\n try {\n await navigator.clipboard.writeText(textToCopy);\n\n // Визуальная индикация успеха\n const btn = select.parentElement.querySelector('.copy-to-clipboard-btn');\n if (btn) {\n btn.classList.add('copied');\n btn.textContent = '✓';\n setTimeout(() => {\n btn.classList.remove('copied');\n btn.textContent = '📋';\n }, 1500);\n }\n\n showSmartDesignMessage(`Скопировано: ${textToCopy}`, 'success');\n } catch (error) {\n console.error('Copy failed:', error);\n showSmartDesignMessage('Ошибка копирования в буфер обмена', 'error');\n }\n }\n\n // ==============================================================================\n // ФУНКЦИИ ДЛЯ МОДАЛЬНОГО ОКНА NEW TRIG\n // ==============================================================================\n\n /**\n * Обновляет поля ID и label в модальном окне New TriG\n * на основе выбранного концепта процесса\n */\n function updateNewTrigFields() {\n const processConceptSelect = document.getElementById('new-trig-process-concept');\n const trigIdInput = document.getElementById('new-trig-id');\n const trigLabelInput = document.getElementById('new-trig-label');\n\n if (!processConceptSelect || !trigIdInput || !trigLabelInput) return;\n\n const selectedUri = processConceptSelect.value;\n if (!selectedUri) {\n trigIdInput.value = '';\n trigLabelInput.value = '';\n return;\n }\n\n // Получаем ID процесса (например, из vad:p1 -> p1)\n const processId = getPrefixedName(selectedUri, currentPrefixes).replace('vad:', '');\n\n // Формируем ID TriG\n const trigId = `t_${processId}`;\n trigIdInput.value = trigId;\n\n // Получаем label процесса\n const selectedOption = processConceptSelect.options[processConceptSelect.selectedIndex];\n const processLabel = selectedOption ? selectedOption.textContent : processId;\n\n // Формируем label TriG в формате \"Схема t_{processId} процесса {processId}\"\n trigLabelInput.value = `Схема ${trigId} процесса ${processId}`;\n }\n\n /**\n * Заполняет выпадающий список концептов процессов в модальном окне New TriG\n * Загружает из ptree только те процессы, у которых ещё нет TriG схемы\n */\n function populateNewTrigProcessConcepts() {\n const processConceptSelect = document.getElementById('new-trig-process-concept');\n if (!processConceptSelect) return;\n\n processConceptSelect.innerHTML = '<option value=\"\">-- Выберите концепт процесса --</option>';\n\n // Получаем все концепты процессов из ptree\n const processConcepts = getProcessSubjects();\n\n // Получаем процессы, которые уже имеют TriG схему\n const processesWithTrig = new Set();\n currentQuads.forEach(quad => {\n const predicate = quad.predicate.value;\n if (predicate === 'http://example.org/vad#hasTrig' ||\n predicate.endsWith('#hasTrig')) {\n processesWithTrig.add(quad.subject.value);\n }\n });\n\n // Фильтруем - показываем только процессы без TriG\n processConcepts.forEach(p => {\n // Показываем все процессы, но помечаем те, у которых уже есть TriG\n const option = document.createElement('option');\n option.value = p.uri;\n\n if (processesWithTrig.has(p.uri)) {\n option.textContent = `${p.label} (уже имеет TriG)`;\n option.disabled = true;\n option.style.color = '#999';\n } else {\n option.textContent = p.label;\n }\n\n processConceptSelect.appendChild(option);\n });\n }\n\n // ==============================================================================\n // ЗАГРУЗКА ТЕХНОЛОГИЧЕСКОГО ПРИЛОЖЕНИЯ\n // ==============================================================================\n\n /**\n * Загружает vad-basic-ontology_tech_Appendix.ttl и парсит технологические объекты\n * Вызывается при старте приложения\n */\n async function loadTechAppendix() {\n try {\n const response = await fetch('vad-basic-ontology_tech_Appendix.ttl');\n if (!response.ok) {\n console.warn('Tech appendix file not found, using default configuration');\n return;\n }\n\n const ttlContent = await response.text();\n\n // Парсим TTL файл с использованием Promise для ожидания завершения\n const techQuads = await new Promise((resolve, reject) => {\n const parser = new N3.Parser({ format: 'text/turtle' });\n const quads = [];\n\n parser.parse(ttlContent, (error, quad, prefixes) => {\n if (error) {\n reject(error);\n return;\n }\n if (quad) {\n quads.push(quad);\n } else {\n // quad is null, parsing is complete\n resolve(quads);\n }\n });\n });\n\n // Извлекаем группы предикатов\n extractPredicateGroups(techQuads);\n\n techAppendixData.loaded = true;\n console.log('Tech appendix loaded successfully');\n console.log('Predicate groups:', Object.keys(techAppendixData.predicateGroups).length);\n console.log('Auto-generated predicates:', Object.keys(techAppendixData.autoGeneratedPredicates).length);\n\n } catch (error) {\n console.error('Error loading tech appendix:', error);\n }\n }\n\n /**\n * Извлекает группы предикатов из технологических объектов\n */\n function extractPredicateGroups(quads) {\n const VAD_NS = 'http://example.org/vad#';\n\n quads.forEach(quad => {\n const subject = quad.subject.value;\n const predicate = quad.predicate.value;\n const object = quad.object.value;\n\n // Извлекаем includePredicate\n if (predicate === VAD_NS + 'includePredicate' ||\n predicate.endsWith('#includePredicate')) {\n if (!techAppendixData.predicateGroups[subject]) {\n techAppendixData.predicateGroups[subject] = [];\n }\n techAppendixData.predicateGroups[subject].push(object);\n }\n\n // Извлекаем autoGeneratedPredicate\n if (predicate === VAD_NS + 'autoGeneratedPredicate' ||\n predicate.endsWith('#autoGeneratedPredicate')) {\n if (!techAppendixData.autoGeneratedPredicates[subject]) {\n techAppendixData.autoGeneratedPredicates[subject] = [];\n }\n techAppendixData.autoGeneratedPredicates[subject].push(object);\n }\n\n // Извлекаем contextTriGType\n if (predicate === VAD_NS + 'contextTriGType' ||\n predicate.endsWith('#contextTriGType')) {\n techAppendixData.contextTriGTypes[subject] = object;\n }\n });\n }\n\n /**\n * Получает предикаты для заданного технологического объекта\n * @param {string} techObjectUri - URI технологического объекта\n * @returns {Array} - Массив URI предикатов\n */\n function getPredicatesFromTechObject(techObjectUri) {\n // Нормализуем URI\n let normalizedUri = techObjectUri;\n if (techObjectUri.startsWith('vad:')) {\n normalizedUri = 'http://example.org/vad#' + techObjectUri.substring(4);\n }\n\n return techAppendixData.predicateGroups[normalizedUri] || [];\n }\n\n /**\n * Проверяет, является ли предикат автоматически генерируемым\n * Если techObjectUri не указан, проверяет во всех технологических объектах\n * @param {string} predicateUri - URI предиката\n * @param {string} [techObjectUri] - URI технологического объекта (опционально)\n * @returns {boolean}\n */\n function isAutoGeneratedPredicate(predicateUri, techObjectUri) {\n if (!predicateUri) return false;\n\n let normalizedPredUri = predicateUri;\n if (predicateUri.startsWith('vad:')) {\n normalizedPredUri = 'http://example.org/vad#' + predicateUri.substring(4);\n }\n\n // Если techObjectUri указан, проверяем только в этом объекте\n if (techObjectUri) {\n let normalizedTechUri = techObjectUri;\n if (techObjectUri.startsWith('vad:')) {\n normalizedTechUri = 'http://example.org/vad#' + techObjectUri.substring(4);\n }\n const autoGenerated = techAppendixData.autoGeneratedPredicates[normalizedTechUri] || [];\n return autoGenerated.includes(normalizedPredUri);\n }\n\n // Если techObjectUri не указан, проверяем во всех технологических объектах\n for (const techUri in techAppendixData.autoGeneratedPredicates) {\n const autoGenerated = techAppendixData.autoGeneratedPredicates[techUri];\n if (autoGenerated && autoGenerated.includes(normalizedPredUri)) {\n return true;\n }\n }\n return false;\n }\n\n // ==============================================================================\n // ОБНОВЛЕННЫЕ ФУНКЦИИ SMART DESIGN\n // ==============================================================================\n\n /**\n * Обновленная функция заполнения Subject dropdown\n * Использует SPARQL запросы для динамического формирования справочника\n */\n function updateSubjectsBySubjectTypeWithSPARQL() {\n const subjectSelect = document.getElementById('smart-design-subject');\n const subjectTypeSelect = document.getElementById('smart-design-subject-type');\n const trigSelect = document.getElementById('smart-design-trig');\n\n if (!subjectSelect || !subjectTypeSelect || !trigSelect) return;\n\n const subjectType = subjectTypeSelect.value;\n const trigUri = trigSelect.value;\n\n subjectSelect.innerHTML = '<option value=\"\">-- Выберите Subject --</option>';\n subjectSelect.innerHTML += '<option value=\"__NEW__\">New (создать новый)</option>';\n\n if (!subjectType || !trigUri) return;\n\n const trigContext = getTrigContext(trigUri);\n let subjects = [];\n\n // Используем SPARQL-подобный запрос к данным\n if (subjectType === 'vad:TypeProcess') {\n if (trigContext === 'ptree') {\n // Получаем концепты процессов из ptree\n subjects = getProcessSubjects();\n } else if (trigContext === 'vadProcessDia') {\n // Получаем индивиды процессов из текущего TriG\n subjects = getProcessIndividualsInTriG(trigUri);\n }\n } else if (subjectType === 'vad:ExecutorGroup') {\n // Получаем группы исполнителей из текущего TriG\n subjects = getExecutorGroupsInTriG(trigUri);\n } else if (subjectType === 'vad:TypeExecutor') {\n if (trigContext === 'rtree') {\n // Получаем исполнителей из rtree\n subjects = getExecutorSubjects();\n }\n } else if (subjectType === 'vad:VADProcessDia') {\n // Для редактирования самого TriG\n subjects = [{\n uri: trigUri,\n label: getPrefixedName(trigUri, currentPrefixes)\n }];\n } else if (subjectType === 'vad:ProcessTree') {\n subjects = [{\n uri: 'http://example.org/vad#ptree',\n label: 'vad:ptree'\n }];\n } else if (subjectType === 'vad:ExecutorTree') {\n subjects = [{\n uri: 'http://example.org/vad#rtree',\n label: 'vad:rtree'\n }];\n }\n\n subjects.forEach(s => {\n const option = document.createElement('option');\n option.value = s.uri;\n option.textContent = s.label;\n subjectSelect.appendChild(option);\n });\n }\n\n /**\n * Получает индивиды процессов в конкретном TriG (VADProcessDia)\n * Индивиды определяются наличием предиката vad:isSubprocessTrig\n */\n function getProcessIndividualsInTriG(trigUri) {\n const individuals = [];\n const seen = new Set();\n\n // Нормализуем URI\n const normalizedTrigUri = normalizeUri(trigUri);\n\n currentQuads.forEach(quad => {\n const predicate = quad.predicate.value;\n\n // Ищем триплеты с vad:isSubprocessTrig\n if (predicate === 'http://example.org/vad#isSubprocessTrig' ||\n predicate.endsWith('#isSubprocessTrig')) {\n\n const objectUri = normalizeUri(quad.object.value);\n\n if (objectUri === normalizedTrigUri) {\n const subjectUri = quad.subject.value;\n if (!seen.has(subjectUri)) {\n seen.add(subjectUri);\n\n // Получаем label из ptree\n const label = getLabelFromPtree(subjectUri) ||\n getPrefixedName(subjectUri, currentPrefixes);\n\n individuals.push({\n uri: subjectUri,\n label: label\n });\n }\n }\n }\n });\n\n return individuals;\n }\n\n /**\n * Получает группы исполнителей в конкретном TriG\n */\n function getExecutorGroupsInTriG(trigUri) {\n const groups = [];\n const seen = new Set();\n\n const normalizedTrigUri = normalizeUri(trigUri);\n\n currentQuads.forEach(quad => {\n if (quad.graph.value === normalizedTrigUri) {\n const predicate = quad.predicate.value;\n\n // Ищем rdf:type vad:ExecutorGroup\n if ((predicate === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' ||\n predicate.endsWith('#type')) &&\n (quad.object.value === 'http://example.org/vad#ExecutorGroup' ||\n quad.object.value.endsWith('#ExecutorGroup'))) {\n\n const subjectUri = quad.subject.value;\n if (!seen.has(subjectUri)) {\n seen.add(subjectUri);\n\n // Ищем label\n const label = getLabelInGraph(subjectUri, normalizedTrigUri) ||\n getPrefixedName(subjectUri, currentPrefixes);\n\n groups.push({\n uri: subjectUri,\n label: label\n });\n }\n }\n }\n });\n\n return groups;\n }\n\n /**\n * Получает label объекта из ptree\n */\n function getLabelFromPtree(subjectUri) {\n const ptreeUri = 'http://example.org/vad#ptree';\n\n for (const quad of currentQuads) {\n if (quad.graph.value === ptreeUri &&\n quad.subject.value === subjectUri &&\n (quad.predicate.value === 'http://www.w3.org/2000/01/rdf-schema#label' ||\n quad.predicate.value.endsWith('#label'))) {\n return quad.object.value;\n }\n }\n return null;\n }\n\n /**\n * Получает label объекта в конкретном графе\n */\n function getLabelInGraph(subjectUri, graphUri) {\n for (const quad of currentQuads) {\n if (quad.graph.value === graphUri &&\n quad.subject.value === subjectUri &&\n (quad.predicate.value === 'http://www.w3.org/2000/01/rdf-schema#label'
[2026-01-26T19:23:56.122Z] [INFO] ||\n quad.predicate.value.endsWith('#label'))) {\n return quad.object.value;\n }\n }\n return null;\n }\n\n /**\n * Нормализует URI (убирает prefix, если нужно)\n */\n function normalizeUri(uri) {\n if (!uri) return '';\n\n // Если уже полный URI\n if (uri.startsWith('http://') || uri.startsWith('https://')) {\n return uri;\n }\n\n // Если prefixed name\n const colonIndex = uri.indexOf(':');\n if (colonIndex > 0) {\n const prefix = uri.substring(0, colonIndex);\n const local = uri.substring(colonIndex + 1);\n const namespace = currentPrefixes[prefix];\n if (namespace) {\n return namespace + local;\n }\n }\n\n return uri;\n }\n\n /**\n * Получает субъекты-исполнители из rtree\n */\n function getExecutorSubjects() {\n const subjects = [];\n const seen = new Set();\n const rtreeUri = 'http://example.org/vad#rtree';\n\n currentQuads.forEach(quad => {\n if (quad.graph.value === rtreeUri) {\n const predicate = quad.predicate.value;\n\n // Ищем rdf:type vad:Executor\n if ((predicate === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' ||\n predicate.endsWith('#type')) &&\n (quad.object.value === 'http://example.org/vad#Executor' ||\n quad.object.value.endsWith('#Executor'))) {\n\n const subjectUri = quad.subject.value;\n if (!seen.has(subjectUri)) {\n seen.add(subjectUri);\n\n const label = getLabelInGraph(subjectUri, rtreeUri) ||\n getPrefixedName(subjectUri, currentPrefixes);\n\n subjects.push({\n uri: subjectUri,\n label: label\n });\n }\n }\n }\n });\n\n return subjects;\n }\n\n /**\n * Обновленная функция заполнения Predicate dropdown с учетом автогенерируемых предикатов\n */\n function updatePredicateBySubjectTypeWithAutoGen() {\n const subjectTypeSelect = document.getElementById('smart-design-subject-type');\n const predicateSelect = document.getElementById('smart-design-predicate');\n const trigSelect = document.getElementById('smart-design-trig');\n\n if (!subjectTypeSelect || !predicateSelect || !trigSelect) return;\n\n const subjectType = subjectTypeSelect.value;\n const trigUri = trigSelect.value;\n const trigContext = getTrigContext(trigUri);\n\n // Сохраняем текущее выбранное значение\n const currentPredicateValue = predicateSelect.value;\n\n predicateSelect.innerHTML = '<option value=\"\">-- Выберите Predicate --</option>';\n\n if (!subjectType) {\n // Показываем все предикаты если тип не выбран\n const allPredicates = getAllPredicates();\n allPredicates.forEach(p => {\n const option = document.createElement('option');\n option.value = p.uri;\n option.textContent = p.label;\n predicateSelect.appendChild(option);\n });\n return;\n }\n\n // Получаем допустимые предикаты для типа\n const allowedPredicates = getPredicatesForSubjectType(subjectType, trigContext);\n\n // Определяем технологический объект для проверки автогенерации\n let techObjectUri = '';\n if (subjectType === 'vad:TypeProcess' && trigContext === 'vadProcessDia') {\n techObjectUri = 'http://example.org/vad#IndividProcessPredicate';\n } else if (subjectType === 'vad:ExecutorGroup') {\n techObjectUri = 'http://example.org/vad#ConceptExecutorGroupPredicate';\n } else if (subjectType === 'vad:VADProcessDia') {\n techObjectUri = 'http://example.org/vad#ConceptTriGPredicate';\n }\n\n allowedPredicates.forEach(pred => {\n const option = document.createElement('option');\n\n // Разворачиваем в полный URI\n let fullUri = pred;\n for (const [prefix, namespace] of Object.entries(currentPrefixes)) {\n if (pred.startsWith(prefix + ':')) {\n fullUri = namespace + pred.substring(prefix.length + 1);\n break;\n }\n }\n option.value = fullUri;\n\n // Проверяем, является ли предикат автогенерируемым\n const isAutoGen = techObjectUri && isAutoGeneratedPredicate(techObjectUri, fullUri);\n\n if (isAutoGen) {\n option.textContent = pred + ' (авто)';\n option.disabled = true;\n option.style.color = '#999';\n option.style.fontStyle = 'italic';\n } else {\n option.textContent = pred;\n }\n\n predicateSelect.appendChild(option);\n });\n\n // Восстанавливаем выбор если значение все еще доступно и не disabled\n const availableOption = Array.from(predicateSelect.options).find(\n o => o.value === currentPredicateValue && !o.disabled\n );\n if (availableOption) {\n predicateSelect.value = currentPredicateValue;\n }\n }\n\n /**\n * Генерирует автоматические триплеты при создании нового индивида процесса\n * @param {string} processUri - URI нового процесса\n * @param {string} trigUri - URI текущего TriG\n * @returns {string} - Дополнительные триплеты для SPARQL INSERT\n */\n function generateAutoTriples(processUri, trigUri) {\n // Извлекаем ID процесса для создания ExecutorGroup\n const processId = getPrefixedName(processUri, currentPrefixes).replace('vad:', '');\n const executorGroupUri = `vad:ExecutorGroup_${processId}`;\n\n // Генерируем автоматические триплеты\n const autoTriples = `\n # Автоматически созданные триплеты для нового индивида процесса\n ${getPrefixedName(processUri, currentPrefixes)} vad:isSubprocessTrig ${getPrefixedName(trigUri, currentPrefixes)} .\n ${getPrefixedName(processUri, currentPrefixes)} vad:hasExecutor ${executorGroupUri} .\n ${executorGroupUri} rdf:type vad:ExecutorGroup .\n ${executorGroupUri} rdfs:label \"Группа исполнителей процесса ${processId}\" .`;\n\n return autoTriples;\n }\n\n // ==============================================================================\n // ИНИЦИАЛИЗАЦИЯ\n // ==============================================================================\n\n // Загружаем технологическое приложение при старте\n document.addEventListener('DOMContentLoaded', function() {\n loadTechAppendix();\n });\n </script>\n</body>\n</html>\n",
[2026-01-26T19:23:56.123Z] [INFO] "after": "<!DOCTYPE html>\n<html lang=\"ru\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>RDF Grapher ver8 - Визуализация RDF данных с поддержкой TriG + SPARQL Smart Design + Ontology </title>\n\n <!--\n RDF Grapher ver8a - Сервис для парсинга RDF данных и их визуализации в виде графа\n Новые возможности ver8: SPARQL Smart Design + Ontology\n Этот сервис является продолжением https://www.ldf.fi/service/rdf-grapher в части BPM\n Использует клиентские JavaScript библиотеки:\n - N3.js для парсинга RDF (замена Redland Raptor)\n - Viz.js для рендеринга графов (замена Graphviz)\n\n Сервис работает полностью на стороне клиента и может быть размещен на GitHub Pages\n -->\n\n <!-- Стили для интерфейса -->\n <link rel=\"stylesheet\" href=\"styles.css\">\n</head>\n<body>\n <!-- Контейнер для панелей свойств узлов -->\n <div id=\"properties-panels-container\"></div>\n\n <div class=\"description\">\n <p>\n <strong>Загрузить пример RDF данных:</strong>\n <span class=\"example-link\" onclick=\"loadExampleTrigVADv4()\">Trig VADv4</span>\n </p>\n <div id=\"example-status\" class=\"example-status\" style=\"display: none;\"></div>\n </div>\n\n <div class=\"container\">\n <div class=\"rdf-input-header\">\n <label for=\"rdf-input\">RDF данные или URI:</label>\n <button class=\"show-in-window-btn\" onclick=\"showRdfInSeparateWindow()\">Показать в окне</button>\n <button class=\"clear-btn\" onclick=\"clearRdfInput()\">Очистить</button>\n <button class=\"save-as-btn\" onclick=\"saveAsFile()\">Сохранить как</button>\n <button class=\"load-btn\" onclick=\"document.getElementById('file-input').click()\">Загрузить</button>\n <button class=\"test-btn\" onclick=\"testRdfValidation()\">Тест</button>\n <button class=\"virtual-rdf-btn\" onclick=\"showVirtualRDFData()\">virtualRDFdata</button>\n <input type=\"file\" id=\"file-input\" style=\"display: none;\" accept=\".ttl,.rdf,.nt,.nq,.trig\" onchange=\"loadFile(event)\">\n </div>\n <textarea id=\"rdf-input\" placeholder=\"Введите RDF данные в формате TriG...\"></textarea>\n\n <div class=\"format-selectors\">\n <div class=\"form-group\">\n <label for=\"input-format\">Входной формат:</label>\n <select id=\"input-format\">\n <option value=\"trig\" selected>TriG</option>\n </select>\n </div>\n\n <div class=\"form-group\">\n <label for=\"output-format\">Выходной формат:</label>\n <select id=\"output-format\">\n <option value=\"svg\" selected>SVG</option>\n <option value=\"png\">PNG</option>\n </select>\n </div>\n\n <div class=\"form-group\">\n <label for=\"layout-engine\">Движок компоновки:</label>\n <select id=\"layout-engine\">\n <option value=\"dot\" selected>dot (иерархическая)</option>\n <option value=\"neato\">neato (spring model)</option>\n <option value=\"fdp\">fdp (force-directed)</option>\n <option value=\"circo\">circo (круговая)</option>\n <option value=\"twopi\">twopi (радиальная)</option>\n </select>\n </div>\n\n <div class=\"form-group\">\n <label for=\"visualization-mode\">Режим визуализации:</label>\n <select id=\"visualization-mode\" onchange=\"updateModeDescription()\">\n <option value=\"vad-trig\" selected>Режим VAD TriG</option>\n </select>\n <div class=\"mode-description\" id=\"mode-description\">\n VAD диаграмма с поддержкой иерархии TriG графов\n </div>\n </div>\n\n <div class=\"form-group\">\n <label for=\"sparql-mode\">Режим SPARQL:</label>\n <select id=\"sparql-mode\" onchange=\"toggleSparqlPanel()\">\n <option value=\"smart-design\" selected>SPARQL Smart Design</option>\n </select>\n <div class=\"mode-description\" id=\"sparql-mode-description\">\n Smart Design для создания SPARQL запросов\n </div>\n </div>\n\n <div class=\"form-group\" id=\"max-label-length-group\">\n <label for=\"max-label-length\">Макс. длина имени:</label>\n <input type=\"number\" id=\"max-label-length\" value=\"25\" min=\"5\" max=\"200\" style=\"width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;\">\n <div class=\"mode-description\" id=\"max-label-length-description\">\n Перенос по словам при превышении\n </div>\n </div>\n\n <div class=\"form-group\" id=\"max-vad-row-length-group\" style=\"display: none;\">\n <label for=\"max-vad-row-length\">Макс. длина VAD:</label>\n <input type=\"number\" id=\"max-vad-row-length\" value=\"8\" min=\"2\" max=\"20\" style=\"width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;\">\n <div class=\"mode-description\" id=\"max-vad-row-length-description\">\n Количество VAD-элементов в строке до переноса\n </div>\n </div>\n </div>\n\n <button id=\"visualize-btn\" onclick=\"visualize()\">Показать</button>\n </div>\n\n <!-- Smart Design Container - отображается только при выборе \"SPARQL Smart Design\" в режиме VAD TriG -->\n <div class=\"smart-design-container\" id=\"smart-design-container\">\n <!-- Панель Smart Design -->\n <div class=\"smart-design-panel\">\n <h3>Smart Design</h3>\n <!-- Верхний ряд: TriG -->\n <div class=\"smart-design-trig-row\">\n <div class=\"smart-design-field smart-design-field-trig\">\n <label for=\"smart-design-trig\">TriG:</label>\n <select id=\"smart-design-trig\">\n <option value=\"\">-- Выберите TriG --</option>\n </select>\n </div>\n </div>\n <!-- Нижний ряд: Subject Type, Subject, Predicate, Object -->\n <div class=\"smart-design-fields\">\n <div class=\"smart-design-field\">\n <label for=\"smart-design-subject-type\">Subject Type:</label>\n <div class=\"smart-design-field-wrapper\">\n <select id=\"smart-design-subject-type\">
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment