First Initialization
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
# modules/__init__.py
|
||||
|
||||
from .segmenter_old import Segmenter
|
||||
from .api_handler import APIHandler
|
||||
from .data_manager import DataManager
|
||||
from .visualizer import Visualizer
|
||||
|
||||
# تعریف صریح کلاسهایی که با ایمپورت پکیج در دسترس قرار میگیرند
|
||||
__all__ = [
|
||||
'Segmenter',
|
||||
'APIHandler',
|
||||
'DataManager',
|
||||
'Visualizer'
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,125 @@
|
||||
import json
|
||||
import re
|
||||
from openai import OpenAI
|
||||
from config import API_KEY, BASE_URL, MODEL_NAME
|
||||
|
||||
|
||||
class APIHandler:
|
||||
def __init__(self):
|
||||
# ایجاد کلاینت با استفاده از کتابخانه رسمی OpenAI
|
||||
self.client = OpenAI(
|
||||
base_url=BASE_URL,
|
||||
api_key=API_KEY
|
||||
)
|
||||
self.model_name = MODEL_NAME
|
||||
|
||||
def _call_api(self, prompt, system_prompt="شما یک دستیار هوش مصنوعی دقیق در تحلیل متن هستید.", temperature=0.3):
|
||||
|
||||
try:
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model_name,
|
||||
messages=[
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
temperature=temperature,
|
||||
timeout=60 # جلوگیری از گیر کردن برنامه در صورت کندی سرور
|
||||
)
|
||||
# استخراج محتوای متنی از ریسپانس استاندارد openai
|
||||
return response.choices[0].message.content.strip()
|
||||
except Exception as e:
|
||||
print(f"API failed {e}")
|
||||
return None
|
||||
|
||||
def get_semantic_unit(self, segment_sentences):
|
||||
"""تابع ۱: استخراج واحد معنایی (برای جایگزینی در نودهای درخت)"""
|
||||
text_to_summarize = " ".join(segment_sentences)
|
||||
|
||||
prompt = f"""تو یک دستیار هوش مصنوعی متخصص در تحلیل متن و زبانشناسی پردازشی هستی.
|
||||
وظیفه تو این است که متن ورودی را بخوانی و تنها «هسته اصلی واحد معنایی» آن را سنتز و تولید کنی.
|
||||
|
||||
تعریف واحد معنایی مورد نظر: یک تکجملهٔ خلاصه، دقیق و جامع که یک رویداد کامل را توصیف کند. این جمله باید حتماً دارای ساختار منطقی (شامل عامل/فاعل، مفعول و فعلِ نشاندهنده نتیجه) باشد.
|
||||
|
||||
قوانین اکید خروجی (Strict Rules):
|
||||
1. به هیچوجه از کلمات ربط اضافی، پیشدرآمد (مثل "جمله استخراج شده:"، "خلاصه متن:" یا "بله، حتما") و توضیحات استفاده نکن.
|
||||
|
||||
مثالها (Few-Shot Examples):
|
||||
متن ورودی: "دیروز در پی بارش شدید باران، رودخانه طغیان کرد. این اتفاق باعث شد تا تمام خانههای روستای مجاور زیر آب بروند و مردم مجبور به تخلیه سریع منطقه شوند."
|
||||
خروجی: طغیان رودخانه بر اثر بارش باران منجر به آبگرفتگی خانهها و تخلیه روستا شد.
|
||||
|
||||
متن ورودی: "تیم توسعهدهنده ماهها روی بهینهسازی الگوریتم کار کرد. در نهایت موفق شدند زمان پردازش دادهها را به نصف کاهش دهند که این موضوع رضایت مدیران را در پی داشت."
|
||||
خروجی: تلاش تیم توسعهدهنده برای بهینهسازی الگوریتم باعث کاهش نصف زمان پردازش و جلب رضایت مدیران شد.
|
||||
|
||||
|
||||
متن ورودی اصلی:
|
||||
{text_to_summarize}
|
||||
"""
|
||||
response = self._call_api(prompt)
|
||||
return response if response else "خطا در استخراج واحد معنایی"
|
||||
|
||||
def extract_propositions(self, sentences):
|
||||
"""تابع ۲: استخراج تکتک گزارهها از جملات یک سگمنت (برای شروع ساخت گراف)"""
|
||||
text = " ".join(sentences)
|
||||
|
||||
prompt = f"""
|
||||
متن زیر را بخوان و تمام گزارههای معنایی (اقدامات، واقعیتها یا مفاهیم مستقل) موجود در آن را استخراج کن.
|
||||
قانون اکید: خروجی را فقط به صورت یک آرایه JSON معتبر از رشتهها برگردان و هیچ متن اضافهای ننویس.
|
||||
مثال خروجی: ["گزاره اول", "گزاره دوم"]
|
||||
|
||||
متن:
|
||||
{text}
|
||||
"""
|
||||
response = self._call_api(prompt, temperature=0.2)
|
||||
if not response:
|
||||
return []
|
||||
|
||||
try:
|
||||
clean_json = re.sub(r'```json|```', '', response).strip()
|
||||
propositions = json.loads(clean_json)
|
||||
if isinstance(propositions, list):
|
||||
return propositions
|
||||
return []
|
||||
except Exception as e:
|
||||
print(f"خطا در پارس کردن گزارهها (JSON Error): {e}")
|
||||
print(f"خروجی خام مدل این بود: {response}")
|
||||
return []
|
||||
|
||||
def check_relation(self, prop1, prop2):
|
||||
"""تابع ۳: بررسی رابطه غایی (هدف-وسیله) یا علت و معلولی بین دو گزاره"""
|
||||
prompt = f"""
|
||||
دو گزاره زیر را بررسی کن و ببین آیا رابطه "علت و معلولی" یا "غایی (هدف و وسیله)" بین آنها برقرار است یا خیر.
|
||||
اگر هیچ ارتباطی از این نوع وجود ندارد، دقیقاً و فقط بنویس: "ارتباط وجود ندارد"
|
||||
اگر ارتباط وجود دارد، در یک گزاره کوتاه و دقیق این رابطه را توصیف کن (مثلاً: گزاره اول وسیلهای برای رسیدن به هدف گزاره دوم است).
|
||||
|
||||
گزاره ۱: {prop1}
|
||||
گزاره ۲: {prop2}
|
||||
"""
|
||||
response = self._call_api(prompt, temperature=0.3)
|
||||
return response if response else "ارتباط وجود ندارد"
|
||||
|
||||
def score_relation(self, relation_desc):
|
||||
"""تابع ۴: امتیازدهی به رابطه از 1 تا 10"""
|
||||
if not relation_desc or relation_desc.strip() == "ارتباط وجود ندارد" or relation_desc.strip() == "":
|
||||
return 0
|
||||
|
||||
prompt = f"""
|
||||
با توجه به توصیف رابطه زیر، یک نمره از 1 تا 10 به میزان قوت و منطقی بودن این رابطه علت و معلولی/غایی بده.
|
||||
قانون اکید: فـــقـــط یک عدد صحیح برگردان. هیچ کاراکتر یا کلمهای غیر از عدد ننویس.
|
||||
|
||||
توصیف رابطه:
|
||||
{relation_desc}
|
||||
"""
|
||||
response = self._call_api(prompt, temperature=0.1)
|
||||
|
||||
if not response:
|
||||
return 0
|
||||
|
||||
try:
|
||||
numbers = re.findall(r'\d+', response)
|
||||
if numbers:
|
||||
score = int(numbers[0])
|
||||
return max(1, min(10, score))
|
||||
return 0
|
||||
except Exception as e:
|
||||
print(f"failed to extract scores {e}")
|
||||
return 0
|
||||
@@ -0,0 +1,59 @@
|
||||
import os
|
||||
import csv
|
||||
import json
|
||||
|
||||
|
||||
class DataManager:
|
||||
def __init__(self, base_projects_dir="projects"):
|
||||
self.base_projects_dir = base_projects_dir
|
||||
if not os.path.exists(self.base_projects_dir):
|
||||
os.makedirs(self.base_projects_dir)
|
||||
|
||||
def create_project_folder(self, project_name):
|
||||
"""ایجاد پوشه اصلی پروژه"""
|
||||
project_path = os.path.join(self.base_projects_dir, project_name)
|
||||
if not os.path.exists(project_path):
|
||||
os.makedirs(project_path)
|
||||
return project_path
|
||||
|
||||
def save_tree_data(self, project_path, tree_data_list):
|
||||
"""ذخیره اطلاعات کل درخت در tree_data.csv"""
|
||||
file_path = os.path.join(project_path, "tree_data.csv")
|
||||
headers = ["level", "segment_id", "parent_segment_id", "original_sentences", "semantic_unit"]
|
||||
|
||||
with open(file_path, mode='w', newline='', encoding='utf-8') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=headers)
|
||||
writer.writeheader()
|
||||
for row in tree_data_list:
|
||||
# تبدیل لیست جملات به رشته JSON برای ذخیره امن در CSV
|
||||
row_copy = row.copy()
|
||||
if isinstance(row_copy.get("original_sentences"), list):
|
||||
row_copy["original_sentences"] = json.dumps(row_copy["original_sentences"], ensure_ascii=False)
|
||||
writer.writerow(row_copy)
|
||||
|
||||
def save_graph_data(self, project_path, node_id, propositions, relations, scores):
|
||||
"""ذخیره ۳ فایل CSV مربوط به گراف یک نود خاص"""
|
||||
graph_dir = os.path.join(project_path, f"{node_id}_graph")
|
||||
if not os.path.exists(graph_dir):
|
||||
os.makedirs(graph_dir)
|
||||
|
||||
# 1. save propositions.csv
|
||||
with open(os.path.join(graph_dir, "propositions.csv"), mode='w', newline='', encoding='utf-8') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=["proposition_id", "sentence", "proposition_text"])
|
||||
writer.writeheader()
|
||||
writer.writerows(propositions)
|
||||
|
||||
# 2. save relations.csv
|
||||
with open(os.path.join(graph_dir, "relations.csv"), mode='w', newline='', encoding='utf-8') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=["relation_id", "source_proposition_id", "target_proposition_id",
|
||||
"relation_type"])
|
||||
writer.writeheader()
|
||||
writer.writerows(relations)
|
||||
|
||||
# 3. save scores.csv
|
||||
with open(os.path.join(graph_dir, "scores.csv"), mode='w', newline='', encoding='utf-8') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=["relation_id", "score"])
|
||||
writer.writeheader()
|
||||
writer.writerows(scores)
|
||||
|
||||
return graph_dir
|
||||
@@ -0,0 +1,116 @@
|
||||
import re
|
||||
import numpy as np
|
||||
from sentence_transformers import SentenceTransformer
|
||||
from sklearn.metrics.pairwise import cosine_similarity
|
||||
|
||||
|
||||
class Segmenter:
|
||||
def __init__(self):
|
||||
print("Loading models :")
|
||||
|
||||
self.model_bge = SentenceTransformer("C:/Models/bge-m3", model_kwargs={"use_safetensors": False})
|
||||
self.model_e5 = SentenceTransformer("C:/Models/multilingual-e5-large")
|
||||
self.model_labse = SentenceTransformer("C:/Models/LaBSE_Local")
|
||||
self.model_parsroberta = SentenceTransformer("C:/Models/ParsRoberta")
|
||||
|
||||
print("4 models loaded successfully")
|
||||
|
||||
def get_similarities(self, text1, text2):
|
||||
"""محاسبه میانگین شباهت کسینوسی از ۴ مدل برای دو متن داده شده"""
|
||||
emb_bge = self.model_bge.encode([text1, text2])
|
||||
emb_e5 = self.model_e5.encode([text1, text2])
|
||||
emb_labse = self.model_labse.encode([text1, text2])
|
||||
emb_parsroberta = self.model_parsroberta.encode([text1, text2])
|
||||
|
||||
sim_bge = cosine_similarity([emb_bge[0]], [emb_bge[1]])[0][0]
|
||||
sim_e5 = cosine_similarity([emb_e5[0]], [emb_e5[1]])[0][0]
|
||||
sim_labse = cosine_similarity([emb_labse[0]], [emb_labse[1]])[0][0]
|
||||
sim_parsroberta = cosine_similarity([emb_parsroberta[0]], [emb_parsroberta[1]])[0][0]
|
||||
|
||||
# میانگینگیری از هر ۴ مدل
|
||||
return np.mean([sim_bge, sim_e5, sim_labse, sim_parsroberta])
|
||||
|
||||
def calculate_depth_scores(self, similarities):
|
||||
"""محاسبه عمق درهها (Gap Scores) با استفاده از قلههای چپ و راست"""
|
||||
depth_scores = []
|
||||
n = len(similarities)
|
||||
|
||||
for i in range(n):
|
||||
left_peak = similarities[i]
|
||||
for j in range(i, -1, -1):
|
||||
if similarities[j] >= left_peak:
|
||||
left_peak = similarities[j]
|
||||
else:
|
||||
break
|
||||
|
||||
right_peak = similarities[i]
|
||||
for j in range(i, n):
|
||||
if similarities[j] >= right_peak:
|
||||
right_peak = similarities[j]
|
||||
else:
|
||||
break
|
||||
|
||||
depth = (left_peak - similarities[i]) + (right_peak - similarities[i])
|
||||
depth_scores.append(depth)
|
||||
|
||||
return depth_scores
|
||||
|
||||
def extract_sentences(self, text):
|
||||
"""جداسازی متن اولیه به جملات"""
|
||||
text = text.replace('\n', ' ')
|
||||
sentences = [s.strip() for s in re.split(r'(?<=[.!?؟]) +', text) if s.strip()]
|
||||
return sentences
|
||||
|
||||
def segment_items(self, items, force_count=None, std_multiplier=0.5, min_depth=0.02):
|
||||
"""
|
||||
دریافت جملات یا واحدهای معنایی و کلاسترینگ آنها.
|
||||
اگر پارامتر force_count ارسال شود، الگوریتم موظف است دقیقاً همان تعداد سگمنت تولید کند.
|
||||
"""
|
||||
n_items = len(items)
|
||||
if n_items < 2:
|
||||
return [items]
|
||||
|
||||
similarities = []
|
||||
for i in range(n_items - 1):
|
||||
sim = self.get_similarities(items[i], items[i + 1])
|
||||
similarities.append(sim)
|
||||
|
||||
depth_scores = self.calculate_depth_scores(similarities)
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# حالت اجباری: رساندن تعداد سگمنتها به عدد دقیق (مثلا 7)
|
||||
# -------------------------------------------------------------
|
||||
if force_count and 1 < force_count < n_items:
|
||||
# پیدا کردن عمیقترین درهها برای ایجاد دقیقا force_count - 1 برش
|
||||
top_cut_indices = np.argsort(depth_scores)[-(force_count - 1):]
|
||||
top_cut_indices = sorted(top_cut_indices)
|
||||
|
||||
grouped_segments = []
|
||||
start = 0
|
||||
for idx in top_cut_indices:
|
||||
grouped_segments.append(items[start:idx + 1])
|
||||
start = idx + 1
|
||||
grouped_segments.append(items[start:])
|
||||
return grouped_segments
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# حالت عادی داینامیک: در سطوح پایینتر که هنوز به تعداد هدف نرسیدهایم
|
||||
# -------------------------------------------------------------
|
||||
mean_depth = np.mean(depth_scores)
|
||||
std_depth = np.std(depth_scores)
|
||||
cutoff_threshold = mean_depth + (std_multiplier * std_depth)
|
||||
|
||||
grouped_segments = []
|
||||
current_segment = [items[0]]
|
||||
|
||||
for i in range(len(similarities)):
|
||||
if depth_scores[i] >= cutoff_threshold and depth_scores[i] > min_depth:
|
||||
grouped_segments.append(current_segment)
|
||||
current_segment = [items[i + 1]]
|
||||
else:
|
||||
current_segment.append(items[i + 1])
|
||||
|
||||
if current_segment:
|
||||
grouped_segments.append(current_segment)
|
||||
|
||||
return grouped_segments
|
||||
@@ -0,0 +1,216 @@
|
||||
import networkx as nx
|
||||
from pyvis.network import Network
|
||||
import textwrap
|
||||
import ast
|
||||
import re
|
||||
|
||||
|
||||
class Visualizer:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def build_graph(self, propositions, relations, scores, output_path):
|
||||
"""
|
||||
دریافت ۳ ساختار مجزا (گزارهها، روابط، امتیازها) و رسم گراف
|
||||
"""
|
||||
net = Network(height="100vh", width="100%", bgcolor="#1a1a1a", font_color="white", directed=True,
|
||||
cdn_resources="in_line")
|
||||
|
||||
# ساخت دیکشنریهای کمکی برای جستجوی سریع
|
||||
prop_dict = {p['proposition_id']: p['proposition_text'] for p in propositions}
|
||||
score_dict = {s['relation_id']: s['score'] for s in scores}
|
||||
|
||||
# 1. اضافه کردن نودها (گزارهها)
|
||||
for p_id, text in prop_dict.items():
|
||||
wrapped_text = "\n".join(textwrap.wrap(text, width=40))
|
||||
net.add_node(
|
||||
p_id,
|
||||
label=f"گزاره {p_id}",
|
||||
title=wrapped_text, # نمایش متن کامل هنگام هاور پیشفرض
|
||||
shape="box",
|
||||
color={"background": "#264653", "border": "#1b3039", "highlight": {"background": "#2a9d8f"}},
|
||||
font={"face": "B Nazanin, Tahoma, Arial", "size": 16, "color": "white"}
|
||||
)
|
||||
|
||||
# 2. اضافه کردن یالها (روابط)
|
||||
for rel in relations:
|
||||
source = rel['source_proposition_id']
|
||||
target = rel['target_proposition_id']
|
||||
rel_id = rel['relation_id']
|
||||
rel_type = rel['relation_type']
|
||||
score = score_dict.get(rel_id, 0)
|
||||
|
||||
wrapped_rel = "\n".join(textwrap.wrap(rel_type, width=40))
|
||||
edge_title = f"امتیاز: {score}\n\nنوع رابطه:\n{wrapped_rel}"
|
||||
|
||||
if source in prop_dict and target in prop_dict:
|
||||
net.add_edge(
|
||||
source,
|
||||
target,
|
||||
label=str(score),
|
||||
title=edge_title, # نمایش متن در سایدبار از طریق رویداد
|
||||
width=max(2, int(score) / 2), # ضخامت خط بر اساس امتیاز
|
||||
color={"color": "#e9c46a", "highlight": "#f4a261"},
|
||||
font={"align": "middle", "color": "#ffea00", "background": "#1a1a1a"},
|
||||
arrows={"to": {"enabled": True, "scaleFactor": 1.2}}
|
||||
)
|
||||
|
||||
# تنظیمات گراف و فعالسازی هاور
|
||||
net.set_options("""
|
||||
{
|
||||
"physics": {
|
||||
"forceAtlas2Based": {
|
||||
"gravitationalConstant": -50,
|
||||
"centralGravity": 0.01,
|
||||
"springLength": 150,
|
||||
"springConstant": 0.08
|
||||
},
|
||||
"minVelocity": 0.75,
|
||||
"solver": "forceAtlas2Based"
|
||||
},
|
||||
"interaction": { "hover": true, "navigationButtons": true }
|
||||
}
|
||||
""")
|
||||
|
||||
html_content = net.generate_html(output_path)
|
||||
|
||||
# تزریق اسکریپت برای ارسال اطلاعات هاور به سایدبار صفحه وب
|
||||
hover_js = """
|
||||
<script type="text/javascript">
|
||||
network.on("hoverEdge", function (params) {
|
||||
var edgeData = edges.get(params.edge);
|
||||
window.parent.postMessage({ type: 'hoverEdge', info: edgeData.title }, '*');
|
||||
});
|
||||
network.on("hoverNode", function (params) {
|
||||
var nodeData = nodes.get(params.node);
|
||||
window.parent.postMessage({ type: 'hoverNode', info: nodeData.title }, '*');
|
||||
});
|
||||
network.on("blurEdge", function (params) {
|
||||
window.parent.postMessage({ type: 'blur', info: '' }, '*');
|
||||
});
|
||||
network.on("blurNode", function (params) {
|
||||
window.parent.postMessage({ type: 'blur', info: '' }, '*');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
"""
|
||||
html_content = html_content.replace('</body>', hover_js)
|
||||
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
f.write(html_content)
|
||||
return output_path
|
||||
|
||||
def build_tree(self, tree_data_list, output_path):
|
||||
net = Network(height="100vh", width="100%", bgcolor="#1a1a1a", font_color="white", directed=True,
|
||||
cdn_resources="in_line")
|
||||
valid_node_ids = set()
|
||||
|
||||
for node_data in tree_data_list:
|
||||
node_id = str(node_data.get('segment_id', '')).strip()
|
||||
valid_node_ids.add(node_id)
|
||||
level = int(node_data.get('level', 0))
|
||||
|
||||
full_text = node_data.get('semantic_unit', 'بدون متن')
|
||||
wrapped_text = "\n".join(textwrap.wrap(full_text, width=40))
|
||||
|
||||
raw_sentences = node_data.get('original_sentences', [])
|
||||
if isinstance(raw_sentences, str):
|
||||
try:
|
||||
raw_sentences = ast.literal_eval(raw_sentences)
|
||||
except (ValueError, SyntaxError):
|
||||
pass
|
||||
|
||||
if isinstance(raw_sentences, list):
|
||||
sentences_str = "<br><br>".join(raw_sentences)
|
||||
num_sentences = len(raw_sentences)
|
||||
else:
|
||||
sentences_str = str(raw_sentences)
|
||||
num_sentences = len([s for s in sentences_str.split('.') if s.strip()]) if sentences_str else 0
|
||||
|
||||
# متمایز کردن رنگ برگها (سطح 0) از نودهای میانی
|
||||
bg_color = "#e76f51" if level == 0 else "#2a9d8f"
|
||||
|
||||
net.add_node(
|
||||
node_id,
|
||||
label=f"سطح {level}\n{node_id}",
|
||||
title=wrapped_text,
|
||||
level=level,
|
||||
shape="box",
|
||||
sentences=sentences_str,
|
||||
num_sentences=num_sentences,
|
||||
color={"background": bg_color, "border": "#1f6e64", "highlight": {"background": "#3bc2b4"}},
|
||||
font={"face": "B Nazanin, Tahoma, Arial", "size": 16}
|
||||
)
|
||||
|
||||
for node_data in tree_data_list:
|
||||
node_id = str(node_data.get('segment_id', '')).strip()
|
||||
parent_val = node_data.get('parent_segment_id')
|
||||
|
||||
if parent_val and str(parent_val).strip() != "":
|
||||
clean_parents_str = re.sub(r"[\[\]\'\"\s]", "", str(parent_val))
|
||||
parents_list = clean_parents_str.split(',')
|
||||
|
||||
for p_id in parents_list:
|
||||
if p_id in valid_node_ids:
|
||||
net.add_edge(
|
||||
p_id,
|
||||
node_id,
|
||||
width=3,
|
||||
color={"color": "#e9c46a", "highlight": "#f4a261"},
|
||||
arrows={"to": {"enabled": True, "scaleFactor": 1.2}},
|
||||
smooth={'type': 'cubicBezier', 'forceDirection': 'vertical', 'roundness': 0.4}
|
||||
)
|
||||
|
||||
net.set_options("""
|
||||
{
|
||||
"layout": {
|
||||
"hierarchical": {
|
||||
"enabled": true,
|
||||
"direction": "DU",
|
||||
"sortMethod": "directed",
|
||||
"nodeSpacing": 250,
|
||||
"levelSeparation": 150
|
||||
}
|
||||
},
|
||||
"physics": {
|
||||
"hierarchicalRepulsion": {
|
||||
"centralGravity": 0.0,
|
||||
"springLength": 100,
|
||||
"springConstant": 0.01,
|
||||
"nodeDistance": 250,
|
||||
"damping": 0.09
|
||||
},
|
||||
"solver": "hierarchicalRepulsion"
|
||||
},
|
||||
"interaction": { "hover": true, "navigationButtons": true }
|
||||
}
|
||||
""")
|
||||
|
||||
html_content = net.generate_html(output_path)
|
||||
|
||||
# تزریق اسکریپت کلیک درخت
|
||||
click_js = """
|
||||
<script type="text/javascript">
|
||||
network.on("click", function (params) {
|
||||
if (params.nodes.length > 0) {
|
||||
var nodeId = params.nodes[0];
|
||||
var clickedNode = nodes.get(nodeId);
|
||||
window.parent.postMessage({
|
||||
type: 'node_click',
|
||||
id: nodeId,
|
||||
label: clickedNode.label,
|
||||
summary: clickedNode.title,
|
||||
sentences: clickedNode.sentences || 'اطلاعات جملات موجود نیست.',
|
||||
num_sentences: clickedNode.num_sentences || 0
|
||||
}, '*');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
"""
|
||||
html_content = html_content.replace('</body>', click_js)
|
||||
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
f.write(html_content)
|
||||
|
||||
return output_path
|
||||
Reference in New Issue
Block a user