Created
January 26, 2026 20:05
-
-
Save konard/0abd132e1d07dcf8adfe18ea2adeedc6 to your computer and use it in GitHub Desktop.
Solution draft log for https://github.com/bpmbpm/rdf-grapher/pull/172
This file has been truncated, but you can view the full file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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()\">×</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()\">×</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, '"');\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}')\">×</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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\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()\">×</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()\">×</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, '"');\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}')\">×</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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\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\"><${namespace}></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