Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/asowerp_2026-practice-of-programming-in-python-and-cpp.json
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
25 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "asowerp/2026-practice-of-programming-in-python-and-cpp", "files": {"ui/task3_widget.py": {"b9055e875c52beba21a8e483cb9bd8b5b65ddb8b": {"t": 1786068483.86647, "n": 21682, "h": "d341779693e241b4", "c": "from __future__ import annotations\n\nfrom PySide6.QtCore import QObject, QThread, Qt, Signal, Slot\nfrom PySide6.QtGui import QFont\nfrom PySide6.QtWidgets import (\n QComboBox,\n QFormLayout,\n QGroupBox,\n QHBoxLayout,\n QLabel,\n QLineEdit,\n QPlainTextEdit,\n QPushButton,\n QSplitter,\n QTextEdit,\n QVBoxLayout,\n QWidget,\n)\n\nfrom engine.ai_log_assistant import AIDebugResult, build_model_config, explain_log_mismatch, get_model_names\nfrom engine.class_manager import ClassManager\nfrom engine.task3_log_helper import LogComparison, build_log_text, compare_logs, filter_events, summarize_bundle\nfrom engine.warcraft_engine import STANDARD_STAGE_DEFINITIONS, SimulationBundle\nfrom ui.block_workspace import BlockProgramEditor, BlockSpec, FieldSpec\nfrom ui.timeline_controller import TimelineController\nfrom ui.timeline_panel import TimelinePanel\n\n\nclass AIAnalysisWorker(QObject):\n progress = Signal(str)\n finished = Signal(object)\n\n def __init__(self, config, comparison: LogComparison, expected_text: str, actual_text: str) -> None:\n super().__init__()\n self.config = config\n self.comparison = comparison\n self.expected_text = expected_text\n self.actual_text = actual_text\n\n @Slot()\n def run(self) -> None:\n try:\n result = explain_log_mismatch(\n self.config,\n self.comparison,\n expected_text=self.expected_text,\n actual_text=self.actual_text,\n progress=self.progress.emit,\n )\n except Exception as exc:\n result = AIDebugResult(False, f\"AI 分析线程异常: {exc}\")\n self.finished.emit(result)\n\n\nclass Task3Widget(QWidget):\n def __init__(self, manager: ClassManager | None = None) -> None:\n super().__init__()\n self.manager = manager or ClassManager.get_instance()\n self.imported_bundle: SimulationBundle | None = None\n self.last_comparison: LogComparison | None = None\n self.timeline_controller: TimelineController | None = None\n self.ai_thread: QThread | None = None\n self.ai_worker: AIAnalysisWorker | None = None\n self._build_ui()\n\n def _build_ui(self) -> None:\n main_layout = QVBoxLayout(self)\n main_layout.setContentsMargins(16, 16, 16, 16)\n main_layout.setSpacing(12)\n\n title = QLabel(\"Task3:完整日志生成与对拍\")\n title_font = QFont()\n title_font.setPointSize(16)\n title_font.setBold(True)\n title.setFont(title_font)\n\n subtitle = QLabel(\n \"Task3 消费 Task2 导出的整局事件流。可以生成标准日志、筛选局部事件、对比学生输出,\"\n \"并在提供 API Key 后让 AI 辅助定位格式或逻辑错误。\"\n )\n subtitle.setWordWrap(True)\n subtitle.setStyleSheet(\"color: #94A3B8; font-size: 13px;\")\n main_layout.addWidget(title)\n main_layout.addWidget(subtitle)\n\n action_row = QHBoxLayout()\n self.example_btn = QPushButton(\"加载推荐积木\")\n self.run_btn = QPushButton(\"执行日志脚本\")\n self.run_btn.setObjectName(\"btnPrimary\")\n self.clear_btn = QPushButton(\"清空差异视图\")\n self.ai_btn = QPushButton(\"AI 分析差异\")\n self.example_btn.setToolTip(\"自动填入导入、生成日志、对拍输出的推荐流程。\")\n self.run_btn.setToolTip(\"按左侧积木顺序生成标准日志并对拍你的输出。\")\n self.clear_btn.setToolTip(\"清空右侧日志、差异和 AI 建议,不删除左侧积木脚本。\")\n self.ai_btn.setToolTip(\"在存在差异时,用所选模型辅助定位原因。\")\n self.example_btn.clicked.connect(self.load_example_script)\n self.run_btn.clicked.connect(self.run_script)\n self.clear_btn.clicked.connect(self.clear_outputs)\n self.ai_btn.clicked.connect(self.run_ai_debug)\n action_row.addWidget(self.example_btn)\n action_row.addWidget(self.run_btn)\n action_row.addWidget(self.clear_btn)\n action_row.addWidget(self.ai_btn)\n action_row.addStretch(1)\n main_layout.addLayout(action_row)\n\n splitter = QSplitter(Qt.Orientation.Horizontal)\n splitter.setHandleWidth(1)\n self.block_editor = BlockProgramEditor(self._build_block_specs())\n splitter.addWidget(self.block_editor)\n splitter.addWidget(self._build_result_panel())\n splitter.setSizes([600, 780])\n splitter.setToolTip(\"拖动中间的分隔线可以调整左右面板宽度\")\n main_layout.addWidget(splitter)\n self._refresh_ai_button_state()\n\n def _build_result_panel(self) -> QWidget:\n container = QWidget()\n layout = QVBoxLayout(container)\n\n self.import_status_label = QLabel(\"尚未接收 Task2 导出的整局事件流。\")\n self.import_status_label.setStyleSheet(\"font-weight: 700; color: #4F46E5;\")\n layout.addWidget(self.import_status_label)\n\n self.summary_label = QLabel(\"导入 Task2 结果后,再用左侧积木生成标准日志并对拍。\")\n self.summary_label.setStyleSheet(\"font-weight: 700; color: #4F46E5;\")\n layout.addWidget(self.summary_label)\n\n right_splitter = QSplitter(Qt.Orientation.Horizontal)\n right_splitter.setHandleWidth(1)\n\n left_widget = QWidget()\n left_layout = QVBoxLayout(left_widget)\n left_layout.setContentsMargins(0, 0, 0, 0)\n\n timeline_group = QGroupBox(\"导入事件时间轴\")\n timeline_layout = QVBoxLayout(timeline_group)\n self.timeline_panel = TimelinePanel(\n \"Task3 对拍时间轴\",\n \"从 Task2 导入事件后,这里会显示同一条事件流,便于按时间定位日志差异。\",\n )\n self.timeline_controller = TimelineController(self.timeline_panel)\n self.timeline_controller.eventSelected.connect(self.focus_timeline_event)\n timeline_layout.addWidget(self.timeline_panel)\n left_layout.addWidget(timeline_group, 1)\n\n ai_group = QGroupBox(\"AI 调错助手\")\n ai_layout = QFormLayout(ai_group)\n self.ai_model_combo = QComboBox()\n self.ai_model_combo.addItems(get_model_names())\n self.ai_key_edit = QLineEdit()\n self.ai_key_edit.setEchoMode(QLineEdit.EchoMode.Password)\n self.ai_key_edit.setPlaceholderText(\"填写 API Key,仅用于本次请求,不会保存\")\n self.ai_model_override_edit = QLineEdit()\n self.ai_model_override_edit.setPlaceholderText(\"可选:覆盖模型名,例如 deepseek-v4-flash 或 deepseek-v4-pro\")\n self.ai_base_url_edit = QLineEdit()\n self.ai_base_url_edit.setPlaceholderText(\"可选:OpenAI 兼容 base URL,例如 https://api.deepseek.com\")\n ai_layout.addRow(\"模型\", self.ai_model_combo)\n ai_layout.addRow(\"API Key\", self.ai_key_edit)\n ai_layout.addRow(\"模型名\", self.ai_model_override_edit)\n ai_layout.addRow(\"接口地址\", self.ai_base_url_edit)\n left_layout.addWidget(ai_group)\n\n process_group = QGroupBox(\"脚本执行日志\")\n process_layout = QVBoxLayout(process_group)\n self.process_output = QPlainTextEdit()\n self.process_output.setReadOnly(True)\n self.process_output.setPlaceholderText(\"执行日志脚本后,这里会显示每个积木的处理结果。\")\n process_layout.addWidget(self.process_output)\n left_layout.addWidget(process_group)\n\n right_widget = QWidget()\n right_layout = QVBoxLayout(right_widget)\n right_layout.setContentsMargins(0, 0, 0, 0)\n\n text_row = QHBoxLayout()\n\n expected_group = QGroupBox(\"当前标准日志\")\n expected_layout = QVBoxLayout(expected_group)\n self.expected_output = QPlainTextEdit()\n self.expected_output.setReadOnly(True)\n self.expected_output.setPlaceholderText(\"生成标准日志后会显示在这里。\")\n expected_layout.addWidget(self.expected_output)\n\n actual_group = QGroupBox(\"你的输出\")\n actual_layout = QVBoxLayout(actual_group)\n self.actual_output = QPlainTextEdit()\n self.actual_output.setPlaceholderText(\"把你程序的输出粘贴到这里,再执行“对比我的输出”。\")\n actual_layout.addWidget(self.actual_output)\n\n text_row.addWidget(expected_group)\n text_row.addWidget(actual_group)\n right_layout.addLayout(text_row, 1)\n\n diff_group = QGroupBox(\"差异高亮\")\n diff_layout = QVBoxLayout(diff_group)\n self.diff_view = QTextEdit()\n self.diff_view.setReadOnly(True)\n self.diff_view.setPlaceholderText(\"执行“对比我的输出”后,这里会显示逐行差异。\")\n diff_layout.addWidget(self.diff_view)\n right_layout.addWidget(diff_group, 1)\n\n ai_result_group = QGroupBox(\"AI 调错建议\")\n ai_result_layout = QVBoxLayout(ai_result_group)\n self.ai_output = QPlainTextEdit()\n self.ai_output.setReadOnly(True)\n self.ai_output.setPlaceholderText(\"先执行日志脚本并产生差异,再点击“AI 分析差异”。\")\n ai_result_layout.addWidget(self.ai_output)\n right_layout.addWidget(ai_result_group, 1)\n\n right_splitter.addWidget(left_widget)\n right_splitter.addWidget(right_widget)\n right_splitter.setSizes([320, 460])\n right_splitter.setToolTip(\"拖动分隔线可调整右侧两栏宽度\")\n layout.addWidget(right_splitter, 1)\n\n return container\n\n def _build_block_specs(self) -> list[BlockSpec]:\n stage_keys = [definition.key for definition in STANDARD_STAGE_DEFINITIONS]\n return [\n BlockSpec(\n key=\"use_imported_bundle\",\n title=\"使用 Task2 导入结果\",\n color=\"#2563EB\",\n description=\"读取 Task2 导出的整局事件流、Case 参数和阶段日程。\",\n ),\n BlockSpec(\n key=\"inspect_bundle\",\n title=\"查看当前导入摘要\",\n color=\"#0891B2\",\n description=\"查看当前是标准题面模式还是自定义教学模式,并预览前几条事件。\",\n ),\n BlockSpec(\n key=\"filter_hour\",\n title=\"按小时筛选\",\n color=\"#16A34A\",\n description=\"只保留某个小时内的事件,用来单独查看局部输出。\",\n fields=[\n FieldSpec(\"hour\", \"小时\", \"int\", 0, minimum=0, maximum=100, width=140),\n ],\n ),\n BlockSpec(\n key=\"filter_stage\",\n title=\"按阶段筛选\",\n color=\"#22C55E\",\n description=\"只保留某类阶段,例如 spawn / march / battle。\",\n fields=[\n FieldSpec(\"stage_key\", \"阶段\", \"combo\", stage_keys[0], stage_keys, editable=False, width=170),\n ],\n ),\n BlockSpec(\n key=\"filter_keyword\",\n title=\"按关键词筛选\",\n color=\"#65A30D\",\n description=\"按关键词过滤事件描述,例如 city 1、dragon、headquarter。\",\n fields=[\n FieldSpec(\"keyword\", \"关键词\", \"text\", \"city 1\", width=170, placeholder=\"例如 city 1\"),\n ],\n ),\n BlockSpec(\n key=\"generate_log\",\n title=\"生成当前日志\",\n color=\"#EA580C\",\n description=\"把当前筛选后的事件序列转换成标准文本。\",\n ),\n BlockSpec(\n key=\"compare_output\",\n title=\"对比我的输出\",\n color=\"#DC2626\",\n description=\"将当前生成的日志与右侧“你的输出”逐行比较。\",\n ),\n BlockSpec(\n key=\"reset_filters\",\n title=\"清空筛选链\",\n color=\"#475569\",\n description=\"恢复为导入时的完整事件流。\",\n ),\n ]\n\n def load_example_script(self) -> None:\n self.block_editor.set_script([\n {\"key\": \"use_imported_bundle\", \"fields\": {}},\n {\"key\": \"inspect_bundle\", \"fields\": {}},\n {\"key\": \"generate_log\", \"fields\": {}},\n {\"key\": \"compare_output\", \"fields\": {}},\n ])\n self.summary_label.setText(\"已加载推荐积木。下一步先从 Task2 导入事件,再点击“执行日志脚本”。\")\n self.summary_label.setStyleSheet(\"font-weight: 700; color: #4F46E5;\")\n\n def clear_outputs(self) -> None:\n self.process_output.clear()\n self.expected_output.clear()\n self.diff_view.clear()\n self.ai_output.clear()\n self.last_comparison = None\n if self.timeline_controller is not None and self.imported_bundle is None:\n self.timeline_controller.set_events([])\n self.summary_label.setText(\"输出已清空。导入结果和左侧脚本仍保留,可继续对拍。\")\n self.summary_label.setStyleSheet(\"font-weight: 700; color: #4F46E5;\")\n self._refresh_ai_button_state()\n\n def load_simulation_bundle(self, bundle: object) -> None:\n if not isinstance(bundle, SimulationBundle):\n self.imported_bundle = None\n self.import_status_label.setText(\"收到的数据不是合法的 Task2 模拟结果。\")\n self.import_status_label.setStyleSheet(\"font-weight: 700; color: #DC2626;\")\n if self.timeline_controller is not None:\n self.timeline_controller.set_events([])\n return\n self.imported_bundle = bundle\n self.import_status_label.setText(\n f\"已接收 Task2 导出的 {len(bundle.events)} 条事件,当前模式:{bundle.mode_label}。\"\n )\n self.import_status_label.setStyleSheet(\"font-weight: 700; color: #4F46E5;\")\n self.process_output.setPlainText(summarize_bundle(bundle))\n if self.timeline_controller is not None:\n self.timeline_controller.load_simulation_bundle(bundle)\n self.summary_label.setText(\"Task2 事件已导入。下一步加载推荐积木或执行已有日志脚本。\")\n self.summary_label.setStyleSheet(\"font-weight: 700; color: #4F46E5;\")\n self._refresh_ai_button_state()\n\n def focus_timeline_event(self, index: int, event: object) -> None:\n event_time = getattr(event, \"display_time\", \"--:--\")\n stage_key = getattr(event, \"stage_key\", \"unknown\")\n description = getattr(event, \"description\", str(event))\n self.summary_label.setText(f\"时间轴已定位到事件 #{index + 1}:{event_time} / {stage_key}。\")\n self.summary_label.setStyleSheet(\"font-weight: 700; color: #4F46E5;\")\n if self.expected_output.toPlainText().strip():\n matching_line = event.to_log_line() if hasattr(event, \"to_log_line\") else description\n self.diff_view.setHtml(\n \"<div style='font-family:Consolas,monospace; padding:10px;'>\"\n f\"<b>当前时间轴事件 #{index + 1}</b><br>\"\n f\"时间:{event_time}<br>阶段:{stage_key}<br>\"\n f\"<pre style='white-space:pre-wrap;'>{matching_line}</pre>\"\n \"</div>\"\n )\n\n def run_script(self) -> None:\n script = self.block_editor.get_script()\n if not script:\n self.summary_label.setText(\"工作区还是空的,请先拖拽日志积木。\")\n self.summary_label.setStyleSheet(\"font-weight: 700; color: #DC2626;\")\n return\n\n context: dict[str, object] = {\n \"bundle\": None,\n \"events\": [],\n \"log_text\": \"\",\n }\n messages: list[str] = []\n last_summary = \"日志脚本执行结束。\"\n last_html = \"\"\n self.last_comparison = None\n\n for index, block in enumerate(script, start=1):\n ok, message, summary, html = self._execute_block(context, str(block.get(\"key\", \"\")), block.get(\"fields\", {}))\n prefix = \"[通过]\" if ok else \"[错误]\"\n messages.append(f\"{prefix} 积木 {index}: {message}\")\n if summary:\n last_summary = summary\n if html:\n last_html = html\n\n self.process_output.setPlainText(\"\\n\".join(messages))\n self.expected_output.setPlainText(str(context.get(\"log_text\", \"\")))\n self.diff_view.setHtml(last_html)\n self.summary_label.setText(last_summary)\n color = \"#16A34A\" if \"完全一致\" in last_summary else \"#4F46E5\"\n if \"差异\" in last_summary:\n color = \"#DC2626\"\n self.summary_label.setStyleSheet(f\"font-weight: 700; color: {color};\")\n self._refresh_ai_button_state()\n\n def run_ai_debug(self) -> None:\n expected_text = self.expected_output.toPlainText()\n actual_text = self.actual_output.toPlainText()\n if not expected_text.strip():\n self.ai_output.setPlainText(\"请先执行日志脚本,生成当前标准日志。\")\n self._refresh_ai_button_state()\n return\n comparison = self.last_comparison or compare_logs(expected_text, actual_text)\n if comparison.matched:\n self.ai_output.setPlainText(\"输出已经完全一致,不需要 AI 调错。\")\n self._refresh_ai_button_state()\n return\n try:\n config = build_model_config(\n self.ai_model_combo.currentText(),\n self.ai_key_edit.text(),\n model_override=self.ai_model_override_edit.text(),\n base_url_override=self.ai_base_url_edit.text(),\n )\n except ValueError as exc:\n self.ai_output.setPlainText(str(exc))\n return\n\n self._start_ai_analysis(config, comparison, expected_text, actual_text)\n\n def _start_ai_analysis(self, config, comparison: LogComparison, expected_text: str, actual_text: str) -> None:\n if self.ai_thread is not None and self.ai_thread.isRunning():\n self.ai_output.setPlainText(\"AI 正在思考中,请等待当前分析完成。\")\n return\n\n self.ai_output.setPlainText(\"AI 正在思考中:准备发送差异摘要...\")\n self.ai_btn.setEnabled(False)\n self.ai_btn.setText(\"AI 分析中...\")\n\n self.ai_thread = QThread(self)\n self.ai_worker = AIAnalysisWorker(config, comparison, expected_text, actual_text)\n self.ai_worker.moveToThread(self.ai_thread)\n self.ai_thread.started.connect(self.ai_worker.run)\n self.ai_worker.progress.connect(self._on_ai_progress)\n self.ai_worker.finished.connect(self._on_ai_finished)\n self.ai_worker.finished.connect(self.ai_thread.quit)\n self.ai_worker.finished.connect(self.ai_worker.deleteLater)\n self.ai_thread.finished.connect(self._cleanup_ai_thread)\n self.ai_thread.start()\n\n @Slot(str)\n def _on_ai_progress(self, message: str) -> None:\n self.ai_output.setPlainText(message)\n\n @Slot(object)\n def _on_ai_finished(self, result) -> None:\n if result.ok:\n self.ai_output.setPlainText(result.suggestion)\n else:\n self.ai_output.setPlainText(result.message + (\"\\n\\n\" + result.suggestion if result.suggestion else \"\"))\n\n @Slot()\n def _cleanup_ai_thread(self) -> None:\n if self.ai_thread is not None:\n self.ai_thread.deleteLater()\n self.ai_thread = None\n self.ai_worker = None\n self.ai_btn.setText(\"AI 分析差异\")\n self._refresh_ai_button_state()\n\n def _refresh_ai_button_state(self) -> None:\n if self.ai_thread is not None and self.ai_thread.isRunning():\n self.ai_btn.setEnabled(False)\n self.ai_btn.setToolTip(\"AI 正在后台分析当前差异,请稍等。\")\n return\n has_expected = bool(self.expected_output.toPlainText().strip())\n has_mismatch = self.last_comparison is not None and not self.last_comparison.matched\n self.ai_btn.setEnabled(has_expected and has_mismatch)\n if not has_expected:\n self.ai_btn.setToolTip(\"先执行日志脚本生成标准日志。\")\n elif not has_mismatch:\n self.ai_btn.setToolTip(\"只有存在对拍差异时才需要 AI 分析。\")\n else:\n self.ai_btn.setToolTip(\"使用所选模型分析当前日志差异。\")\n\n def _execute_block(self, context: dict[str, object], key: str, fields: dict[str, object]) -> tuple[bool, str, str, str]:\n bundle = context.get(\"bundle\")\n events = context.get(\"events\")\n if key == \"use_imported_bundle\":\n if self.imported_bundle is None:\n return False, \"当前还没有从 Task2 导入整局事件流。\", \"\", \"\"\n context[\"bundle\"] = self.imported_bundle\n context[\"events\"] = list(self.imported_bundle.events)\n return True, f\"已载入 {len(self.imported_bundle.events)} 条事件。\", f\"当前模式:{self.imported_bundle.mode_label}\", \"\"\n\n if key == \"inspect_bundle\":\n if not isinstance(bundle, SimulationBundle):\n return False, \"请先使用 Task2 导入结果。\", \"\", \"\"\n return True, summarize_bundle(bundle), f\"当前模式:{bundle.mode_label}\", \"\"\n\n if key == \"filter_hour\":\n if not isinstance(events, list):\n return False, \"当前没有事件流,请先导入 Task2 结果。\", \"\", \"\"\n hour = int(fields.get(\"hour\", 0))\n context[\"events\"] = filter_events(events, hour=hour)\n return True, f\"已筛选到第 {hour} 小时,共剩余 {len(context['events'])} 条事件。\", \"\", \"\"\n\n if key == \"filter_stage\":\n if not isinstance(events, list):\n return False, \"当前没有事件流,请先导入 Task2 结果。\", \"\", \"\"\n stage_key = str(fields.get(\"stage_key\", \"\")).strip()\n context[\"events\"] = filter_events(events, stage_key=stage_key)\n return True, f\"已筛选阶段 {stage_key},共剩余 {len(context['events'])} 条事件。\", \"\", \"\"\n\n if key == \"filter_keyword\":\n if not isinstance(events, list):\n return False, \"当前没有事件流,请先导入 Task2 结果。\", \"\", \"\"\n keyword = str(fields.get(\"keyword\", \"\")).strip()\n context[\"events\"] = filter_events(events, city_keyword=keyword)\n return True, f\"已按关键词 `{keyword}` 筛选,共剩余 {len(context['events'])} 条事件。\", \"\", \"\"\n\n if key == \"generate_log\":\n if not isinstance(events, list):\n return False, \"当前没有事件流,请先导入 Task2 结果。\", \"\", \"\"\n context[\"log_text\"] = build_log_text(events)\n if not str(context[\"log_text\"]).strip():\n return True, \"当前筛选链下没有可输出的事件。\", \"日志已生成,但为空。\", \"\"\n return True, f\"已生成 {len(str(context['log_text']).splitlines())} 行日志。\", \"标准日志已生成。\", \"\"\n\n if key == \"compare_output\":\n log_text = str(context.get(\"log_text\", \"\"))\n if not log_text.strip():\n return False, \"请先生成当前日志。\", \"\", \"\"\n comparison = compare_logs(log_text, self.actual_output.toPlainText())\n self.last_comparison = comparison\n return comparison.matched, comparison.summary, comparison.summary, comparison.html\n\n if key == \"reset_filters\":\n if not isinstance(bundle, SimulationBundle):\n return False, \"当前没有导入结果可恢复。\", \"\", \"\"\n context[\"events\"] = list(bundle.events)\n context[\"log_text\"] = \"\"\n return True, \"已恢复为导入时的完整事件流。\", \"筛选链已清空。\", \"\"\n\n return False, f\"未知积木 {key}。\", \"\", \"\"\n"}}}}