Remove deprecated files and modules from the txt2graph tool, including the main application, graph builder, text extractor, and associated resources. This cleanup streamlines the project structure and prepares for future enhancements.
This commit is contained in:
parent
08f417f3b7
commit
3156f9453d
20 changed files with 0 additions and 3112 deletions
Binary file not shown.
|
|
@ -1,3 +0,0 @@
|
|||
# Zep Cloud API Key
|
||||
# 从 https://app.getzep.com 获取
|
||||
ZEP_API_KEY=your_zep_api_key_here
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
# txt2graph
|
||||
|
||||
将文本文件(PDF/Markdown/TXT)转换为知识图谱的工具。
|
||||
|
||||
## 功能特点
|
||||
|
||||
- 支持多种文件格式:PDF、Markdown、TXT
|
||||
- 基于 Zep Cloud 的知识图谱构建
|
||||
- 自动提取真实存在的实体(人物、公司、组织、地点、产品、事件、媒体)
|
||||
- 交互式图谱可视化界面
|
||||
- 实体和关系的详细展示
|
||||
|
||||
## 实体类型
|
||||
|
||||
本工具只提取现实生活中真实存在的、可以有行动的实体:
|
||||
|
||||
| 类型 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| Person | 真实的人物 | 马化腾、Elon Musk |
|
||||
| Company | 注册的公司 | 腾讯、Apple Inc. |
|
||||
| Organization | 组织机构 | 武汉大学、联合国 |
|
||||
| Location | 地理位置 | 北京、硅谷 |
|
||||
| Product | 具体产品/服务 | iPhone、微信 |
|
||||
| Event | 真实事件 | 2024年巴黎奥运会 |
|
||||
| Media | 媒体机构 | 人民日报、CNN |
|
||||
|
||||
## 安装
|
||||
|
||||
### 1. 激活conda环境
|
||||
|
||||
```bash
|
||||
conda activate MiroFish
|
||||
```
|
||||
|
||||
### 2. 安装依赖
|
||||
|
||||
```bash
|
||||
cd txt2graph
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 3. 配置环境变量
|
||||
|
||||
复制 `.env.example` 为 `.env` 并填入你的 Zep API Key:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# 编辑 .env 文件,填入 ZEP_API_KEY
|
||||
```
|
||||
|
||||
获取 API Key: https://app.getzep.com
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 方式1: Web界面(推荐)
|
||||
|
||||
启动 Streamlit 应用:
|
||||
|
||||
```bash
|
||||
streamlit run app.py
|
||||
```
|
||||
|
||||
然后在浏览器中打开显示的URL(通常是 http://localhost:8501)
|
||||
|
||||
### 方式2: 命令行
|
||||
|
||||
```python
|
||||
from text_extractor import extract_text
|
||||
from graph_builder import build_graph_from_text
|
||||
|
||||
# 从文件提取文本
|
||||
text = extract_text("your_document.pdf")
|
||||
|
||||
# 构建知识图谱
|
||||
graph_data = build_graph_from_text(
|
||||
text=text,
|
||||
graph_name="我的知识图谱",
|
||||
progress_callback=print
|
||||
)
|
||||
|
||||
# 查看结果
|
||||
print(f"节点数: {len(graph_data.nodes)}")
|
||||
print(f"边数: {len(graph_data.edges)}")
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
txt2graph/
|
||||
├── app.py # Streamlit Web应用
|
||||
├── text_extractor.py # 文本提取模块
|
||||
├── graph_builder.py # 图谱构建模块
|
||||
├── ontology.py # 实体类型定义
|
||||
├── requirements.txt # 依赖列表
|
||||
├── .env.example # 环境变量示例
|
||||
└── README.md # 说明文档
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **处理时间**:知识图谱构建可能需要几分钟,取决于文本长度
|
||||
2. **API限制**:Zep Cloud 有API调用限制,大文件建议分批处理
|
||||
3. **文本质量**:输入文本的质量直接影响实体提取效果
|
||||
4. **费用**:Zep Cloud 可能会产生API调用费用,请查看其定价
|
||||
|
||||
## 技术栈
|
||||
|
||||
- [Zep Cloud](https://www.getzep.com/) - 知识图谱服务
|
||||
- [Streamlit](https://streamlit.io/) - Web界面框架
|
||||
- [PyVis](https://pyvis.readthedocs.io/) - 图可视化
|
||||
- [PyMuPDF](https://pymupdf.readthedocs.io/) - PDF处理
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
497
txt2graph/app.py
497
txt2graph/app.py
|
|
@ -1,497 +0,0 @@
|
|||
"""
|
||||
txt2graph 可视化界面
|
||||
基于Streamlit和PyVis实现知识图谱可视化
|
||||
"""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import streamlit as st
|
||||
from pathlib import Path
|
||||
from pyvis.network import Network
|
||||
import streamlit.components.v1 as components
|
||||
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
|
||||
from text_extractor import extract_text, split_text_into_chunks
|
||||
from graph_builder import ZepGraphBuilder, GraphData
|
||||
|
||||
|
||||
# 页面配置
|
||||
st.set_page_config(
|
||||
page_title="txt2graph - 知识图谱生成器",
|
||||
page_icon="🕸️",
|
||||
layout="wide",
|
||||
initial_sidebar_state="expanded"
|
||||
)
|
||||
|
||||
# 自定义CSS样式
|
||||
st.markdown("""
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&family=JetBrains+Mono&display=swap');
|
||||
|
||||
.main {
|
||||
font-family: 'Noto Sans SC', sans-serif;
|
||||
}
|
||||
|
||||
.stTitle {
|
||||
font-weight: 700 !important;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.stats-card {
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin: 10px 0;
|
||||
border: 1px solid rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.stats-number {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: #667eea;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
|
||||
.stats-label {
|
||||
font-size: 0.9rem;
|
||||
color: #a0a0a0;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.entity-tag {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.8rem;
|
||||
margin: 2px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.entity-Person { background: rgba(255, 107, 107, 0.2); color: #ff6b6b; border: 1px solid #ff6b6b; }
|
||||
.entity-Company { background: rgba(78, 205, 196, 0.2); color: #4ecdc4; border: 1px solid #4ecdc4; }
|
||||
.entity-Organization { background: rgba(69, 183, 209, 0.2); color: #45b7d1; border: 1px solid #45b7d1; }
|
||||
.entity-Location { background: rgba(150, 206, 180, 0.2); color: #96ceb4; border: 1px solid #96ceb4; }
|
||||
.entity-Product { background: rgba(255, 238, 173, 0.2); color: #ffeead; border: 1px solid #ffeead; }
|
||||
.entity-Event { background: rgba(220, 198, 224, 0.2); color: #dcc6e0; border: 1px solid #dcc6e0; }
|
||||
.entity-Media { background: rgba(255, 183, 77, 0.2); color: #ffb74d; border: 1px solid #ffb74d; }
|
||||
|
||||
.sidebar .stButton > button {
|
||||
width: 100%;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar .stButton > button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
</style>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
|
||||
# 实体类型对应的颜色
|
||||
ENTITY_COLORS = {
|
||||
"Person": "#ff6b6b",
|
||||
"Company": "#4ecdc4",
|
||||
"Organization": "#45b7d1",
|
||||
"Location": "#96ceb4",
|
||||
"Product": "#ffeead",
|
||||
"Event": "#dcc6e0",
|
||||
"Media": "#ffb74d",
|
||||
}
|
||||
|
||||
|
||||
def create_pyvis_graph(graph_data: GraphData) -> str:
|
||||
"""
|
||||
创建PyVis图并返回HTML
|
||||
"""
|
||||
# 创建网络图
|
||||
net = Network(
|
||||
height="700px",
|
||||
width="100%",
|
||||
bgcolor="#0e1117",
|
||||
font_color="white",
|
||||
directed=True,
|
||||
select_menu=True,
|
||||
filter_menu=True,
|
||||
)
|
||||
|
||||
# 配置物理引擎
|
||||
net.set_options("""
|
||||
{
|
||||
"nodes": {
|
||||
"font": {
|
||||
"size": 14,
|
||||
"face": "Noto Sans SC, Arial"
|
||||
},
|
||||
"borderWidth": 2,
|
||||
"shadow": true
|
||||
},
|
||||
"edges": {
|
||||
"color": {
|
||||
"inherit": false,
|
||||
"color": "#555555",
|
||||
"highlight": "#667eea"
|
||||
},
|
||||
"arrows": {
|
||||
"to": {
|
||||
"enabled": true,
|
||||
"scaleFactor": 0.5
|
||||
}
|
||||
},
|
||||
"smooth": {
|
||||
"type": "continuous",
|
||||
"roundness": 0.2
|
||||
},
|
||||
"font": {
|
||||
"size": 10,
|
||||
"color": "#888888",
|
||||
"face": "Noto Sans SC, Arial"
|
||||
}
|
||||
},
|
||||
"physics": {
|
||||
"enabled": true,
|
||||
"barnesHut": {
|
||||
"gravitationalConstant": -5000,
|
||||
"centralGravity": 0.3,
|
||||
"springLength": 150,
|
||||
"springConstant": 0.04,
|
||||
"damping": 0.09
|
||||
},
|
||||
"stabilization": {
|
||||
"enabled": true,
|
||||
"iterations": 200
|
||||
}
|
||||
},
|
||||
"interaction": {
|
||||
"hover": true,
|
||||
"tooltipDelay": 100,
|
||||
"navigationButtons": true,
|
||||
"keyboard": true
|
||||
}
|
||||
}
|
||||
""")
|
||||
|
||||
# 构建节点UUID到名称的映射
|
||||
node_map = {node.uuid: node for node in graph_data.nodes}
|
||||
|
||||
# 添加节点
|
||||
for node in graph_data.nodes:
|
||||
# 确定节点类型和颜色
|
||||
node_type = node.labels[0] if node.labels else "Unknown"
|
||||
color = ENTITY_COLORS.get(node_type, "#888888")
|
||||
|
||||
# 构建工具提示
|
||||
title = f"<b>{node.name}</b><br>"
|
||||
title += f"<i>类型: {node_type}</i><br><br>"
|
||||
if node.summary:
|
||||
title += f"{node.summary[:200]}{'...' if len(node.summary) > 200 else ''}"
|
||||
|
||||
# 根据节点类型调整大小
|
||||
size = 25 if node_type == "Person" else 30 if node_type in ["Company", "Organization"] else 20
|
||||
|
||||
net.add_node(
|
||||
node.uuid,
|
||||
label=node.name,
|
||||
title=title,
|
||||
color=color,
|
||||
size=size,
|
||||
shape="dot",
|
||||
)
|
||||
|
||||
# 添加边
|
||||
for edge in graph_data.edges:
|
||||
if edge.source_node_uuid in node_map and edge.target_node_uuid in node_map:
|
||||
# 构建边的工具提示
|
||||
title = edge.fact if edge.fact else edge.name
|
||||
|
||||
net.add_edge(
|
||||
edge.source_node_uuid,
|
||||
edge.target_node_uuid,
|
||||
title=title,
|
||||
label=edge.name[:20] if edge.name else "",
|
||||
)
|
||||
|
||||
# 生成HTML
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False, encoding='utf-8') as f:
|
||||
net.save_graph(f.name)
|
||||
with open(f.name, 'r', encoding='utf-8') as html_file:
|
||||
html_content = html_file.read()
|
||||
os.unlink(f.name)
|
||||
|
||||
return html_content
|
||||
|
||||
|
||||
def display_stats(graph_data: GraphData):
|
||||
"""显示图谱统计信息"""
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
st.markdown(f"""
|
||||
<div class="stats-card">
|
||||
<div class="stats-number">{len(graph_data.nodes)}</div>
|
||||
<div class="stats-label">实体节点</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
with col2:
|
||||
st.markdown(f"""
|
||||
<div class="stats-card">
|
||||
<div class="stats-number">{len(graph_data.edges)}</div>
|
||||
<div class="stats-label">关系边</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# 统计实体类型分布
|
||||
type_counts = {}
|
||||
for node in graph_data.nodes:
|
||||
node_type = node.labels[0] if node.labels else "Unknown"
|
||||
type_counts[node_type] = type_counts.get(node_type, 0) + 1
|
||||
|
||||
with col3:
|
||||
st.markdown(f"""
|
||||
<div class="stats-card">
|
||||
<div class="stats-number">{len(type_counts)}</div>
|
||||
<div class="stats-label">实体类型</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
|
||||
def display_entity_list(graph_data: GraphData):
|
||||
"""显示实体列表"""
|
||||
st.subheader("实体列表")
|
||||
|
||||
# 按类型分组
|
||||
entities_by_type = {}
|
||||
for node in graph_data.nodes:
|
||||
node_type = node.labels[0] if node.labels else "Unknown"
|
||||
if node_type not in entities_by_type:
|
||||
entities_by_type[node_type] = []
|
||||
entities_by_type[node_type].append(node)
|
||||
|
||||
# 创建标签页
|
||||
if entities_by_type:
|
||||
tabs = st.tabs(list(entities_by_type.keys()))
|
||||
|
||||
for tab, (entity_type, entities) in zip(tabs, entities_by_type.items()):
|
||||
with tab:
|
||||
for entity in entities:
|
||||
with st.expander(f"{entity.name}", expanded=False):
|
||||
if entity.summary:
|
||||
st.write(entity.summary)
|
||||
if entity.attributes:
|
||||
st.json(entity.attributes)
|
||||
|
||||
|
||||
def main():
|
||||
# 标题
|
||||
st.title("txt2graph")
|
||||
st.markdown("*将文本转化为知识图谱*")
|
||||
|
||||
# 侧边栏
|
||||
with st.sidebar:
|
||||
st.header("配置")
|
||||
|
||||
# API Key
|
||||
api_key = st.text_input(
|
||||
"Zep API Key",
|
||||
type="password",
|
||||
value=os.environ.get("ZEP_API_KEY", ""),
|
||||
help="从 https://app.getzep.com 获取API Key"
|
||||
)
|
||||
|
||||
if api_key:
|
||||
os.environ["ZEP_API_KEY"] = api_key
|
||||
|
||||
st.divider()
|
||||
|
||||
# 文件上传
|
||||
st.header("上传文件")
|
||||
uploaded_file = st.file_uploader(
|
||||
"支持 .txt, .md, .pdf 文件",
|
||||
type=["txt", "md", "pdf"],
|
||||
help="上传要转换为知识图谱的文本文件"
|
||||
)
|
||||
|
||||
# 或者直接输入文本
|
||||
st.divider()
|
||||
st.header("或直接输入文本")
|
||||
text_input = st.text_area(
|
||||
"输入文本内容",
|
||||
height=150,
|
||||
placeholder="在此输入或粘贴文本..."
|
||||
)
|
||||
|
||||
st.divider()
|
||||
|
||||
# 高级设置
|
||||
with st.expander("高级设置"):
|
||||
chunk_size = st.slider(
|
||||
"文本分块大小",
|
||||
min_value=500,
|
||||
max_value=4000,
|
||||
value=2000,
|
||||
step=500,
|
||||
help="较小的块处理更稳定,较大的块包含更多上下文"
|
||||
)
|
||||
|
||||
graph_name = st.text_input(
|
||||
"图谱名称",
|
||||
value="Knowledge Graph",
|
||||
help="为生成的图谱命名"
|
||||
)
|
||||
|
||||
st.divider()
|
||||
|
||||
# 生成按钮
|
||||
generate_btn = st.button("生成知识图谱", type="primary", use_container_width=True)
|
||||
|
||||
# 主内容区
|
||||
if "graph_data" not in st.session_state:
|
||||
st.session_state.graph_data = None
|
||||
|
||||
if generate_btn:
|
||||
if not api_key:
|
||||
st.error("请先配置 Zep API Key")
|
||||
return
|
||||
|
||||
# 获取文本内容
|
||||
text_content = None
|
||||
|
||||
if uploaded_file:
|
||||
with st.spinner("正在提取文本..."):
|
||||
# 保存上传的文件到临时位置
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=Path(uploaded_file.name).suffix) as tmp:
|
||||
tmp.write(uploaded_file.getvalue())
|
||||
tmp_path = tmp.name
|
||||
|
||||
try:
|
||||
text_content = extract_text(tmp_path)
|
||||
finally:
|
||||
os.unlink(tmp_path)
|
||||
elif text_input:
|
||||
text_content = text_input
|
||||
else:
|
||||
st.warning("请上传文件或输入文本")
|
||||
return
|
||||
|
||||
if text_content:
|
||||
st.info(f"提取了 {len(text_content)} 个字符的文本")
|
||||
|
||||
# 进度显示
|
||||
progress_bar = st.progress(0)
|
||||
status_text = st.empty()
|
||||
|
||||
try:
|
||||
# 创建图谱构建器
|
||||
builder = ZepGraphBuilder(api_key=api_key)
|
||||
|
||||
# 创建图谱
|
||||
status_text.text("创建图谱...")
|
||||
progress_bar.progress(10)
|
||||
graph_id = builder.create_graph(name=graph_name)
|
||||
|
||||
# 设置本体
|
||||
status_text.text("配置实体类型...")
|
||||
progress_bar.progress(20)
|
||||
builder.set_ontology(graph_id)
|
||||
|
||||
# 分块
|
||||
status_text.text("分割文本...")
|
||||
progress_bar.progress(30)
|
||||
chunks = split_text_into_chunks(text_content, max_chunk_size=chunk_size)
|
||||
st.info(f"文本已分为 {len(chunks)} 个块")
|
||||
|
||||
# 添加到图谱
|
||||
status_text.text("正在发送数据到Zep...")
|
||||
progress_bar.progress(40)
|
||||
|
||||
def update_progress(msg):
|
||||
status_text.text(msg)
|
||||
|
||||
# 分批发送数据
|
||||
task_ids = builder.add_text_to_graph(
|
||||
graph_id=graph_id,
|
||||
text_chunks=chunks,
|
||||
batch_size=3,
|
||||
progress_callback=update_progress
|
||||
)
|
||||
|
||||
# 等待处理完成
|
||||
progress_bar.progress(60)
|
||||
status_text.text("等待Zep处理数据...")
|
||||
|
||||
if task_ids:
|
||||
builder.wait_for_tasks(
|
||||
task_ids,
|
||||
timeout=600,
|
||||
progress_callback=update_progress
|
||||
)
|
||||
|
||||
# 获取图数据
|
||||
status_text.text("获取图谱数据...")
|
||||
progress_bar.progress(90)
|
||||
st.session_state.graph_data = builder.get_graph_data(graph_id)
|
||||
st.session_state.graph_id = graph_id
|
||||
|
||||
progress_bar.progress(100)
|
||||
status_text.text("完成!")
|
||||
st.success(f"知识图谱生成成功! Graph ID: {graph_id}")
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"生成图谱时出错: {str(e)}")
|
||||
import traceback
|
||||
st.code(traceback.format_exc())
|
||||
|
||||
# 显示图谱
|
||||
if st.session_state.graph_data:
|
||||
graph_data = st.session_state.graph_data
|
||||
|
||||
# 统计信息
|
||||
display_stats(graph_data)
|
||||
|
||||
st.divider()
|
||||
|
||||
# 图谱可视化
|
||||
st.subheader("知识图谱可视化")
|
||||
|
||||
if graph_data.nodes:
|
||||
with st.spinner("渲染图谱..."):
|
||||
html_content = create_pyvis_graph(graph_data)
|
||||
components.html(html_content, height=750, scrolling=True)
|
||||
else:
|
||||
st.warning("图谱中没有节点")
|
||||
|
||||
st.divider()
|
||||
|
||||
# 实体列表
|
||||
col1, col2 = st.columns([1, 1])
|
||||
|
||||
with col1:
|
||||
display_entity_list(graph_data)
|
||||
|
||||
with col2:
|
||||
st.subheader("关系列表")
|
||||
if graph_data.edges:
|
||||
for edge in graph_data.edges[:50]: # 只显示前50条
|
||||
st.markdown(f"- **{edge.fact}**" if edge.fact else f"- {edge.name}")
|
||||
if len(graph_data.edges) > 50:
|
||||
st.caption(f"...还有 {len(graph_data.edges) - 50} 条关系")
|
||||
else:
|
||||
st.info("暂无关系数据")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
|
@ -1,415 +0,0 @@
|
|||
"""
|
||||
Zep图谱构建模块
|
||||
负责与Zep云服务交互,构建知识图谱
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import uuid
|
||||
from typing import Optional, Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
from zep_cloud.client import Zep
|
||||
from zep_cloud import EpisodeData, EntityEdgeSourceTarget
|
||||
|
||||
from ontology import ENTITY_TYPES, EDGE_TYPES
|
||||
|
||||
|
||||
@dataclass
|
||||
class GraphNode:
|
||||
"""图节点数据结构"""
|
||||
uuid: str
|
||||
name: str
|
||||
summary: str
|
||||
labels: list[str]
|
||||
attributes: dict
|
||||
|
||||
|
||||
@dataclass
|
||||
class GraphEdge:
|
||||
"""图边数据结构"""
|
||||
uuid: str
|
||||
name: str
|
||||
fact: str
|
||||
source_node_uuid: str
|
||||
target_node_uuid: str
|
||||
attributes: dict
|
||||
|
||||
|
||||
@dataclass
|
||||
class GraphData:
|
||||
"""完整图数据"""
|
||||
graph_id: str
|
||||
nodes: list[GraphNode]
|
||||
edges: list[GraphEdge]
|
||||
|
||||
|
||||
class ZepGraphBuilder:
|
||||
"""Zep知识图谱构建器"""
|
||||
|
||||
def __init__(self, api_key: Optional[str] = None):
|
||||
"""
|
||||
初始化图谱构建器
|
||||
|
||||
Args:
|
||||
api_key: Zep API密钥,如果不提供则从环境变量ZEP_API_KEY读取
|
||||
"""
|
||||
self.api_key = api_key or os.environ.get("ZEP_API_KEY")
|
||||
if not self.api_key:
|
||||
raise ValueError("需要提供ZEP_API_KEY,可以通过参数传入或设置环境变量")
|
||||
|
||||
self.client = Zep(api_key=self.api_key)
|
||||
|
||||
def create_graph(self, graph_id: Optional[str] = None, name: str = "Knowledge Graph") -> str:
|
||||
"""
|
||||
创建新的图谱
|
||||
|
||||
Args:
|
||||
graph_id: 图谱ID,如果不提供则自动生成
|
||||
name: 图谱名称
|
||||
|
||||
Returns:
|
||||
图谱ID
|
||||
"""
|
||||
if graph_id is None:
|
||||
graph_id = f"graph_{uuid.uuid4().hex[:16]}"
|
||||
|
||||
self.client.graph.create(
|
||||
graph_id=graph_id,
|
||||
name=name,
|
||||
description="Knowledge graph generated by txt2graph"
|
||||
)
|
||||
|
||||
return graph_id
|
||||
|
||||
def set_ontology(self, graph_id: str):
|
||||
"""
|
||||
为图谱设置自定义本体(实体和边类型)
|
||||
|
||||
Args:
|
||||
graph_id: 图谱ID
|
||||
"""
|
||||
# 构建边类型的源目标映射
|
||||
edge_definitions = {}
|
||||
|
||||
# WORKS_FOR: Person -> Organization/Company
|
||||
edge_definitions["WORKS_FOR"] = (
|
||||
EDGE_TYPES["WORKS_FOR"],
|
||||
[
|
||||
EntityEdgeSourceTarget(source="Person", target="Organization"),
|
||||
EntityEdgeSourceTarget(source="Person", target="Company"),
|
||||
]
|
||||
)
|
||||
|
||||
# LOCATED_IN: 多种实体 -> Location
|
||||
edge_definitions["LOCATED_IN"] = (
|
||||
EDGE_TYPES["LOCATED_IN"],
|
||||
[
|
||||
EntityEdgeSourceTarget(source="Person", target="Location"),
|
||||
EntityEdgeSourceTarget(source="Organization", target="Location"),
|
||||
EntityEdgeSourceTarget(source="Company", target="Location"),
|
||||
EntityEdgeSourceTarget(source="Event", target="Location"),
|
||||
]
|
||||
)
|
||||
|
||||
# PART_OF: Organization -> Organization, Company -> Company
|
||||
edge_definitions["PART_OF"] = (
|
||||
EDGE_TYPES["PART_OF"],
|
||||
[
|
||||
EntityEdgeSourceTarget(source="Organization", target="Organization"),
|
||||
EntityEdgeSourceTarget(source="Company", target="Company"),
|
||||
]
|
||||
)
|
||||
|
||||
# PRODUCES: Company -> Product
|
||||
edge_definitions["PRODUCES"] = (
|
||||
EDGE_TYPES["PRODUCES"],
|
||||
[
|
||||
EntityEdgeSourceTarget(source="Company", target="Product"),
|
||||
EntityEdgeSourceTarget(source="Organization", target="Product"),
|
||||
]
|
||||
)
|
||||
|
||||
# PARTICIPATES_IN: Person/Organization/Company -> Event
|
||||
edge_definitions["PARTICIPATES_IN"] = (
|
||||
EDGE_TYPES["PARTICIPATES_IN"],
|
||||
[
|
||||
EntityEdgeSourceTarget(source="Person", target="Event"),
|
||||
EntityEdgeSourceTarget(source="Organization", target="Event"),
|
||||
EntityEdgeSourceTarget(source="Company", target="Event"),
|
||||
]
|
||||
)
|
||||
|
||||
# COLLABORATES: 各种实体之间的合作
|
||||
edge_definitions["COLLABORATES"] = (
|
||||
EDGE_TYPES["COLLABORATES"],
|
||||
[
|
||||
EntityEdgeSourceTarget(source="Person", target="Person"),
|
||||
EntityEdgeSourceTarget(source="Company", target="Company"),
|
||||
EntityEdgeSourceTarget(source="Organization", target="Organization"),
|
||||
EntityEdgeSourceTarget(source="Company", target="Organization"),
|
||||
]
|
||||
)
|
||||
|
||||
# COMPETES: 公司之间的竞争
|
||||
edge_definitions["COMPETES"] = (
|
||||
EDGE_TYPES["COMPETES"],
|
||||
[
|
||||
EntityEdgeSourceTarget(source="Company", target="Company"),
|
||||
]
|
||||
)
|
||||
|
||||
# REPORTS: Media报道相关实体
|
||||
edge_definitions["REPORTS"] = (
|
||||
EDGE_TYPES["REPORTS"],
|
||||
[
|
||||
EntityEdgeSourceTarget(source="Media", target="Person"),
|
||||
EntityEdgeSourceTarget(source="Media", target="Company"),
|
||||
EntityEdgeSourceTarget(source="Media", target="Organization"),
|
||||
EntityEdgeSourceTarget(source="Media", target="Event"),
|
||||
]
|
||||
)
|
||||
|
||||
# 设置本体
|
||||
self.client.graph.set_ontology(
|
||||
graph_ids=[graph_id],
|
||||
entities=ENTITY_TYPES,
|
||||
edges=edge_definitions,
|
||||
)
|
||||
|
||||
def add_text_to_graph(
|
||||
self,
|
||||
graph_id: str,
|
||||
text_chunks: list[str],
|
||||
batch_size: int = 3,
|
||||
progress_callback: Optional[Callable] = None
|
||||
) -> list[str]:
|
||||
"""
|
||||
将文本块分批添加到图谱中
|
||||
|
||||
Args:
|
||||
graph_id: 图谱ID
|
||||
text_chunks: 文本块列表
|
||||
batch_size: 每批发送的块数量
|
||||
progress_callback: 进度回调函数
|
||||
|
||||
Returns:
|
||||
任务ID列表
|
||||
"""
|
||||
task_ids = []
|
||||
total_chunks = len(text_chunks)
|
||||
|
||||
# 分批处理
|
||||
for i in range(0, total_chunks, batch_size):
|
||||
batch_chunks = text_chunks[i:i + batch_size]
|
||||
batch_num = i // batch_size + 1
|
||||
total_batches = (total_chunks + batch_size - 1) // batch_size
|
||||
|
||||
if progress_callback:
|
||||
progress_callback(f"发送第 {batch_num}/{total_batches} 批数据 ({len(batch_chunks)} 块)...")
|
||||
|
||||
# 构建episode数据
|
||||
episodes = [
|
||||
EpisodeData(data=chunk, type="text")
|
||||
for chunk in batch_chunks
|
||||
]
|
||||
|
||||
try:
|
||||
# 批量添加
|
||||
batch_result = self.client.graph.add_batch(
|
||||
graph_id=graph_id,
|
||||
episodes=episodes
|
||||
)
|
||||
|
||||
if batch_result and batch_result[0].task_id:
|
||||
task_ids.append(batch_result[0].task_id)
|
||||
|
||||
# 短暂等待,避免请求过快
|
||||
time.sleep(1)
|
||||
|
||||
except Exception as e:
|
||||
if progress_callback:
|
||||
progress_callback(f"批次 {batch_num} 发送失败: {str(e)}")
|
||||
raise
|
||||
|
||||
return task_ids
|
||||
|
||||
def wait_for_tasks(
|
||||
self,
|
||||
task_ids: list[str],
|
||||
timeout: int = 600,
|
||||
progress_callback: Optional[Callable] = None
|
||||
):
|
||||
"""
|
||||
等待所有任务完成
|
||||
|
||||
Args:
|
||||
task_ids: 任务ID列表
|
||||
timeout: 超时时间(秒)
|
||||
progress_callback: 进度回调
|
||||
"""
|
||||
if not task_ids:
|
||||
return
|
||||
|
||||
start_time = time.time()
|
||||
pending_tasks = set(task_ids)
|
||||
completed_tasks = set()
|
||||
|
||||
while pending_tasks:
|
||||
if time.time() - start_time > timeout:
|
||||
if progress_callback:
|
||||
progress_callback(f"警告: 部分任务超时,已完成 {len(completed_tasks)}/{len(task_ids)}")
|
||||
break
|
||||
|
||||
for task_id in list(pending_tasks):
|
||||
try:
|
||||
task = self.client.task.get(task_id=task_id)
|
||||
|
||||
if task.status == "completed":
|
||||
pending_tasks.remove(task_id)
|
||||
completed_tasks.add(task_id)
|
||||
elif task.status == "failed":
|
||||
pending_tasks.remove(task_id)
|
||||
if progress_callback:
|
||||
progress_callback(f"任务失败: {task.error}")
|
||||
|
||||
except Exception as e:
|
||||
if progress_callback:
|
||||
progress_callback(f"检查任务状态出错: {str(e)}")
|
||||
|
||||
if pending_tasks:
|
||||
if progress_callback:
|
||||
elapsed = int(time.time() - start_time)
|
||||
progress_callback(f"等待处理中... 已完成 {len(completed_tasks)}/{len(task_ids)} ({elapsed}秒)")
|
||||
time.sleep(3)
|
||||
|
||||
if progress_callback:
|
||||
progress_callback(f"所有任务处理完成: {len(completed_tasks)}/{len(task_ids)}")
|
||||
|
||||
def get_graph_data(self, graph_id: str) -> GraphData:
|
||||
"""
|
||||
获取图谱的完整数据
|
||||
|
||||
Args:
|
||||
graph_id: 图谱ID
|
||||
|
||||
Returns:
|
||||
GraphData对象,包含所有节点和边
|
||||
"""
|
||||
# 获取所有节点
|
||||
raw_nodes = self.client.graph.node.get_by_graph_id(graph_id=graph_id)
|
||||
nodes = [
|
||||
GraphNode(
|
||||
uuid=node.uuid_,
|
||||
name=node.name,
|
||||
summary=node.summary or "",
|
||||
labels=node.labels or [],
|
||||
attributes=node.attributes or {}
|
||||
)
|
||||
for node in raw_nodes
|
||||
]
|
||||
|
||||
# 获取所有边
|
||||
raw_edges = self.client.graph.edge.get_by_graph_id(graph_id=graph_id)
|
||||
edges = [
|
||||
GraphEdge(
|
||||
uuid=edge.uuid_,
|
||||
name=edge.name or "",
|
||||
fact=edge.fact or "",
|
||||
source_node_uuid=edge.source_node_uuid,
|
||||
target_node_uuid=edge.target_node_uuid,
|
||||
attributes=edge.attributes or {}
|
||||
)
|
||||
for edge in raw_edges
|
||||
]
|
||||
|
||||
return GraphData(
|
||||
graph_id=graph_id,
|
||||
nodes=nodes,
|
||||
edges=edges
|
||||
)
|
||||
|
||||
def delete_graph(self, graph_id: str):
|
||||
"""删除图谱"""
|
||||
self.client.graph.delete(graph_id=graph_id)
|
||||
|
||||
|
||||
def build_graph_from_text(
|
||||
text: str,
|
||||
graph_name: str = "Knowledge Graph",
|
||||
api_key: Optional[str] = None,
|
||||
chunk_size: int = 2000,
|
||||
progress_callback: Optional[Callable] = None
|
||||
) -> GraphData:
|
||||
"""
|
||||
便捷函数:从文本构建知识图谱
|
||||
|
||||
Args:
|
||||
text: 输入文本
|
||||
graph_name: 图谱名称
|
||||
api_key: Zep API密钥
|
||||
chunk_size: 文本分块大小(默认2000字符)
|
||||
progress_callback: 进度回调
|
||||
|
||||
Returns:
|
||||
GraphData对象
|
||||
"""
|
||||
from text_extractor import split_text_into_chunks
|
||||
|
||||
builder = ZepGraphBuilder(api_key=api_key)
|
||||
|
||||
# 创建图谱
|
||||
graph_id = builder.create_graph(name=graph_name)
|
||||
if progress_callback:
|
||||
progress_callback(f"创建图谱: {graph_id}")
|
||||
|
||||
# 设置本体
|
||||
builder.set_ontology(graph_id)
|
||||
if progress_callback:
|
||||
progress_callback("设置实体类型...")
|
||||
|
||||
# 分块处理文本
|
||||
chunks = split_text_into_chunks(text, max_chunk_size=chunk_size)
|
||||
if progress_callback:
|
||||
progress_callback(f"文本分为 {len(chunks)} 个块")
|
||||
|
||||
# 分批添加到图谱
|
||||
task_ids = builder.add_text_to_graph(
|
||||
graph_id=graph_id,
|
||||
text_chunks=chunks,
|
||||
batch_size=3,
|
||||
progress_callback=progress_callback
|
||||
)
|
||||
|
||||
# 等待所有任务完成
|
||||
if task_ids:
|
||||
builder.wait_for_tasks(task_ids, progress_callback=progress_callback)
|
||||
|
||||
# 获取并返回图数据
|
||||
return builder.get_graph_data(graph_id)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 测试
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
|
||||
test_text = """
|
||||
武汉大学是中国著名的高等学府,位于湖北省武汉市。
|
||||
该校的樱花季每年吸引大量游客。
|
||||
马化腾是腾讯公司的创始人,腾讯总部位于深圳。
|
||||
"""
|
||||
|
||||
result = build_graph_from_text(
|
||||
text=test_text,
|
||||
graph_name="测试图谱",
|
||||
progress_callback=print
|
||||
)
|
||||
|
||||
print(f"\n节点数: {len(result.nodes)}")
|
||||
for node in result.nodes:
|
||||
print(f" - {node.name} ({node.labels})")
|
||||
|
||||
print(f"\n边数: {len(result.edges)}")
|
||||
for edge in result.edges:
|
||||
print(f" - {edge.fact}")
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,189 +0,0 @@
|
|||
function neighbourhoodHighlight(params) {
|
||||
// console.log("in nieghbourhoodhighlight");
|
||||
allNodes = nodes.get({ returnType: "Object" });
|
||||
// originalNodes = JSON.parse(JSON.stringify(allNodes));
|
||||
// if something is selected:
|
||||
if (params.nodes.length > 0) {
|
||||
highlightActive = true;
|
||||
var i, j;
|
||||
var selectedNode = params.nodes[0];
|
||||
var degrees = 2;
|
||||
|
||||
// mark all nodes as hard to read.
|
||||
for (let nodeId in allNodes) {
|
||||
// nodeColors[nodeId] = allNodes[nodeId].color;
|
||||
allNodes[nodeId].color = "rgba(200,200,200,0.5)";
|
||||
if (allNodes[nodeId].hiddenLabel === undefined) {
|
||||
allNodes[nodeId].hiddenLabel = allNodes[nodeId].label;
|
||||
allNodes[nodeId].label = undefined;
|
||||
}
|
||||
}
|
||||
var connectedNodes = network.getConnectedNodes(selectedNode);
|
||||
var allConnectedNodes = [];
|
||||
|
||||
// get the second degree nodes
|
||||
for (i = 1; i < degrees; i++) {
|
||||
for (j = 0; j < connectedNodes.length; j++) {
|
||||
allConnectedNodes = allConnectedNodes.concat(
|
||||
network.getConnectedNodes(connectedNodes[j])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// all second degree nodes get a different color and their label back
|
||||
for (i = 0; i < allConnectedNodes.length; i++) {
|
||||
// allNodes[allConnectedNodes[i]].color = "pink";
|
||||
allNodes[allConnectedNodes[i]].color = "rgba(150,150,150,0.75)";
|
||||
if (allNodes[allConnectedNodes[i]].hiddenLabel !== undefined) {
|
||||
allNodes[allConnectedNodes[i]].label =
|
||||
allNodes[allConnectedNodes[i]].hiddenLabel;
|
||||
allNodes[allConnectedNodes[i]].hiddenLabel = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// all first degree nodes get their own color and their label back
|
||||
for (i = 0; i < connectedNodes.length; i++) {
|
||||
// allNodes[connectedNodes[i]].color = undefined;
|
||||
allNodes[connectedNodes[i]].color = nodeColors[connectedNodes[i]];
|
||||
if (allNodes[connectedNodes[i]].hiddenLabel !== undefined) {
|
||||
allNodes[connectedNodes[i]].label =
|
||||
allNodes[connectedNodes[i]].hiddenLabel;
|
||||
allNodes[connectedNodes[i]].hiddenLabel = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// the main node gets its own color and its label back.
|
||||
// allNodes[selectedNode].color = undefined;
|
||||
allNodes[selectedNode].color = nodeColors[selectedNode];
|
||||
if (allNodes[selectedNode].hiddenLabel !== undefined) {
|
||||
allNodes[selectedNode].label = allNodes[selectedNode].hiddenLabel;
|
||||
allNodes[selectedNode].hiddenLabel = undefined;
|
||||
}
|
||||
} else if (highlightActive === true) {
|
||||
// console.log("highlightActive was true");
|
||||
// reset all nodes
|
||||
for (let nodeId in allNodes) {
|
||||
// allNodes[nodeId].color = "purple";
|
||||
allNodes[nodeId].color = nodeColors[nodeId];
|
||||
// delete allNodes[nodeId].color;
|
||||
if (allNodes[nodeId].hiddenLabel !== undefined) {
|
||||
allNodes[nodeId].label = allNodes[nodeId].hiddenLabel;
|
||||
allNodes[nodeId].hiddenLabel = undefined;
|
||||
}
|
||||
}
|
||||
highlightActive = false;
|
||||
}
|
||||
|
||||
// transform the object into an array
|
||||
var updateArray = [];
|
||||
if (params.nodes.length > 0) {
|
||||
for (let nodeId in allNodes) {
|
||||
if (allNodes.hasOwnProperty(nodeId)) {
|
||||
// console.log(allNodes[nodeId]);
|
||||
updateArray.push(allNodes[nodeId]);
|
||||
}
|
||||
}
|
||||
nodes.update(updateArray);
|
||||
} else {
|
||||
// console.log("Nothing was selected");
|
||||
for (let nodeId in allNodes) {
|
||||
if (allNodes.hasOwnProperty(nodeId)) {
|
||||
// console.log(allNodes[nodeId]);
|
||||
// allNodes[nodeId].color = {};
|
||||
updateArray.push(allNodes[nodeId]);
|
||||
}
|
||||
}
|
||||
nodes.update(updateArray);
|
||||
}
|
||||
}
|
||||
|
||||
function filterHighlight(params) {
|
||||
allNodes = nodes.get({ returnType: "Object" });
|
||||
// if something is selected:
|
||||
if (params.nodes.length > 0) {
|
||||
filterActive = true;
|
||||
let selectedNodes = params.nodes;
|
||||
|
||||
// hiding all nodes and saving the label
|
||||
for (let nodeId in allNodes) {
|
||||
allNodes[nodeId].hidden = true;
|
||||
if (allNodes[nodeId].savedLabel === undefined) {
|
||||
allNodes[nodeId].savedLabel = allNodes[nodeId].label;
|
||||
allNodes[nodeId].label = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i=0; i < selectedNodes.length; i++) {
|
||||
allNodes[selectedNodes[i]].hidden = false;
|
||||
if (allNodes[selectedNodes[i]].savedLabel !== undefined) {
|
||||
allNodes[selectedNodes[i]].label = allNodes[selectedNodes[i]].savedLabel;
|
||||
allNodes[selectedNodes[i]].savedLabel = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (filterActive === true) {
|
||||
// reset all nodes
|
||||
for (let nodeId in allNodes) {
|
||||
allNodes[nodeId].hidden = false;
|
||||
if (allNodes[nodeId].savedLabel !== undefined) {
|
||||
allNodes[nodeId].label = allNodes[nodeId].savedLabel;
|
||||
allNodes[nodeId].savedLabel = undefined;
|
||||
}
|
||||
}
|
||||
filterActive = false;
|
||||
}
|
||||
|
||||
// transform the object into an array
|
||||
var updateArray = [];
|
||||
if (params.nodes.length > 0) {
|
||||
for (let nodeId in allNodes) {
|
||||
if (allNodes.hasOwnProperty(nodeId)) {
|
||||
updateArray.push(allNodes[nodeId]);
|
||||
}
|
||||
}
|
||||
nodes.update(updateArray);
|
||||
} else {
|
||||
for (let nodeId in allNodes) {
|
||||
if (allNodes.hasOwnProperty(nodeId)) {
|
||||
updateArray.push(allNodes[nodeId]);
|
||||
}
|
||||
}
|
||||
nodes.update(updateArray);
|
||||
}
|
||||
}
|
||||
|
||||
function selectNode(nodes) {
|
||||
network.selectNodes(nodes);
|
||||
neighbourhoodHighlight({ nodes: nodes });
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function selectNodes(nodes) {
|
||||
network.selectNodes(nodes);
|
||||
filterHighlight({nodes: nodes});
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function highlightFilter(filter) {
|
||||
let selectedNodes = []
|
||||
let selectedProp = filter['property']
|
||||
if (filter['item'] === 'node') {
|
||||
let allNodes = nodes.get({ returnType: "Object" });
|
||||
for (let nodeId in allNodes) {
|
||||
if (allNodes[nodeId][selectedProp] && filter['value'].includes((allNodes[nodeId][selectedProp]).toString())) {
|
||||
selectedNodes.push(nodeId)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (filter['item'] === 'edge'){
|
||||
let allEdges = edges.get({returnType: 'object'});
|
||||
// check if the selected property exists for selected edge and select the nodes connected to the edge
|
||||
for (let edge in allEdges) {
|
||||
if (allEdges[edge][selectedProp] && filter['value'].includes((allEdges[edge][selectedProp]).toString())) {
|
||||
selectedNodes.push(allEdges[edge]['from'])
|
||||
selectedNodes.push(allEdges[edge]['to'])
|
||||
}
|
||||
}
|
||||
}
|
||||
selectNodes(selectedNodes)
|
||||
}
|
||||
356
txt2graph/lib/tom-select/tom-select.complete.min.js
vendored
356
txt2graph/lib/tom-select/tom-select.complete.min.js
vendored
|
|
@ -1,356 +0,0 @@
|
|||
/**
|
||||
* Tom Select v2.0.0-rc.4
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
*/
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).TomSelect=t()}(this,(function(){"use strict"
|
||||
function e(e,t){e.split(/\s+/).forEach((e=>{t(e)}))}class t{constructor(){this._events={}}on(t,i){e(t,(e=>{this._events[e]=this._events[e]||[],this._events[e].push(i)}))}off(t,i){var s=arguments.length
|
||||
0!==s?e(t,(e=>{if(1===s)return delete this._events[e]
|
||||
e in this._events!=!1&&this._events[e].splice(this._events[e].indexOf(i),1)})):this._events={}}trigger(t,...i){var s=this
|
||||
e(t,(e=>{if(e in s._events!=!1)for(let t of s._events[e])t.apply(s,i)}))}}var i
|
||||
const s="[̀-ͯ·ʾ]",n=new RegExp(s,"g")
|
||||
var o
|
||||
const r={"æ":"ae","ⱥ":"a","ø":"o"},l=new RegExp(Object.keys(r).join("|"),"g"),a=[[67,67],[160,160],[192,438],[452,652],[961,961],[1019,1019],[1083,1083],[1281,1289],[1984,1984],[5095,5095],[7429,7441],[7545,7549],[7680,7935],[8580,8580],[9398,9449],[11360,11391],[42792,42793],[42802,42851],[42873,42897],[42912,42922],[64256,64260],[65313,65338],[65345,65370]],c=e=>e.normalize("NFKD").replace(n,"").toLowerCase().replace(l,(function(e){return r[e]})),d=(e,t="|")=>{if(1==e.length)return e[0]
|
||||
var i=1
|
||||
return e.forEach((e=>{i=Math.max(i,e.length)})),1==i?"["+e.join("")+"]":"(?:"+e.join(t)+")"},p=e=>{if(1===e.length)return[[e]]
|
||||
var t=[]
|
||||
return p(e.substring(1)).forEach((function(i){var s=i.slice(0)
|
||||
s[0]=e.charAt(0)+s[0],t.push(s),(s=i.slice(0)).unshift(e.charAt(0)),t.push(s)})),t},u=e=>{void 0===o&&(o=(()=>{var e={}
|
||||
a.forEach((t=>{for(let s=t[0];s<=t[1];s++){let t=String.fromCharCode(s),n=c(t)
|
||||
if(n!=t.toLowerCase()){n in e||(e[n]=[n])
|
||||
var i=new RegExp(d(e[n]),"iu")
|
||||
t.match(i)||e[n].push(t)}}}))
|
||||
var t=Object.keys(e)
|
||||
t=t.sort(((e,t)=>t.length-e.length)),i=new RegExp("("+d(t)+"[̀-ͯ·ʾ]*)","g")
|
||||
var s={}
|
||||
return t.sort(((e,t)=>e.length-t.length)).forEach((t=>{var i=p(t).map((t=>(t=t.map((t=>e.hasOwnProperty(t)?d(e[t]):t)),d(t,""))))
|
||||
s[t]=d(i)})),s})())
|
||||
return e.normalize("NFKD").toLowerCase().split(i).map((e=>{if(""==e)return""
|
||||
const t=c(e)
|
||||
if(o.hasOwnProperty(t))return o[t]
|
||||
const i=e.normalize("NFC")
|
||||
return i!=e?d([e,i]):e})).join("")},h=(e,t)=>{if(e)return e[t]},g=(e,t)=>{if(e){for(var i,s=t.split(".");(i=s.shift())&&(e=e[i]););return e}},f=(e,t,i)=>{var s,n
|
||||
return e?-1===(n=(e+="").search(t.regex))?0:(s=t.string.length/e.length,0===n&&(s+=.5),s*i):0},v=e=>(e+"").replace(/([\$\(-\+\.\?\[-\^\{-\}])/g,"\\$1"),m=(e,t)=>{var i=e[t]
|
||||
if("function"==typeof i)return i
|
||||
i&&!Array.isArray(i)&&(e[t]=[i])},y=(e,t)=>{if(Array.isArray(e))e.forEach(t)
|
||||
else for(var i in e)e.hasOwnProperty(i)&&t(e[i],i)},O=(e,t)=>"number"==typeof e&&"number"==typeof t?e>t?1:e<t?-1:0:(e=c(e+"").toLowerCase())>(t=c(t+"").toLowerCase())?1:t>e?-1:0
|
||||
class b{constructor(e,t){this.items=e,this.settings=t||{diacritics:!0}}tokenize(e,t,i){if(!e||!e.length)return[]
|
||||
const s=[],n=e.split(/\s+/)
|
||||
var o
|
||||
return i&&(o=new RegExp("^("+Object.keys(i).map(v).join("|")+"):(.*)$")),n.forEach((e=>{let i,n=null,r=null
|
||||
o&&(i=e.match(o))&&(n=i[1],e=i[2]),e.length>0&&(r=v(e),this.settings.diacritics&&(r=u(r)),t&&(r="\\b"+r)),s.push({string:e,regex:r?new RegExp(r,"iu"):null,field:n})})),s}getScoreFunction(e,t){var i=this.prepareSearch(e,t)
|
||||
return this._getScoreFunction(i)}_getScoreFunction(e){const t=e.tokens,i=t.length
|
||||
if(!i)return function(){return 0}
|
||||
const s=e.options.fields,n=e.weights,o=s.length,r=e.getAttrFn
|
||||
if(!o)return function(){return 1}
|
||||
const l=1===o?function(e,t){const i=s[0].field
|
||||
return f(r(t,i),e,n[i])}:function(e,t){var i=0
|
||||
if(e.field){const s=r(t,e.field)
|
||||
!e.regex&&s?i+=1/o:i+=f(s,e,1)}else y(n,((s,n)=>{i+=f(r(t,n),e,s)}))
|
||||
return i/o}
|
||||
return 1===i?function(e){return l(t[0],e)}:"and"===e.options.conjunction?function(e){for(var s,n=0,o=0;n<i;n++){if((s=l(t[n],e))<=0)return 0
|
||||
o+=s}return o/i}:function(e){var s=0
|
||||
return y(t,(t=>{s+=l(t,e)})),s/i}}getSortFunction(e,t){var i=this.prepareSearch(e,t)
|
||||
return this._getSortFunction(i)}_getSortFunction(e){var t,i,s
|
||||
const n=this,o=e.options,r=!e.query&&o.sort_empty?o.sort_empty:o.sort,l=[],a=[]
|
||||
if("function"==typeof r)return r.bind(this)
|
||||
const c=function(t,i){return"$score"===t?i.score:e.getAttrFn(n.items[i.id],t)}
|
||||
if(r)for(t=0,i=r.length;t<i;t++)(e.query||"$score"!==r[t].field)&&l.push(r[t])
|
||||
if(e.query){for(s=!0,t=0,i=l.length;t<i;t++)if("$score"===l[t].field){s=!1
|
||||
break}s&&l.unshift({field:"$score",direction:"desc"})}else for(t=0,i=l.length;t<i;t++)if("$score"===l[t].field){l.splice(t,1)
|
||||
break}for(t=0,i=l.length;t<i;t++)a.push("desc"===l[t].direction?-1:1)
|
||||
const d=l.length
|
||||
if(d){if(1===d){const e=l[0].field,t=a[0]
|
||||
return function(i,s){return t*O(c(e,i),c(e,s))}}return function(e,t){var i,s,n
|
||||
for(i=0;i<d;i++)if(n=l[i].field,s=a[i]*O(c(n,e),c(n,t)))return s
|
||||
return 0}}return null}prepareSearch(e,t){const i={}
|
||||
var s=Object.assign({},t)
|
||||
if(m(s,"sort"),m(s,"sort_empty"),s.fields){m(s,"fields")
|
||||
const e=[]
|
||||
s.fields.forEach((t=>{"string"==typeof t&&(t={field:t,weight:1}),e.push(t),i[t.field]="weight"in t?t.weight:1})),s.fields=e}return{options:s,query:e.toLowerCase().trim(),tokens:this.tokenize(e,s.respect_word_boundaries,i),total:0,items:[],weights:i,getAttrFn:s.nesting?g:h}}search(e,t){var i,s,n=this
|
||||
s=this.prepareSearch(e,t),t=s.options,e=s.query
|
||||
const o=t.score||n._getScoreFunction(s)
|
||||
e.length?y(n.items,((e,n)=>{i=o(e),(!1===t.filter||i>0)&&s.items.push({score:i,id:n})})):y(n.items,((e,t)=>{s.items.push({score:1,id:t})}))
|
||||
const r=n._getSortFunction(s)
|
||||
return r&&s.items.sort(r),s.total=s.items.length,"number"==typeof t.limit&&(s.items=s.items.slice(0,t.limit)),s}}const w=e=>{if(e.jquery)return e[0]
|
||||
if(e instanceof HTMLElement)return e
|
||||
if(e.indexOf("<")>-1){let t=document.createElement("div")
|
||||
return t.innerHTML=e.trim(),t.firstChild}return document.querySelector(e)},_=(e,t)=>{var i=document.createEvent("HTMLEvents")
|
||||
i.initEvent(t,!0,!1),e.dispatchEvent(i)},I=(e,t)=>{Object.assign(e.style,t)},C=(e,...t)=>{var i=A(t);(e=x(e)).map((e=>{i.map((t=>{e.classList.add(t)}))}))},S=(e,...t)=>{var i=A(t);(e=x(e)).map((e=>{i.map((t=>{e.classList.remove(t)}))}))},A=e=>{var t=[]
|
||||
return y(e,(e=>{"string"==typeof e&&(e=e.trim().split(/[\11\12\14\15\40]/)),Array.isArray(e)&&(t=t.concat(e))})),t.filter(Boolean)},x=e=>(Array.isArray(e)||(e=[e]),e),k=(e,t,i)=>{if(!i||i.contains(e))for(;e&&e.matches;){if(e.matches(t))return e
|
||||
e=e.parentNode}},F=(e,t=0)=>t>0?e[e.length-1]:e[0],L=(e,t)=>{if(!e)return-1
|
||||
t=t||e.nodeName
|
||||
for(var i=0;e=e.previousElementSibling;)e.matches(t)&&i++
|
||||
return i},P=(e,t)=>{y(t,((t,i)=>{null==t?e.removeAttribute(i):e.setAttribute(i,""+t)}))},E=(e,t)=>{e.parentNode&&e.parentNode.replaceChild(t,e)},T=(e,t)=>{if(null===t)return
|
||||
if("string"==typeof t){if(!t.length)return
|
||||
t=new RegExp(t,"i")}const i=e=>3===e.nodeType?(e=>{var i=e.data.match(t)
|
||||
if(i&&e.data.length>0){var s=document.createElement("span")
|
||||
s.className="highlight"
|
||||
var n=e.splitText(i.index)
|
||||
n.splitText(i[0].length)
|
||||
var o=n.cloneNode(!0)
|
||||
return s.appendChild(o),E(n,s),1}return 0})(e):((e=>{if(1===e.nodeType&&e.childNodes&&!/(script|style)/i.test(e.tagName)&&("highlight"!==e.className||"SPAN"!==e.tagName))for(var t=0;t<e.childNodes.length;++t)t+=i(e.childNodes[t])})(e),0)
|
||||
i(e)},V="undefined"!=typeof navigator&&/Mac/.test(navigator.userAgent)?"metaKey":"ctrlKey"
|
||||
var j={options:[],optgroups:[],plugins:[],delimiter:",",splitOn:null,persist:!0,diacritics:!0,create:null,createOnBlur:!1,createFilter:null,highlight:!0,openOnFocus:!0,shouldOpen:null,maxOptions:50,maxItems:null,hideSelected:null,duplicates:!1,addPrecedence:!1,selectOnTab:!1,preload:null,allowEmptyOption:!1,loadThrottle:300,loadingClass:"loading",dataAttr:null,optgroupField:"optgroup",valueField:"value",labelField:"text",disabledField:"disabled",optgroupLabelField:"label",optgroupValueField:"value",lockOptgroupOrder:!1,sortField:"$order",searchField:["text"],searchConjunction:"and",mode:null,wrapperClass:"ts-wrapper",controlClass:"ts-control",dropdownClass:"ts-dropdown",dropdownContentClass:"ts-dropdown-content",itemClass:"item",optionClass:"option",dropdownParent:null,copyClassesToDropdown:!1,placeholder:null,hidePlaceholder:null,shouldLoad:function(e){return e.length>0},render:{}}
|
||||
const q=e=>null==e?null:D(e),D=e=>"boolean"==typeof e?e?"1":"0":e+"",N=e=>(e+"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""),z=(e,t)=>{var i
|
||||
return function(s,n){var o=this
|
||||
i&&(o.loading=Math.max(o.loading-1,0),clearTimeout(i)),i=setTimeout((function(){i=null,o.loadedSearches[s]=!0,e.call(o,s,n)}),t)}},R=(e,t,i)=>{var s,n=e.trigger,o={}
|
||||
for(s in e.trigger=function(){var i=arguments[0]
|
||||
if(-1===t.indexOf(i))return n.apply(e,arguments)
|
||||
o[i]=arguments},i.apply(e,[]),e.trigger=n,o)n.apply(e,o[s])},H=(e,t=!1)=>{e&&(e.preventDefault(),t&&e.stopPropagation())},B=(e,t,i,s)=>{e.addEventListener(t,i,s)},K=(e,t)=>!!t&&(!!t[e]&&1===(t.altKey?1:0)+(t.ctrlKey?1:0)+(t.shiftKey?1:0)+(t.metaKey?1:0)),M=(e,t)=>{const i=e.getAttribute("id")
|
||||
return i||(e.setAttribute("id",t),t)},Q=e=>e.replace(/[\\"']/g,"\\$&"),G=(e,t)=>{t&&e.append(t)}
|
||||
function U(e,t){var i=Object.assign({},j,t),s=i.dataAttr,n=i.labelField,o=i.valueField,r=i.disabledField,l=i.optgroupField,a=i.optgroupLabelField,c=i.optgroupValueField,d=e.tagName.toLowerCase(),p=e.getAttribute("placeholder")||e.getAttribute("data-placeholder")
|
||||
if(!p&&!i.allowEmptyOption){let t=e.querySelector('option[value=""]')
|
||||
t&&(p=t.textContent)}var u,h,g,f,v,m,O={placeholder:p,options:[],optgroups:[],items:[],maxItems:null}
|
||||
return"select"===d?(h=O.options,g={},f=1,v=e=>{var t=Object.assign({},e.dataset),i=s&&t[s]
|
||||
return"string"==typeof i&&i.length&&(t=Object.assign(t,JSON.parse(i))),t},m=(e,t)=>{var s=q(e.value)
|
||||
if(null!=s&&(s||i.allowEmptyOption)){if(g.hasOwnProperty(s)){if(t){var a=g[s][l]
|
||||
a?Array.isArray(a)?a.push(t):g[s][l]=[a,t]:g[s][l]=t}}else{var c=v(e)
|
||||
c[n]=c[n]||e.textContent,c[o]=c[o]||s,c[r]=c[r]||e.disabled,c[l]=c[l]||t,c.$option=e,g[s]=c,h.push(c)}e.selected&&O.items.push(s)}},O.maxItems=e.hasAttribute("multiple")?null:1,y(e.children,(e=>{var t,i,s
|
||||
"optgroup"===(u=e.tagName.toLowerCase())?((s=v(t=e))[a]=s[a]||t.getAttribute("label")||"",s[c]=s[c]||f++,s[r]=s[r]||t.disabled,O.optgroups.push(s),i=s[c],y(t.children,(e=>{m(e,i)}))):"option"===u&&m(e)}))):(()=>{const t=e.getAttribute(s)
|
||||
if(t)O.options=JSON.parse(t),y(O.options,(e=>{O.items.push(e[o])}))
|
||||
else{var r=e.value.trim()||""
|
||||
if(!i.allowEmptyOption&&!r.length)return
|
||||
const t=r.split(i.delimiter)
|
||||
y(t,(e=>{const t={}
|
||||
t[n]=e,t[o]=e,O.options.push(t)})),O.items=t}})(),Object.assign({},j,O,t)}var W=0
|
||||
class J extends(function(e){return e.plugins={},class extends e{constructor(...e){super(...e),this.plugins={names:[],settings:{},requested:{},loaded:{}}}static define(t,i){e.plugins[t]={name:t,fn:i}}initializePlugins(e){var t,i
|
||||
const s=this,n=[]
|
||||
if(Array.isArray(e))e.forEach((e=>{"string"==typeof e?n.push(e):(s.plugins.settings[e.name]=e.options,n.push(e.name))}))
|
||||
else if(e)for(t in e)e.hasOwnProperty(t)&&(s.plugins.settings[t]=e[t],n.push(t))
|
||||
for(;i=n.shift();)s.require(i)}loadPlugin(t){var i=this,s=i.plugins,n=e.plugins[t]
|
||||
if(!e.plugins.hasOwnProperty(t))throw new Error('Unable to find "'+t+'" plugin')
|
||||
s.requested[t]=!0,s.loaded[t]=n.fn.apply(i,[i.plugins.settings[t]||{}]),s.names.push(t)}require(e){var t=this,i=t.plugins
|
||||
if(!t.plugins.loaded.hasOwnProperty(e)){if(i.requested[e])throw new Error('Plugin has circular dependency ("'+e+'")')
|
||||
t.loadPlugin(e)}return i.loaded[e]}}}(t)){constructor(e,t){var i
|
||||
super(),this.order=0,this.isOpen=!1,this.isDisabled=!1,this.isInvalid=!1,this.isValid=!0,this.isLocked=!1,this.isFocused=!1,this.isInputHidden=!1,this.isSetup=!1,this.ignoreFocus=!1,this.hasOptions=!1,this.lastValue="",this.caretPos=0,this.loading=0,this.loadedSearches={},this.activeOption=null,this.activeItems=[],this.optgroups={},this.options={},this.userOptions={},this.items=[],W++
|
||||
var s=w(e)
|
||||
if(s.tomselect)throw new Error("Tom Select already initialized on this element")
|
||||
s.tomselect=this,i=(window.getComputedStyle&&window.getComputedStyle(s,null)).getPropertyValue("direction")
|
||||
const n=U(s,t)
|
||||
this.settings=n,this.input=s,this.tabIndex=s.tabIndex||0,this.is_select_tag="select"===s.tagName.toLowerCase(),this.rtl=/rtl/i.test(i),this.inputId=M(s,"tomselect-"+W),this.isRequired=s.required,this.sifter=new b(this.options,{diacritics:n.diacritics}),n.mode=n.mode||(1===n.maxItems?"single":"multi"),"boolean"!=typeof n.hideSelected&&(n.hideSelected="multi"===n.mode),"boolean"!=typeof n.hidePlaceholder&&(n.hidePlaceholder="multi"!==n.mode)
|
||||
var o=n.createFilter
|
||||
"function"!=typeof o&&("string"==typeof o&&(o=new RegExp(o)),o instanceof RegExp?n.createFilter=e=>o.test(e):n.createFilter=()=>!0),this.initializePlugins(n.plugins),this.setupCallbacks(),this.setupTemplates()
|
||||
const r=w("<div>"),l=w("<div>"),a=this._render("dropdown"),c=w('<div role="listbox" tabindex="-1">'),d=this.input.getAttribute("class")||"",p=n.mode
|
||||
var u
|
||||
if(C(r,n.wrapperClass,d,p),C(l,n.controlClass),G(r,l),C(a,n.dropdownClass,p),n.copyClassesToDropdown&&C(a,d),C(c,n.dropdownContentClass),G(a,c),w(n.dropdownParent||r).appendChild(a),n.hasOwnProperty("controlInput"))n.controlInput?(u=w(n.controlInput),this.focus_node=u):(u=w("<input/>"),this.focus_node=l)
|
||||
else{u=w('<input type="text" autocomplete="off" size="1" />')
|
||||
y(["autocorrect","autocapitalize","autocomplete"],(e=>{s.getAttribute(e)&&P(u,{[e]:s.getAttribute(e)})})),u.tabIndex=-1,l.appendChild(u),this.focus_node=u}this.wrapper=r,this.dropdown=a,this.dropdown_content=c,this.control=l,this.control_input=u,this.setup()}setup(){const e=this,t=e.settings,i=e.control_input,s=e.dropdown,n=e.dropdown_content,o=e.wrapper,r=e.control,l=e.input,a=e.focus_node,c={passive:!0},d=e.inputId+"-ts-dropdown"
|
||||
P(n,{id:d}),P(a,{role:"combobox","aria-haspopup":"listbox","aria-expanded":"false","aria-controls":d})
|
||||
const p=M(a,e.inputId+"-ts-control"),u="label[for='"+(e=>e.replace(/['"\\]/g,"\\$&"))(e.inputId)+"']",h=document.querySelector(u),g=e.focus.bind(e)
|
||||
if(h){B(h,"click",g),P(h,{for:p})
|
||||
const t=M(h,e.inputId+"-ts-label")
|
||||
P(a,{"aria-labelledby":t}),P(n,{"aria-labelledby":t})}if(o.style.width=l.style.width,e.plugins.names.length){const t="plugin-"+e.plugins.names.join(" plugin-")
|
||||
C([o,s],t)}(null===t.maxItems||t.maxItems>1)&&e.is_select_tag&&P(l,{multiple:"multiple"}),e.settings.placeholder&&P(i,{placeholder:t.placeholder}),!e.settings.splitOn&&e.settings.delimiter&&(e.settings.splitOn=new RegExp("\\s*"+v(e.settings.delimiter)+"+\\s*")),t.load&&t.loadThrottle&&(t.load=z(t.load,t.loadThrottle)),e.control_input.type=l.type,B(s,"click",(t=>{const i=k(t.target,"[data-selectable]")
|
||||
i&&(e.onOptionSelect(t,i),H(t,!0))})),B(r,"click",(t=>{var s=k(t.target,"[data-ts-item]",r)
|
||||
s&&e.onItemSelect(t,s)?H(t,!0):""==i.value&&(e.onClick(),H(t,!0))})),B(i,"mousedown",(e=>{""!==i.value&&e.stopPropagation()})),B(a,"keydown",(t=>e.onKeyDown(t))),B(i,"keypress",(t=>e.onKeyPress(t))),B(i,"input",(t=>e.onInput(t))),B(a,"resize",(()=>e.positionDropdown()),c),B(a,"blur",(t=>e.onBlur(t))),B(a,"focus",(t=>e.onFocus(t))),B(a,"paste",(t=>e.onPaste(t)))
|
||||
const f=t=>{const i=t.composedPath()[0]
|
||||
if(!o.contains(i)&&!s.contains(i))return e.isFocused&&e.blur(),void e.inputState()
|
||||
H(t,!0)}
|
||||
var m=()=>{e.isOpen&&e.positionDropdown()}
|
||||
B(document,"mousedown",f),B(window,"scroll",m,c),B(window,"resize",m,c),this._destroy=()=>{document.removeEventListener("mousedown",f),window.removeEventListener("sroll",m),window.removeEventListener("resize",m),h&&h.removeEventListener("click",g)},this.revertSettings={innerHTML:l.innerHTML,tabIndex:l.tabIndex},l.tabIndex=-1,l.insertAdjacentElement("afterend",e.wrapper),e.sync(!1),t.items=[],delete t.optgroups,delete t.options,B(l,"invalid",(t=>{e.isValid&&(e.isValid=!1,e.isInvalid=!0,e.refreshState())})),e.updateOriginalInput(),e.refreshItems(),e.close(!1),e.inputState(),e.isSetup=!0,l.disabled?e.disable():e.enable(),e.on("change",this.onChange),C(l,"tomselected","ts-hidden-accessible"),e.trigger("initialize"),!0===t.preload&&e.preload()}setupOptions(e=[],t=[]){this.addOptions(e),y(t,(e=>{this.registerOptionGroup(e)}))}setupTemplates(){var e=this,t=e.settings.labelField,i=e.settings.optgroupLabelField,s={optgroup:e=>{let t=document.createElement("div")
|
||||
return t.className="optgroup",t.appendChild(e.options),t},optgroup_header:(e,t)=>'<div class="optgroup-header">'+t(e[i])+"</div>",option:(e,i)=>"<div>"+i(e[t])+"</div>",item:(e,i)=>"<div>"+i(e[t])+"</div>",option_create:(e,t)=>'<div class="create">Add <strong>'+t(e.input)+"</strong>…</div>",no_results:()=>'<div class="no-results">No results found</div>',loading:()=>'<div class="spinner"></div>',not_loading:()=>{},dropdown:()=>"<div></div>"}
|
||||
e.settings.render=Object.assign({},s,e.settings.render)}setupCallbacks(){var e,t,i={initialize:"onInitialize",change:"onChange",item_add:"onItemAdd",item_remove:"onItemRemove",item_select:"onItemSelect",clear:"onClear",option_add:"onOptionAdd",option_remove:"onOptionRemove",option_clear:"onOptionClear",optgroup_add:"onOptionGroupAdd",optgroup_remove:"onOptionGroupRemove",optgroup_clear:"onOptionGroupClear",dropdown_open:"onDropdownOpen",dropdown_close:"onDropdownClose",type:"onType",load:"onLoad",focus:"onFocus",blur:"onBlur"}
|
||||
for(e in i)(t=this.settings[i[e]])&&this.on(e,t)}sync(e=!0){const t=this,i=e?U(t.input,{delimiter:t.settings.delimiter}):t.settings
|
||||
t.setupOptions(i.options,i.optgroups),t.setValue(i.items,!0),t.lastQuery=null}onClick(){var e=this
|
||||
if(e.activeItems.length>0)return e.clearActiveItems(),void e.focus()
|
||||
e.isFocused&&e.isOpen?e.blur():e.focus()}onMouseDown(){}onChange(){_(this.input,"input"),_(this.input,"change")}onPaste(e){var t=this
|
||||
t.isFull()||t.isInputHidden||t.isLocked?H(e):t.settings.splitOn&&setTimeout((()=>{var e=t.inputValue()
|
||||
if(e.match(t.settings.splitOn)){var i=e.trim().split(t.settings.splitOn)
|
||||
y(i,(e=>{t.createItem(e)}))}}),0)}onKeyPress(e){var t=this
|
||||
if(!t.isLocked){var i=String.fromCharCode(e.keyCode||e.which)
|
||||
return t.settings.create&&"multi"===t.settings.mode&&i===t.settings.delimiter?(t.createItem(),void H(e)):void 0}H(e)}onKeyDown(e){var t=this
|
||||
if(t.isLocked)9!==e.keyCode&&H(e)
|
||||
else{switch(e.keyCode){case 65:if(K(V,e))return H(e),void t.selectAll()
|
||||
break
|
||||
case 27:return t.isOpen&&(H(e,!0),t.close()),void t.clearActiveItems()
|
||||
case 40:if(!t.isOpen&&t.hasOptions)t.open()
|
||||
else if(t.activeOption){let e=t.getAdjacent(t.activeOption,1)
|
||||
e&&t.setActiveOption(e)}return void H(e)
|
||||
case 38:if(t.activeOption){let e=t.getAdjacent(t.activeOption,-1)
|
||||
e&&t.setActiveOption(e)}return void H(e)
|
||||
case 13:return void(t.isOpen&&t.activeOption?(t.onOptionSelect(e,t.activeOption),H(e)):t.settings.create&&t.createItem()&&H(e))
|
||||
case 37:return void t.advanceSelection(-1,e)
|
||||
case 39:return void t.advanceSelection(1,e)
|
||||
case 9:return void(t.settings.selectOnTab&&(t.isOpen&&t.activeOption&&(t.onOptionSelect(e,t.activeOption),H(e)),t.settings.create&&t.createItem()&&H(e)))
|
||||
case 8:case 46:return void t.deleteSelection(e)}t.isInputHidden&&!K(V,e)&&H(e)}}onInput(e){var t=this
|
||||
if(!t.isLocked){var i=t.inputValue()
|
||||
t.lastValue!==i&&(t.lastValue=i,t.settings.shouldLoad.call(t,i)&&t.load(i),t.refreshOptions(),t.trigger("type",i))}}onFocus(e){var t=this,i=t.isFocused
|
||||
if(t.isDisabled)return t.blur(),void H(e)
|
||||
t.ignoreFocus||(t.isFocused=!0,"focus"===t.settings.preload&&t.preload(),i||t.trigger("focus"),t.activeItems.length||(t.showInput(),t.refreshOptions(!!t.settings.openOnFocus)),t.refreshState())}onBlur(e){if(!1!==document.hasFocus()){var t=this
|
||||
if(t.isFocused){t.isFocused=!1,t.ignoreFocus=!1
|
||||
var i=()=>{t.close(),t.setActiveItem(),t.setCaret(t.items.length),t.trigger("blur")}
|
||||
t.settings.create&&t.settings.createOnBlur?t.createItem(null,!1,i):i()}}}onOptionSelect(e,t){var i,s=this
|
||||
t&&(t.parentElement&&t.parentElement.matches("[data-disabled]")||(t.classList.contains("create")?s.createItem(null,!0,(()=>{s.settings.closeAfterSelect&&s.close()})):void 0!==(i=t.dataset.value)&&(s.lastQuery=null,s.addItem(i),s.settings.closeAfterSelect&&s.close(),!s.settings.hideSelected&&e.type&&/click/.test(e.type)&&s.setActiveOption(t))))}onItemSelect(e,t){var i=this
|
||||
return!i.isLocked&&"multi"===i.settings.mode&&(H(e),i.setActiveItem(t,e),!0)}canLoad(e){return!!this.settings.load&&!this.loadedSearches.hasOwnProperty(e)}load(e){const t=this
|
||||
if(!t.canLoad(e))return
|
||||
C(t.wrapper,t.settings.loadingClass),t.loading++
|
||||
const i=t.loadCallback.bind(t)
|
||||
t.settings.load.call(t,e,i)}loadCallback(e,t){const i=this
|
||||
i.loading=Math.max(i.loading-1,0),i.lastQuery=null,i.clearActiveOption(),i.setupOptions(e,t),i.refreshOptions(i.isFocused&&!i.isInputHidden),i.loading||S(i.wrapper,i.settings.loadingClass),i.trigger("load",e,t)}preload(){var e=this.wrapper.classList
|
||||
e.contains("preloaded")||(e.add("preloaded"),this.load(""))}setTextboxValue(e=""){var t=this.control_input
|
||||
t.value!==e&&(t.value=e,_(t,"update"),this.lastValue=e)}getValue(){return this.is_select_tag&&this.input.hasAttribute("multiple")?this.items:this.items.join(this.settings.delimiter)}setValue(e,t){R(this,t?[]:["change"],(()=>{this.clear(t),this.addItems(e,t)}))}setMaxItems(e){0===e&&(e=null),this.settings.maxItems=e,this.refreshState()}setActiveItem(e,t){var i,s,n,o,r,l,a=this
|
||||
if("single"!==a.settings.mode){if(!e)return a.clearActiveItems(),void(a.isFocused&&a.showInput())
|
||||
if("click"===(i=t&&t.type.toLowerCase())&&K("shiftKey",t)&&a.activeItems.length){for(l=a.getLastActive(),(n=Array.prototype.indexOf.call(a.control.children,l))>(o=Array.prototype.indexOf.call(a.control.children,e))&&(r=n,n=o,o=r),s=n;s<=o;s++)e=a.control.children[s],-1===a.activeItems.indexOf(e)&&a.setActiveItemClass(e)
|
||||
H(t)}else"click"===i&&K(V,t)||"keydown"===i&&K("shiftKey",t)?e.classList.contains("active")?a.removeActiveItem(e):a.setActiveItemClass(e):(a.clearActiveItems(),a.setActiveItemClass(e))
|
||||
a.hideInput(),a.isFocused||a.focus()}}setActiveItemClass(e){const t=this,i=t.control.querySelector(".last-active")
|
||||
i&&S(i,"last-active"),C(e,"active last-active"),t.trigger("item_select",e),-1==t.activeItems.indexOf(e)&&t.activeItems.push(e)}removeActiveItem(e){var t=this.activeItems.indexOf(e)
|
||||
this.activeItems.splice(t,1),S(e,"active")}clearActiveItems(){S(this.activeItems,"active"),this.activeItems=[]}setActiveOption(e){e!==this.activeOption&&(this.clearActiveOption(),e&&(this.activeOption=e,P(this.focus_node,{"aria-activedescendant":e.getAttribute("id")}),P(e,{"aria-selected":"true"}),C(e,"active"),this.scrollToOption(e)))}scrollToOption(e,t){if(!e)return
|
||||
const i=this.dropdown_content,s=i.clientHeight,n=i.scrollTop||0,o=e.offsetHeight,r=e.getBoundingClientRect().top-i.getBoundingClientRect().top+n
|
||||
r+o>s+n?this.scroll(r-s+o,t):r<n&&this.scroll(r,t)}scroll(e,t){const i=this.dropdown_content
|
||||
t&&(i.style.scrollBehavior=t),i.scrollTop=e,i.style.scrollBehavior=""}clearActiveOption(){this.activeOption&&(S(this.activeOption,"active"),P(this.activeOption,{"aria-selected":null})),this.activeOption=null,P(this.focus_node,{"aria-activedescendant":null})}selectAll(){if("single"===this.settings.mode)return
|
||||
const e=this.controlChildren()
|
||||
e.length&&(this.hideInput(),this.close(),this.activeItems=e,C(e,"active"))}inputState(){var e=this
|
||||
e.control.contains(e.control_input)&&(P(e.control_input,{placeholder:e.settings.placeholder}),e.activeItems.length>0||!e.isFocused&&e.settings.hidePlaceholder&&e.items.length>0?(e.setTextboxValue(),e.isInputHidden=!0):(e.settings.hidePlaceholder&&e.items.length>0&&P(e.control_input,{placeholder:""}),e.isInputHidden=!1),e.wrapper.classList.toggle("input-hidden",e.isInputHidden))}hideInput(){this.inputState()}showInput(){this.inputState()}inputValue(){return this.control_input.value.trim()}focus(){var e=this
|
||||
e.isDisabled||(e.ignoreFocus=!0,e.control_input.offsetWidth?e.control_input.focus():e.focus_node.focus(),setTimeout((()=>{e.ignoreFocus=!1,e.onFocus()}),0))}blur(){this.focus_node.blur(),this.onBlur()}getScoreFunction(e){return this.sifter.getScoreFunction(e,this.getSearchOptions())}getSearchOptions(){var e=this.settings,t=e.sortField
|
||||
return"string"==typeof e.sortField&&(t=[{field:e.sortField}]),{fields:e.searchField,conjunction:e.searchConjunction,sort:t,nesting:e.nesting}}search(e){var t,i,s,n=this,o=this.getSearchOptions()
|
||||
if(n.settings.score&&"function"!=typeof(s=n.settings.score.call(n,e)))throw new Error('Tom Select "score" setting must be a function that returns a function')
|
||||
if(e!==n.lastQuery?(n.lastQuery=e,i=n.sifter.search(e,Object.assign(o,{score:s})),n.currentResults=i):i=Object.assign({},n.currentResults),n.settings.hideSelected)for(t=i.items.length-1;t>=0;t--){let e=q(i.items[t].id)
|
||||
e&&-1!==n.items.indexOf(e)&&i.items.splice(t,1)}return i}refreshOptions(e=!0){var t,i,s,n,o,r,l,a,c,d,p
|
||||
const u={},h=[]
|
||||
var g,f=this,v=f.inputValue(),m=f.search(v),O=f.activeOption,b=f.settings.shouldOpen||!1,w=f.dropdown_content
|
||||
for(O&&(c=O.dataset.value,d=O.closest("[data-group]")),n=m.items.length,"number"==typeof f.settings.maxOptions&&(n=Math.min(n,f.settings.maxOptions)),n>0&&(b=!0),t=0;t<n;t++){let e=m.items[t].id,n=f.options[e],l=f.getOption(e,!0)
|
||||
for(f.settings.hideSelected||l.classList.toggle("selected",f.items.includes(e)),o=n[f.settings.optgroupField]||"",i=0,s=(r=Array.isArray(o)?o:[o])&&r.length;i<s;i++)o=r[i],f.optgroups.hasOwnProperty(o)||(o=""),u.hasOwnProperty(o)||(u[o]=document.createDocumentFragment(),h.push(o)),i>0&&(l=l.cloneNode(!0),P(l,{id:n.$id+"-clone-"+i,"aria-selected":null}),l.classList.add("ts-cloned"),S(l,"active")),c==e&&d&&d.dataset.group===o&&(O=l),u[o].appendChild(l)}this.settings.lockOptgroupOrder&&h.sort(((e,t)=>(f.optgroups[e]&&f.optgroups[e].$order||0)-(f.optgroups[t]&&f.optgroups[t].$order||0))),l=document.createDocumentFragment(),y(h,(e=>{if(f.optgroups.hasOwnProperty(e)&&u[e].children.length){let t=document.createDocumentFragment(),i=f.render("optgroup_header",f.optgroups[e])
|
||||
G(t,i),G(t,u[e])
|
||||
let s=f.render("optgroup",{group:f.optgroups[e],options:t})
|
||||
G(l,s)}else G(l,u[e])})),w.innerHTML="",G(w,l),f.settings.highlight&&(g=w.querySelectorAll("span.highlight"),Array.prototype.forEach.call(g,(function(e){var t=e.parentNode
|
||||
t.replaceChild(e.firstChild,e),t.normalize()})),m.query.length&&m.tokens.length&&y(m.tokens,(e=>{T(w,e.regex)})))
|
||||
var _=e=>{let t=f.render(e,{input:v})
|
||||
return t&&(b=!0,w.insertBefore(t,w.firstChild)),t}
|
||||
if(f.loading?_("loading"):f.settings.shouldLoad.call(f,v)?0===m.items.length&&_("no_results"):_("not_loading"),(a=f.canCreate(v))&&(p=_("option_create")),f.hasOptions=m.items.length>0||a,b){if(m.items.length>0){if(!w.contains(O)&&"single"===f.settings.mode&&f.items.length&&(O=f.getOption(f.items[0])),!w.contains(O)){let e=0
|
||||
p&&!f.settings.addPrecedence&&(e=1),O=f.selectable()[e]}}else p&&(O=p)
|
||||
e&&!f.isOpen&&(f.open(),f.scrollToOption(O,"auto")),f.setActiveOption(O)}else f.clearActiveOption(),e&&f.isOpen&&f.close(!1)}selectable(){return this.dropdown_content.querySelectorAll("[data-selectable]")}addOption(e,t=!1){const i=this
|
||||
if(Array.isArray(e))return i.addOptions(e,t),!1
|
||||
const s=q(e[i.settings.valueField])
|
||||
return null!==s&&!i.options.hasOwnProperty(s)&&(e.$order=e.$order||++i.order,e.$id=i.inputId+"-opt-"+e.$order,i.options[s]=e,i.lastQuery=null,t&&(i.userOptions[s]=t,i.trigger("option_add",s,e)),s)}addOptions(e,t=!1){y(e,(e=>{this.addOption(e,t)}))}registerOption(e){return this.addOption(e)}registerOptionGroup(e){var t=q(e[this.settings.optgroupValueField])
|
||||
return null!==t&&(e.$order=e.$order||++this.order,this.optgroups[t]=e,t)}addOptionGroup(e,t){var i
|
||||
t[this.settings.optgroupValueField]=e,(i=this.registerOptionGroup(t))&&this.trigger("optgroup_add",i,t)}removeOptionGroup(e){this.optgroups.hasOwnProperty(e)&&(delete this.optgroups[e],this.clearCache(),this.trigger("optgroup_remove",e))}clearOptionGroups(){this.optgroups={},this.clearCache(),this.trigger("optgroup_clear")}updateOption(e,t){const i=this
|
||||
var s,n
|
||||
const o=q(e),r=q(t[i.settings.valueField])
|
||||
if(null===o)return
|
||||
if(!i.options.hasOwnProperty(o))return
|
||||
if("string"!=typeof r)throw new Error("Value must be set in option data")
|
||||
const l=i.getOption(o),a=i.getItem(o)
|
||||
if(t.$order=t.$order||i.options[o].$order,delete i.options[o],i.uncacheValue(r),i.options[r]=t,l){if(i.dropdown_content.contains(l)){const e=i._render("option",t)
|
||||
E(l,e),i.activeOption===l&&i.setActiveOption(e)}l.remove()}a&&(-1!==(n=i.items.indexOf(o))&&i.items.splice(n,1,r),s=i._render("item",t),a.classList.contains("active")&&C(s,"active"),E(a,s)),i.lastQuery=null}removeOption(e,t){const i=this
|
||||
e=D(e),i.uncacheValue(e),delete i.userOptions[e],delete i.options[e],i.lastQuery=null,i.trigger("option_remove",e),i.removeItem(e,t)}clearOptions(){this.loadedSearches={},this.userOptions={},this.clearCache()
|
||||
var e={}
|
||||
y(this.options,((t,i)=>{this.items.indexOf(i)>=0&&(e[i]=this.options[i])})),this.options=this.sifter.items=e,this.lastQuery=null,this.trigger("option_clear")}getOption(e,t=!1){const i=q(e)
|
||||
if(null!==i&&this.options.hasOwnProperty(i)){const e=this.options[i]
|
||||
if(e.$div)return e.$div
|
||||
if(t)return this._render("option",e)}return null}getAdjacent(e,t,i="option"){var s
|
||||
if(!e)return null
|
||||
s="item"==i?this.controlChildren():this.dropdown_content.querySelectorAll("[data-selectable]")
|
||||
for(let i=0;i<s.length;i++)if(s[i]==e)return t>0?s[i+1]:s[i-1]
|
||||
return null}getItem(e){if("object"==typeof e)return e
|
||||
var t=q(e)
|
||||
return null!==t?this.control.querySelector(`[data-value="${Q(t)}"]`):null}addItems(e,t){var i=this,s=Array.isArray(e)?e:[e]
|
||||
for(let e=0,n=(s=s.filter((e=>-1===i.items.indexOf(e)))).length;e<n;e++)i.isPending=e<n-1,i.addItem(s[e],t)}addItem(e,t){R(this,t?[]:["change"],(()=>{var i,s
|
||||
const n=this,o=n.settings.mode,r=q(e)
|
||||
if((!r||-1===n.items.indexOf(r)||("single"===o&&n.close(),"single"!==o&&n.settings.duplicates))&&null!==r&&n.options.hasOwnProperty(r)&&("single"===o&&n.clear(t),"multi"!==o||!n.isFull())){if(i=n._render("item",n.options[r]),n.control.contains(i)&&(i=i.cloneNode(!0)),s=n.isFull(),n.items.splice(n.caretPos,0,r),n.insertAtCaret(i),n.isSetup){if(!n.isPending&&n.settings.hideSelected){let e=n.getOption(r),t=n.getAdjacent(e,1)
|
||||
t&&n.setActiveOption(t)}n.isPending||n.refreshOptions(n.isFocused&&"single"!==o),0!=n.settings.closeAfterSelect&&n.isFull()?n.close():n.isPending||n.positionDropdown(),n.trigger("item_add",r,i),n.isPending||n.updateOriginalInput({silent:t})}(!n.isPending||!s&&n.isFull())&&(n.inputState(),n.refreshState())}}))}removeItem(e=null,t){const i=this
|
||||
if(!(e=i.getItem(e)))return
|
||||
var s,n
|
||||
const o=e.dataset.value
|
||||
s=L(e),e.remove(),e.classList.contains("active")&&(n=i.activeItems.indexOf(e),i.activeItems.splice(n,1),S(e,"active")),i.items.splice(s,1),i.lastQuery=null,!i.settings.persist&&i.userOptions.hasOwnProperty(o)&&i.removeOption(o,t),s<i.caretPos&&i.setCaret(i.caretPos-1),i.updateOriginalInput({silent:t}),i.refreshState(),i.positionDropdown(),i.trigger("item_remove",o,e)}createItem(e=null,t=!0,i=(()=>{})){var s,n=this,o=n.caretPos
|
||||
if(e=e||n.inputValue(),!n.canCreate(e))return i(),!1
|
||||
n.lock()
|
||||
var r=!1,l=e=>{if(n.unlock(),!e||"object"!=typeof e)return i()
|
||||
var s=q(e[n.settings.valueField])
|
||||
if("string"!=typeof s)return i()
|
||||
n.setTextboxValue(),n.addOption(e,!0),n.setCaret(o),n.addItem(s),n.refreshOptions(t&&"single"!==n.settings.mode),i(e),r=!0}
|
||||
return s="function"==typeof n.settings.create?n.settings.create.call(this,e,l):{[n.settings.labelField]:e,[n.settings.valueField]:e},r||l(s),!0}refreshItems(){var e=this
|
||||
e.lastQuery=null,e.isSetup&&e.addItems(e.items),e.updateOriginalInput(),e.refreshState()}refreshState(){const e=this
|
||||
e.refreshValidityState()
|
||||
const t=e.isFull(),i=e.isLocked
|
||||
e.wrapper.classList.toggle("rtl",e.rtl)
|
||||
const s=e.wrapper.classList
|
||||
var n
|
||||
s.toggle("focus",e.isFocused),s.toggle("disabled",e.isDisabled),s.toggle("required",e.isRequired),s.toggle("invalid",!e.isValid),s.toggle("locked",i),s.toggle("full",t),s.toggle("input-active",e.isFocused&&!e.isInputHidden),s.toggle("dropdown-active",e.isOpen),s.toggle("has-options",(n=e.options,0===Object.keys(n).length)),s.toggle("has-items",e.items.length>0)}refreshValidityState(){var e=this
|
||||
e.input.checkValidity&&(e.isValid=e.input.checkValidity(),e.isInvalid=!e.isValid)}isFull(){return null!==this.settings.maxItems&&this.items.length>=this.settings.maxItems}updateOriginalInput(e={}){const t=this
|
||||
var i,s
|
||||
const n=t.input.querySelector('option[value=""]')
|
||||
if(t.is_select_tag){const e=[]
|
||||
function o(i,s,o){return i||(i=w('<option value="'+N(s)+'">'+N(o)+"</option>")),i!=n&&t.input.append(i),e.push(i),i.selected=!0,i}t.input.querySelectorAll("option:checked").forEach((e=>{e.selected=!1})),0==t.items.length&&"single"==t.settings.mode?o(n,"",""):t.items.forEach((n=>{if(i=t.options[n],s=i[t.settings.labelField]||"",e.includes(i.$option)){o(t.input.querySelector(`option[value="${Q(n)}"]:not(:checked)`),n,s)}else i.$option=o(i.$option,n,s)}))}else t.input.value=t.getValue()
|
||||
t.isSetup&&(e.silent||t.trigger("change",t.getValue()))}open(){var e=this
|
||||
e.isLocked||e.isOpen||"multi"===e.settings.mode&&e.isFull()||(e.isOpen=!0,P(e.focus_node,{"aria-expanded":"true"}),e.refreshState(),I(e.dropdown,{visibility:"hidden",display:"block"}),e.positionDropdown(),I(e.dropdown,{visibility:"visible",display:"block"}),e.focus(),e.trigger("dropdown_open",e.dropdown))}close(e=!0){var t=this,i=t.isOpen
|
||||
e&&(t.setTextboxValue(),"single"===t.settings.mode&&t.items.length&&t.hideInput()),t.isOpen=!1,P(t.focus_node,{"aria-expanded":"false"}),I(t.dropdown,{display:"none"}),t.settings.hideSelected&&t.clearActiveOption(),t.refreshState(),i&&t.trigger("dropdown_close",t.dropdown)}positionDropdown(){if("body"===this.settings.dropdownParent){var e=this.control,t=e.getBoundingClientRect(),i=e.offsetHeight+t.top+window.scrollY,s=t.left+window.scrollX
|
||||
I(this.dropdown,{width:t.width+"px",top:i+"px",left:s+"px"})}}clear(e){var t=this
|
||||
if(t.items.length){var i=t.controlChildren()
|
||||
y(i,(e=>{t.removeItem(e,!0)})),t.showInput(),e||t.updateOriginalInput(),t.trigger("clear")}}insertAtCaret(e){const t=this,i=t.caretPos,s=t.control
|
||||
s.insertBefore(e,s.children[i]),t.setCaret(i+1)}deleteSelection(e){var t,i,s,n,o,r=this
|
||||
t=e&&8===e.keyCode?-1:1,i={start:(o=r.control_input).selectionStart||0,length:(o.selectionEnd||0)-(o.selectionStart||0)}
|
||||
const l=[]
|
||||
if(r.activeItems.length)n=F(r.activeItems,t),s=L(n),t>0&&s++,y(r.activeItems,(e=>l.push(e)))
|
||||
else if((r.isFocused||"single"===r.settings.mode)&&r.items.length){const e=r.controlChildren()
|
||||
t<0&&0===i.start&&0===i.length?l.push(e[r.caretPos-1]):t>0&&i.start===r.inputValue().length&&l.push(e[r.caretPos])}const a=l.map((e=>e.dataset.value))
|
||||
if(!a.length||"function"==typeof r.settings.onDelete&&!1===r.settings.onDelete.call(r,a,e))return!1
|
||||
for(H(e,!0),void 0!==s&&r.setCaret(s);l.length;)r.removeItem(l.pop())
|
||||
return r.showInput(),r.positionDropdown(),r.refreshOptions(!1),!0}advanceSelection(e,t){var i,s,n=this
|
||||
n.rtl&&(e*=-1),n.inputValue().length||(K(V,t)||K("shiftKey",t)?(s=(i=n.getLastActive(e))?i.classList.contains("active")?n.getAdjacent(i,e,"item"):i:e>0?n.control_input.nextElementSibling:n.control_input.previousElementSibling)&&(s.classList.contains("active")&&n.removeActiveItem(i),n.setActiveItemClass(s)):n.moveCaret(e))}moveCaret(e){}getLastActive(e){let t=this.control.querySelector(".last-active")
|
||||
if(t)return t
|
||||
var i=this.control.querySelectorAll(".active")
|
||||
return i?F(i,e):void 0}setCaret(e){this.caretPos=this.items.length}controlChildren(){return Array.from(this.control.querySelectorAll("[data-ts-item]"))}lock(){this.close(),this.isLocked=!0,this.refreshState()}unlock(){this.isLocked=!1,this.refreshState()}disable(){var e=this
|
||||
e.input.disabled=!0,e.control_input.disabled=!0,e.focus_node.tabIndex=-1,e.isDisabled=!0,e.lock()}enable(){var e=this
|
||||
e.input.disabled=!1,e.control_input.disabled=!1,e.focus_node.tabIndex=e.tabIndex,e.isDisabled=!1,e.unlock()}destroy(){var e=this,t=e.revertSettings
|
||||
e.trigger("destroy"),e.off(),e.wrapper.remove(),e.dropdown.remove(),e.input.innerHTML=t.innerHTML,e.input.tabIndex=t.tabIndex,S(e.input,"tomselected","ts-hidden-accessible"),e._destroy(),delete e.input.tomselect}render(e,t){return"function"!=typeof this.settings.render[e]?null:this._render(e,t)}_render(e,t){var i,s,n=""
|
||||
const o=this
|
||||
return"option"!==e&&"item"!=e||(n=D(t[o.settings.valueField])),null==(s=o.settings.render[e].call(this,t,N))||(s=w(s),"option"===e||"option_create"===e?t[o.settings.disabledField]?P(s,{"aria-disabled":"true"}):P(s,{"data-selectable":""}):"optgroup"===e&&(i=t.group[o.settings.optgroupValueField],P(s,{"data-group":i}),t.group[o.settings.disabledField]&&P(s,{"data-disabled":""})),"option"!==e&&"item"!==e||(P(s,{"data-value":n}),"item"===e?(C(s,o.settings.itemClass),P(s,{"data-ts-item":""})):(C(s,o.settings.optionClass),P(s,{role:"option",id:t.$id}),o.options[n].$div=s))),s}clearCache(){y(this.options,((e,t)=>{e.$div&&(e.$div.remove(),delete e.$div)}))}uncacheValue(e){const t=this.getOption(e)
|
||||
t&&t.remove()}canCreate(e){return this.settings.create&&e.length>0&&this.settings.createFilter.call(this,e)}hook(e,t,i){var s=this,n=s[t]
|
||||
s[t]=function(){var t,o
|
||||
return"after"===e&&(t=n.apply(s,arguments)),o=i.apply(s,arguments),"instead"===e?o:("before"===e&&(t=n.apply(s,arguments)),t)}}}return J.define("change_listener",(function(){B(this.input,"change",(()=>{this.sync()}))})),J.define("checkbox_options",(function(){var e=this,t=e.onOptionSelect
|
||||
e.settings.hideSelected=!1
|
||||
var i=function(e){setTimeout((()=>{var t=e.querySelector("input")
|
||||
e.classList.contains("selected")?t.checked=!0:t.checked=!1}),1)}
|
||||
e.hook("after","setupTemplates",(()=>{var t=e.settings.render.option
|
||||
e.settings.render.option=(i,s)=>{var n=w(t.call(e,i,s)),o=document.createElement("input")
|
||||
o.addEventListener("click",(function(e){H(e)})),o.type="checkbox"
|
||||
const r=q(i[e.settings.valueField])
|
||||
return r&&e.items.indexOf(r)>-1&&(o.checked=!0),n.prepend(o),n}})),e.on("item_remove",(t=>{var s=e.getOption(t)
|
||||
s&&(s.classList.remove("selected"),i(s))})),e.hook("instead","onOptionSelect",((s,n)=>{if(n.classList.contains("selected"))return n.classList.remove("selected"),e.removeItem(n.dataset.value),e.refreshOptions(),void H(s,!0)
|
||||
t.call(e,s,n),i(n)}))})),J.define("clear_button",(function(e){const t=this,i=Object.assign({className:"clear-button",title:"Clear All",html:e=>`<div class="${e.className}" title="${e.title}">×</div>`},e)
|
||||
t.on("initialize",(()=>{var e=w(i.html(i))
|
||||
e.addEventListener("click",(e=>{t.clear(),"single"===t.settings.mode&&t.settings.allowEmptyOption&&t.addItem(""),e.preventDefault(),e.stopPropagation()})),t.control.appendChild(e)}))})),J.define("drag_drop",(function(){var e=this
|
||||
if(!$.fn.sortable)throw new Error('The "drag_drop" plugin requires jQuery UI "sortable".')
|
||||
if("multi"===e.settings.mode){var t=e.lock,i=e.unlock
|
||||
e.hook("instead","lock",(()=>{var i=$(e.control).data("sortable")
|
||||
return i&&i.disable(),t.call(e)})),e.hook("instead","unlock",(()=>{var t=$(e.control).data("sortable")
|
||||
return t&&t.enable(),i.call(e)})),e.on("initialize",(()=>{var t=$(e.control).sortable({items:"[data-value]",forcePlaceholderSize:!0,disabled:e.isLocked,start:(e,i)=>{i.placeholder.css("width",i.helper.css("width")),t.css({overflow:"visible"})},stop:()=>{t.css({overflow:"hidden"})
|
||||
var i=[]
|
||||
t.children("[data-value]").each((function(){this.dataset.value&&i.push(this.dataset.value)})),e.setValue(i)}})}))}})),J.define("dropdown_header",(function(e){const t=this,i=Object.assign({title:"Untitled",headerClass:"dropdown-header",titleRowClass:"dropdown-header-title",labelClass:"dropdown-header-label",closeClass:"dropdown-header-close",html:e=>'<div class="'+e.headerClass+'"><div class="'+e.titleRowClass+'"><span class="'+e.labelClass+'">'+e.title+'</span><a class="'+e.closeClass+'">×</a></div></div>'},e)
|
||||
t.on("initialize",(()=>{var e=w(i.html(i)),s=e.querySelector("."+i.closeClass)
|
||||
s&&s.addEventListener("click",(e=>{H(e,!0),t.close()})),t.dropdown.insertBefore(e,t.dropdown.firstChild)}))})),J.define("caret_position",(function(){var e=this
|
||||
e.hook("instead","setCaret",(t=>{"single"!==e.settings.mode&&e.control.contains(e.control_input)?(t=Math.max(0,Math.min(e.items.length,t)))==e.caretPos||e.isPending||e.controlChildren().forEach(((i,s)=>{s<t?e.control_input.insertAdjacentElement("beforebegin",i):e.control.appendChild(i)})):t=e.items.length,e.caretPos=t})),e.hook("instead","moveCaret",(t=>{if(!e.isFocused)return
|
||||
const i=e.getLastActive(t)
|
||||
if(i){const s=L(i)
|
||||
e.setCaret(t>0?s+1:s),e.setActiveItem()}else e.setCaret(e.caretPos+t)}))})),J.define("dropdown_input",(function(){var e=this
|
||||
e.settings.shouldOpen=!0,e.hook("before","setup",(()=>{e.focus_node=e.control,C(e.control_input,"dropdown-input")
|
||||
const t=w('<div class="dropdown-input-wrap">')
|
||||
t.append(e.control_input),e.dropdown.insertBefore(t,e.dropdown.firstChild)})),e.on("initialize",(()=>{e.control_input.addEventListener("keydown",(t=>{switch(t.keyCode){case 27:return e.isOpen&&(H(t,!0),e.close()),void e.clearActiveItems()
|
||||
case 9:e.focus_node.tabIndex=-1}return e.onKeyDown.call(e,t)})),e.on("blur",(()=>{e.focus_node.tabIndex=e.isDisabled?-1:e.tabIndex})),e.on("dropdown_open",(()=>{e.control_input.focus()}))
|
||||
const t=e.onBlur
|
||||
e.hook("instead","onBlur",(i=>{if(!i||i.relatedTarget!=e.control_input)return t.call(e)})),B(e.control_input,"blur",(()=>e.onBlur())),e.hook("before","close",(()=>{e.isOpen&&e.focus_node.focus()}))}))})),J.define("input_autogrow",(function(){var e=this
|
||||
e.on("initialize",(()=>{var t=document.createElement("span"),i=e.control_input
|
||||
t.style.cssText="position:absolute; top:-99999px; left:-99999px; width:auto; padding:0; white-space:pre; ",e.wrapper.appendChild(t)
|
||||
for(const e of["letterSpacing","fontSize","fontFamily","fontWeight","textTransform"])t.style[e]=i.style[e]
|
||||
var s=()=>{e.items.length>0?(t.textContent=i.value,i.style.width=t.clientWidth+"px"):i.style.width=""}
|
||||
s(),e.on("update item_add item_remove",s),B(i,"input",s),B(i,"keyup",s),B(i,"blur",s),B(i,"update",s)}))})),J.define("no_backspace_delete",(function(){var e=this,t=e.deleteSelection
|
||||
this.hook("instead","deleteSelection",(i=>!!e.activeItems.length&&t.call(e,i)))})),J.define("no_active_items",(function(){this.hook("instead","setActiveItem",(()=>{})),this.hook("instead","selectAll",(()=>{}))})),J.define("optgroup_columns",(function(){var e=this,t=e.onKeyDown
|
||||
e.hook("instead","onKeyDown",(i=>{var s,n,o,r
|
||||
if(!e.isOpen||37!==i.keyCode&&39!==i.keyCode)return t.call(e,i)
|
||||
r=k(e.activeOption,"[data-group]"),s=L(e.activeOption,"[data-selectable]"),r&&(r=37===i.keyCode?r.previousSibling:r.nextSibling)&&(n=(o=r.querySelectorAll("[data-selectable]"))[Math.min(o.length-1,s)])&&e.setActiveOption(n)}))})),J.define("remove_button",(function(e){const t=Object.assign({label:"×",title:"Remove",className:"remove",append:!0},e)
|
||||
var i=this
|
||||
if(t.append){var s='<a href="javascript:void(0)" class="'+t.className+'" tabindex="-1" title="'+N(t.title)+'">'+t.label+"</a>"
|
||||
i.hook("after","setupTemplates",(()=>{var e=i.settings.render.item
|
||||
i.settings.render.item=(t,n)=>{var o=w(e.call(i,t,n)),r=w(s)
|
||||
return o.appendChild(r),B(r,"mousedown",(e=>{H(e,!0)})),B(r,"click",(e=>{if(H(e,!0),!i.isLocked){var t=o.dataset.value
|
||||
i.removeItem(t),i.refreshOptions(!1)}})),o}}))}})),J.define("restore_on_backspace",(function(e){const t=this,i=Object.assign({text:e=>e[t.settings.labelField]},e)
|
||||
t.on("item_remove",(function(e){if(""===t.control_input.value.trim()){var s=t.options[e]
|
||||
s&&t.setTextboxValue(i.text.call(t,s))}}))})),J.define("virtual_scroll",(function(){const e=this,t=e.canLoad,i=e.clearActiveOption,s=e.loadCallback
|
||||
var n,o={},r=!1
|
||||
if(!e.settings.firstUrl)throw"virtual_scroll plugin requires a firstUrl() method"
|
||||
function l(t){return!("number"==typeof e.settings.maxOptions&&n.children.length>=e.settings.maxOptions)&&!(!(t in o)||!o[t])}e.settings.sortField=[{field:"$order"},{field:"$score"}],e.setNextUrl=function(e,t){o[e]=t},e.getUrl=function(t){if(t in o){const e=o[t]
|
||||
return o[t]=!1,e}return o={},e.settings.firstUrl(t)},e.hook("instead","clearActiveOption",(()=>{if(!r)return i.call(e)})),e.hook("instead","canLoad",(i=>i in o?l(i):t.call(e,i))),e.hook("instead","loadCallback",((t,i)=>{r||e.clearOptions(),s.call(e,t,i),r=!1})),e.hook("after","refreshOptions",(()=>{const t=e.lastValue
|
||||
var i
|
||||
l(t)?(i=e.render("loading_more",{query:t}))&&i.setAttribute("data-selectable",""):t in o&&!n.querySelector(".no-results")&&(i=e.render("no_more_results",{query:t})),i&&(C(i,e.settings.optionClass),n.append(i))})),e.on("initialize",(()=>{n=e.dropdown_content,e.settings.render=Object.assign({},{loading_more:function(){return'<div class="loading-more-results">Loading more results ... </div>'},no_more_results:function(){return'<div class="no-more-results">No more results</div>'}},e.settings.render),n.addEventListener("scroll",(function(){n.clientHeight/(n.scrollHeight-n.scrollTop)<.95||l(e.lastValue)&&(r||(r=!0,e.load.call(e,e.lastValue)))}))}))})),J}))
|
||||
var tomSelect=function(e,t){return new TomSelect(e,t)}
|
||||
//# sourceMappingURL=tom-select.complete.min.js.map
|
||||
|
|
@ -1,334 +0,0 @@
|
|||
/**
|
||||
* tom-select.css (v2.0.0-rc.4)
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
|
||||
* file except in compliance with the License. You may obtain a copy of the License at:
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
* ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
.ts-wrapper.plugin-drag_drop.multi > .ts-control > div.ui-sortable-placeholder {
|
||||
visibility: visible !important;
|
||||
background: #f2f2f2 !important;
|
||||
background: rgba(0, 0, 0, 0.06) !important;
|
||||
border: 0 none !important;
|
||||
box-shadow: inset 0 0 12px 4px #fff; }
|
||||
|
||||
.ts-wrapper.plugin-drag_drop .ui-sortable-placeholder::after {
|
||||
content: '!';
|
||||
visibility: hidden; }
|
||||
|
||||
.ts-wrapper.plugin-drag_drop .ui-sortable-helper {
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); }
|
||||
|
||||
.plugin-checkbox_options .option input {
|
||||
margin-right: 0.5rem; }
|
||||
|
||||
.plugin-clear_button .ts-control {
|
||||
padding-right: calc( 1em + (3 * 6px)) !important; }
|
||||
|
||||
.plugin-clear_button .clear-button {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: calc(8px - 6px);
|
||||
margin-right: 0 !important;
|
||||
background: transparent !important;
|
||||
transition: opacity 0.5s;
|
||||
cursor: pointer; }
|
||||
|
||||
.plugin-clear_button.single .clear-button {
|
||||
right: calc(8px - 6px + 2rem); }
|
||||
|
||||
.plugin-clear_button.focus.has-items .clear-button,
|
||||
.plugin-clear_button:hover.has-items .clear-button {
|
||||
opacity: 1; }
|
||||
|
||||
.ts-wrapper .dropdown-header {
|
||||
position: relative;
|
||||
padding: 10px 8px;
|
||||
border-bottom: 1px solid #d0d0d0;
|
||||
background: #f8f8f8;
|
||||
border-radius: 3px 3px 0 0; }
|
||||
|
||||
.ts-wrapper .dropdown-header-close {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
color: #303030;
|
||||
opacity: 0.4;
|
||||
margin-top: -12px;
|
||||
line-height: 20px;
|
||||
font-size: 20px !important; }
|
||||
|
||||
.ts-wrapper .dropdown-header-close:hover {
|
||||
color: black; }
|
||||
|
||||
.plugin-dropdown_input.focus.dropdown-active .ts-control {
|
||||
box-shadow: none;
|
||||
border: 1px solid #d0d0d0; }
|
||||
|
||||
.plugin-dropdown_input .dropdown-input {
|
||||
border: 1px solid #d0d0d0;
|
||||
border-width: 0 0 1px 0;
|
||||
display: block;
|
||||
padding: 8px 8px;
|
||||
box-shadow: none;
|
||||
width: 100%;
|
||||
background: transparent; }
|
||||
|
||||
.ts-wrapper.plugin-input_autogrow.has-items .ts-control > input {
|
||||
min-width: 0; }
|
||||
|
||||
.ts-wrapper.plugin-input_autogrow.has-items.focus .ts-control > input {
|
||||
flex: none;
|
||||
min-width: 4px; }
|
||||
.ts-wrapper.plugin-input_autogrow.has-items.focus .ts-control > input::-webkit-input-placeholder {
|
||||
color: transparent; }
|
||||
.ts-wrapper.plugin-input_autogrow.has-items.focus .ts-control > input::-ms-input-placeholder {
|
||||
color: transparent; }
|
||||
.ts-wrapper.plugin-input_autogrow.has-items.focus .ts-control > input::placeholder {
|
||||
color: transparent; }
|
||||
|
||||
.ts-dropdown.plugin-optgroup_columns .ts-dropdown-content {
|
||||
display: flex; }
|
||||
|
||||
.ts-dropdown.plugin-optgroup_columns .optgroup {
|
||||
border-right: 1px solid #f2f2f2;
|
||||
border-top: 0 none;
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
min-width: 0; }
|
||||
|
||||
.ts-dropdown.plugin-optgroup_columns .optgroup:last-child {
|
||||
border-right: 0 none; }
|
||||
|
||||
.ts-dropdown.plugin-optgroup_columns .optgroup:before {
|
||||
display: none; }
|
||||
|
||||
.ts-dropdown.plugin-optgroup_columns .optgroup-header {
|
||||
border-top: 0 none; }
|
||||
|
||||
.ts-wrapper.plugin-remove_button .item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding-right: 0 !important; }
|
||||
|
||||
.ts-wrapper.plugin-remove_button .item .remove {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
padding: 2px 6px;
|
||||
border-left: 1px solid #d0d0d0;
|
||||
border-radius: 0 2px 2px 0;
|
||||
box-sizing: border-box;
|
||||
margin-left: 6px; }
|
||||
|
||||
.ts-wrapper.plugin-remove_button .item .remove:hover {
|
||||
background: rgba(0, 0, 0, 0.05); }
|
||||
|
||||
.ts-wrapper.plugin-remove_button .item.active .remove {
|
||||
border-left-color: #cacaca; }
|
||||
|
||||
.ts-wrapper.plugin-remove_button.disabled .item .remove:hover {
|
||||
background: none; }
|
||||
|
||||
.ts-wrapper.plugin-remove_button.disabled .item .remove {
|
||||
border-left-color: white; }
|
||||
|
||||
.ts-wrapper.plugin-remove_button .remove-single {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
font-size: 23px; }
|
||||
|
||||
.ts-wrapper {
|
||||
position: relative; }
|
||||
|
||||
.ts-dropdown,
|
||||
.ts-control,
|
||||
.ts-control input {
|
||||
color: #303030;
|
||||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
font-smoothing: inherit; }
|
||||
|
||||
.ts-control,
|
||||
.ts-wrapper.single.input-active .ts-control {
|
||||
background: #fff;
|
||||
cursor: text; }
|
||||
|
||||
.ts-control {
|
||||
border: 1px solid #d0d0d0;
|
||||
padding: 8px 8px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
box-shadow: none;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
flex-wrap: wrap; }
|
||||
.ts-wrapper.multi.has-items .ts-control {
|
||||
padding: calc( 8px - 2px - 0) 8px calc( 8px - 2px - 3px - 0); }
|
||||
.full .ts-control {
|
||||
background-color: #fff; }
|
||||
.disabled .ts-control,
|
||||
.disabled .ts-control * {
|
||||
cursor: default !important; }
|
||||
.focus .ts-control {
|
||||
box-shadow: none; }
|
||||
.ts-control > * {
|
||||
vertical-align: baseline;
|
||||
display: inline-block; }
|
||||
.ts-wrapper.multi .ts-control > div {
|
||||
cursor: pointer;
|
||||
margin: 0 3px 3px 0;
|
||||
padding: 2px 6px;
|
||||
background: #f2f2f2;
|
||||
color: #303030;
|
||||
border: 0 solid #d0d0d0; }
|
||||
.ts-wrapper.multi .ts-control > div.active {
|
||||
background: #e8e8e8;
|
||||
color: #303030;
|
||||
border: 0 solid #cacaca; }
|
||||
.ts-wrapper.multi.disabled .ts-control > div, .ts-wrapper.multi.disabled .ts-control > div.active {
|
||||
color: #7d7c7c;
|
||||
background: white;
|
||||
border: 0 solid white; }
|
||||
.ts-control > input {
|
||||
flex: 1 1 auto;
|
||||
min-width: 7rem;
|
||||
display: inline-block !important;
|
||||
padding: 0 !important;
|
||||
min-height: 0 !important;
|
||||
max-height: none !important;
|
||||
max-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
text-indent: 0 !important;
|
||||
border: 0 none !important;
|
||||
background: none !important;
|
||||
line-height: inherit !important;
|
||||
-webkit-user-select: auto !important;
|
||||
-moz-user-select: auto !important;
|
||||
-ms-user-select: auto !important;
|
||||
user-select: auto !important;
|
||||
box-shadow: none !important; }
|
||||
.ts-control > input::-ms-clear {
|
||||
display: none; }
|
||||
.ts-control > input:focus {
|
||||
outline: none !important; }
|
||||
.has-items .ts-control > input {
|
||||
margin: 0 4px !important; }
|
||||
.ts-control.rtl {
|
||||
text-align: right; }
|
||||
.ts-control.rtl.single .ts-control:after {
|
||||
left: 15px;
|
||||
right: auto; }
|
||||
.ts-control.rtl .ts-control > input {
|
||||
margin: 0 4px 0 -2px !important; }
|
||||
.disabled .ts-control {
|
||||
opacity: 0.5;
|
||||
background-color: #fafafa; }
|
||||
.input-hidden .ts-control > input {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
left: -10000px; }
|
||||
|
||||
.ts-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
border: 1px solid #d0d0d0;
|
||||
background: #fff;
|
||||
margin: 0.25rem 0 0 0;
|
||||
border-top: 0 none;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 0 0 3px 3px; }
|
||||
.ts-dropdown [data-selectable] {
|
||||
cursor: pointer;
|
||||
overflow: hidden; }
|
||||
.ts-dropdown [data-selectable] .highlight {
|
||||
background: rgba(125, 168, 208, 0.2);
|
||||
border-radius: 1px; }
|
||||
.ts-dropdown .option,
|
||||
.ts-dropdown .optgroup-header,
|
||||
.ts-dropdown .no-results,
|
||||
.ts-dropdown .create {
|
||||
padding: 5px 8px; }
|
||||
.ts-dropdown .option, .ts-dropdown [data-disabled], .ts-dropdown [data-disabled] [data-selectable].option {
|
||||
cursor: inherit;
|
||||
opacity: 0.5; }
|
||||
.ts-dropdown [data-selectable].option {
|
||||
opacity: 1;
|
||||
cursor: pointer; }
|
||||
.ts-dropdown .optgroup:first-child .optgroup-header {
|
||||
border-top: 0 none; }
|
||||
.ts-dropdown .optgroup-header {
|
||||
color: #303030;
|
||||
background: #fff;
|
||||
cursor: default; }
|
||||
.ts-dropdown .create:hover,
|
||||
.ts-dropdown .option:hover,
|
||||
.ts-dropdown .active {
|
||||
background-color: #f5fafd;
|
||||
color: #495c68; }
|
||||
.ts-dropdown .create:hover.create,
|
||||
.ts-dropdown .option:hover.create,
|
||||
.ts-dropdown .active.create {
|
||||
color: #495c68; }
|
||||
.ts-dropdown .create {
|
||||
color: rgba(48, 48, 48, 0.5); }
|
||||
.ts-dropdown .spinner {
|
||||
display: inline-block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin: 5px 8px; }
|
||||
.ts-dropdown .spinner:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 3px;
|
||||
border-radius: 50%;
|
||||
border: 5px solid #d0d0d0;
|
||||
border-color: #d0d0d0 transparent #d0d0d0 transparent;
|
||||
animation: lds-dual-ring 1.2s linear infinite; }
|
||||
|
||||
@keyframes lds-dual-ring {
|
||||
0% {
|
||||
transform: rotate(0deg); }
|
||||
100% {
|
||||
transform: rotate(360deg); } }
|
||||
|
||||
.ts-dropdown-content {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
max-height: 200px;
|
||||
overflow-scrolling: touch;
|
||||
scroll-behavior: smooth; }
|
||||
|
||||
.ts-hidden-accessible {
|
||||
border: 0 !important;
|
||||
clip: rect(0 0 0 0) !important;
|
||||
-webkit-clip-path: inset(50%) !important;
|
||||
clip-path: inset(50%) !important;
|
||||
height: 1px !important;
|
||||
overflow: hidden !important;
|
||||
padding: 0 !important;
|
||||
position: absolute !important;
|
||||
width: 1px !important;
|
||||
white-space: nowrap !important; }
|
||||
|
||||
/*# sourceMappingURL=tom-select.css.map */
|
||||
File diff suppressed because one or more lines are too long
27
txt2graph/lib/vis-9.1.2/vis-network.min.js
vendored
27
txt2graph/lib/vis-9.1.2/vis-network.min.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -1,163 +0,0 @@
|
|||
"""
|
||||
自定义实体类型定义
|
||||
只提取现实生活中真实存在的、可以有行动的实体
|
||||
"""
|
||||
|
||||
from pydantic import Field
|
||||
from zep_cloud.external_clients.ontology import EntityModel, EntityText, EdgeModel
|
||||
|
||||
|
||||
# ============== 实体类型定义 ==============
|
||||
|
||||
class Person(EntityModel):
|
||||
"""A real, named individual. Must have a specific name like "马化腾" or "Elon Musk". NOT generic terms like "某人", "用户", pronouns, or abstract roles."""
|
||||
role: EntityText = Field(
|
||||
description="职业或职位",
|
||||
default=None
|
||||
)
|
||||
affiliation: EntityText = Field(
|
||||
description="所属组织",
|
||||
default=None
|
||||
)
|
||||
|
||||
|
||||
class Organization(EntityModel):
|
||||
"""A real organization with an official name like "武汉大学", "联合国". NOT generic terms like "大学", "政府", "组织"."""
|
||||
org_type: EntityText = Field(
|
||||
description="组织类型",
|
||||
default=None
|
||||
)
|
||||
location: EntityText = Field(
|
||||
description="所在地",
|
||||
default=None
|
||||
)
|
||||
|
||||
|
||||
class Company(EntityModel):
|
||||
"""A real company with registered name like "腾讯", "Apple Inc.". NOT generic terms like "科技公司", "某企业"."""
|
||||
industry: EntityText = Field(
|
||||
description="所属行业",
|
||||
default=None
|
||||
)
|
||||
headquarters: EntityText = Field(
|
||||
description="总部位置",
|
||||
default=None
|
||||
)
|
||||
|
||||
|
||||
class Location(EntityModel):
|
||||
"""A real geographic location like "北京", "硅谷", "故宫". NOT generic terms like "某地", "一个城市"."""
|
||||
location_type: EntityText = Field(
|
||||
description="地点类型",
|
||||
default=None
|
||||
)
|
||||
country: EntityText = Field(
|
||||
description="所属国家",
|
||||
default=None
|
||||
)
|
||||
|
||||
|
||||
class Product(EntityModel):
|
||||
"""A real product/service with brand name like "iPhone", "微信". NOT generic categories like "手机", "软件"."""
|
||||
category: EntityText = Field(
|
||||
description="产品类别",
|
||||
default=None
|
||||
)
|
||||
manufacturer: EntityText = Field(
|
||||
description="制造商",
|
||||
default=None
|
||||
)
|
||||
|
||||
|
||||
class Event(EntityModel):
|
||||
"""A real, documented event like "2024巴黎奥运会", "新冠疫情". NOT generic terms like "会议", "活动"."""
|
||||
event_type: EntityText = Field(
|
||||
description="事件类型",
|
||||
default=None
|
||||
)
|
||||
date: EntityText = Field(
|
||||
description="发生日期",
|
||||
default=None
|
||||
)
|
||||
|
||||
|
||||
class Media(EntityModel):
|
||||
"""A real media outlet like "人民日报", "CNN", "微博". NOT generic terms like "媒体", "新闻"."""
|
||||
media_type: EntityText = Field(
|
||||
description="媒体类型",
|
||||
default=None
|
||||
)
|
||||
|
||||
|
||||
# ============== 边类型定义 ==============
|
||||
|
||||
class WorksFor(EdgeModel):
|
||||
"""Employment or affiliation relationship."""
|
||||
position: EntityText = Field(
|
||||
description="职位",
|
||||
default=None
|
||||
)
|
||||
|
||||
|
||||
class LocatedIn(EdgeModel):
|
||||
"""Geographic location relationship."""
|
||||
pass
|
||||
|
||||
|
||||
class PartOf(EdgeModel):
|
||||
"""Part-of or subsidiary relationship."""
|
||||
pass
|
||||
|
||||
|
||||
class Produces(EdgeModel):
|
||||
"""Production or creation relationship."""
|
||||
pass
|
||||
|
||||
|
||||
class ParticipatesIn(EdgeModel):
|
||||
"""Participation in an event."""
|
||||
role: EntityText = Field(
|
||||
description="参与角色",
|
||||
default=None
|
||||
)
|
||||
|
||||
|
||||
class Collaborates(EdgeModel):
|
||||
"""Collaboration or partnership relationship."""
|
||||
pass
|
||||
|
||||
|
||||
class Competes(EdgeModel):
|
||||
"""Competitive relationship."""
|
||||
pass
|
||||
|
||||
|
||||
class Reports(EdgeModel):
|
||||
"""Media reporting or coverage relationship."""
|
||||
pass
|
||||
|
||||
|
||||
# ============== 本体配置 ==============
|
||||
|
||||
# 实体类型字典
|
||||
ENTITY_TYPES = {
|
||||
"Person": Person,
|
||||
"Organization": Organization,
|
||||
"Company": Company,
|
||||
"Location": Location,
|
||||
"Product": Product,
|
||||
"Event": Event,
|
||||
"Media": Media,
|
||||
}
|
||||
|
||||
# 边类型字典
|
||||
EDGE_TYPES = {
|
||||
"WORKS_FOR": WorksFor,
|
||||
"LOCATED_IN": LocatedIn,
|
||||
"PART_OF": PartOf,
|
||||
"PRODUCES": Produces,
|
||||
"PARTICIPATES_IN": ParticipatesIn,
|
||||
"COLLABORATES": Collaborates,
|
||||
"COMPETES": Competes,
|
||||
"REPORTS": Reports,
|
||||
}
|
||||
|
|
@ -1,405 +0,0 @@
|
|||
"""
|
||||
Zep Graph HTML渲染器
|
||||
从Zep云服务获取图谱数据并生成交互式HTML可视化
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import argparse
|
||||
import tempfile
|
||||
import time
|
||||
from datetime import datetime
|
||||
from dotenv import load_dotenv
|
||||
from zep_cloud.client import Zep
|
||||
from pyvis.network import Network
|
||||
|
||||
# 加载环境变量
|
||||
load_dotenv()
|
||||
|
||||
# 实体类型对应的颜色
|
||||
ENTITY_COLORS = {
|
||||
"Person": "#ff6b6b",
|
||||
"Company": "#4ecdc4",
|
||||
"Organization": "#45b7d1",
|
||||
"Location": "#96ceb4",
|
||||
"Product": "#ffeead",
|
||||
"Event": "#dcc6e0",
|
||||
"Media": "#ffb74d",
|
||||
"Preference": "#a29bfe",
|
||||
"Topic": "#fd79a8",
|
||||
"Object": "#636e72",
|
||||
"Entity": "#b2bec3",
|
||||
}
|
||||
|
||||
# 默认颜色
|
||||
DEFAULT_COLOR = "#74b9ff"
|
||||
|
||||
|
||||
def get_graph_data(client: Zep, graph_id: str, max_retries: int = 3) -> tuple[list, list]:
|
||||
"""
|
||||
从Zep获取图谱的所有节点和边
|
||||
"""
|
||||
print(f"正在获取图谱 {graph_id} 的数据...")
|
||||
|
||||
nodes = None
|
||||
edges = None
|
||||
|
||||
# 获取所有节点(带重试)
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
nodes = client.graph.node.get_by_graph_id(graph_id=graph_id)
|
||||
print(f" 获取到 {len(nodes)} 个节点")
|
||||
break
|
||||
except Exception as e:
|
||||
if attempt < max_retries - 1:
|
||||
print(f" 获取节点失败,重试中... ({attempt + 1}/{max_retries})")
|
||||
time.sleep(2)
|
||||
else:
|
||||
raise e
|
||||
|
||||
# 获取所有边(带重试)
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
edges = client.graph.edge.get_by_graph_id(graph_id=graph_id)
|
||||
print(f" 获取到 {len(edges)} 个边")
|
||||
break
|
||||
except Exception as e:
|
||||
if attempt < max_retries - 1:
|
||||
print(f" 获取边失败,重试中... ({attempt + 1}/{max_retries})")
|
||||
time.sleep(2)
|
||||
else:
|
||||
raise e
|
||||
|
||||
return nodes or [], edges or []
|
||||
|
||||
|
||||
def create_html_graph(nodes: list, edges: list, graph_id: str, output_file: str):
|
||||
"""
|
||||
创建交互式HTML图谱可视化
|
||||
"""
|
||||
print("正在生成HTML可视化...")
|
||||
|
||||
# 创建网络图
|
||||
net = Network(
|
||||
height="100vh",
|
||||
width="100%",
|
||||
bgcolor="#1a1a2e",
|
||||
font_color="white",
|
||||
directed=True,
|
||||
select_menu=True,
|
||||
filter_menu=True,
|
||||
notebook=False,
|
||||
)
|
||||
|
||||
# 配置物理引擎和交互
|
||||
net.set_options("""
|
||||
{
|
||||
"nodes": {
|
||||
"font": {
|
||||
"size": 14,
|
||||
"face": "Arial, sans-serif",
|
||||
"color": "white"
|
||||
},
|
||||
"borderWidth": 2,
|
||||
"borderWidthSelected": 4,
|
||||
"shadow": {
|
||||
"enabled": true,
|
||||
"color": "rgba(0,0,0,0.5)",
|
||||
"size": 10
|
||||
}
|
||||
},
|
||||
"edges": {
|
||||
"color": {
|
||||
"color": "#555555",
|
||||
"highlight": "#667eea",
|
||||
"hover": "#667eea"
|
||||
},
|
||||
"arrows": {
|
||||
"to": {
|
||||
"enabled": true,
|
||||
"scaleFactor": 0.5
|
||||
}
|
||||
},
|
||||
"smooth": {
|
||||
"type": "continuous",
|
||||
"roundness": 0.2
|
||||
},
|
||||
"font": {
|
||||
"size": 10,
|
||||
"color": "#aaaaaa",
|
||||
"face": "Arial, sans-serif",
|
||||
"strokeWidth": 0,
|
||||
"background": "rgba(26,26,46,0.8)"
|
||||
},
|
||||
"width": 1.5,
|
||||
"hoverWidth": 2.5
|
||||
},
|
||||
"physics": {
|
||||
"enabled": true,
|
||||
"barnesHut": {
|
||||
"gravitationalConstant": -8000,
|
||||
"centralGravity": 0.3,
|
||||
"springLength": 150,
|
||||
"springConstant": 0.04,
|
||||
"damping": 0.09,
|
||||
"avoidOverlap": 0.5
|
||||
},
|
||||
"stabilization": {
|
||||
"enabled": true,
|
||||
"iterations": 300,
|
||||
"updateInterval": 25
|
||||
}
|
||||
},
|
||||
"interaction": {
|
||||
"hover": true,
|
||||
"tooltipDelay": 100,
|
||||
"navigationButtons": true,
|
||||
"keyboard": {
|
||||
"enabled": true
|
||||
},
|
||||
"multiselect": true,
|
||||
"zoomView": true
|
||||
}
|
||||
}
|
||||
""")
|
||||
|
||||
# 构建节点UUID到节点的映射
|
||||
node_map = {}
|
||||
for node in nodes:
|
||||
node_map[node.uuid_] = node
|
||||
|
||||
# 统计节点类型
|
||||
type_counts = {}
|
||||
|
||||
# 添加节点
|
||||
for node in nodes:
|
||||
node_labels = node.labels or []
|
||||
specific_labels = [l for l in node_labels if l not in ["Entity", "Node"]]
|
||||
node_type = specific_labels[0] if specific_labels else (node_labels[0] if node_labels else "Unknown")
|
||||
|
||||
color = ENTITY_COLORS.get(node_type, DEFAULT_COLOR)
|
||||
type_counts[node_type] = type_counts.get(node_type, 0) + 1
|
||||
|
||||
# 简化的工具提示(避免复杂HTML)
|
||||
summary_text = (node.summary or '无摘要')[:200]
|
||||
title = f"{node.name}\n类型: {', '.join(node_labels)}\n摘要: {summary_text}"
|
||||
|
||||
# 根据节点类型调整大小
|
||||
if node_type == "Person":
|
||||
size = 25
|
||||
elif node_type in ["Company", "Organization"]:
|
||||
size = 30
|
||||
elif node_type == "Location":
|
||||
size = 22
|
||||
elif node_type == "Event":
|
||||
size = 20
|
||||
else:
|
||||
size = 18
|
||||
|
||||
net.add_node(
|
||||
node.uuid_,
|
||||
label=node.name[:20] + "..." if len(node.name) > 20 else node.name,
|
||||
title=title,
|
||||
color=color,
|
||||
size=size,
|
||||
)
|
||||
|
||||
# 添加边
|
||||
edge_count = 0
|
||||
for edge in edges:
|
||||
source_uuid = edge.source_node_uuid
|
||||
target_uuid = edge.target_node_uuid
|
||||
|
||||
if source_uuid in node_map and target_uuid in node_map:
|
||||
source_node = node_map[source_uuid]
|
||||
target_node = node_map[target_uuid]
|
||||
|
||||
title = f"{source_node.name} -> {target_node.name}\n关系: {edge.name or '未命名'}\n事实: {edge.fact or '无描述'}"
|
||||
|
||||
edge_label = edge.name or ""
|
||||
if len(edge_label) > 15:
|
||||
edge_label = edge_label[:15] + "..."
|
||||
|
||||
net.add_edge(
|
||||
source_uuid,
|
||||
target_uuid,
|
||||
title=title,
|
||||
label=edge_label,
|
||||
)
|
||||
edge_count += 1
|
||||
|
||||
print(f" 添加了 {len(nodes)} 个节点和 {edge_count} 条边到可视化")
|
||||
|
||||
# 打印类型统计
|
||||
print("\n节点类型统计:")
|
||||
for t, count in sorted(type_counts.items(), key=lambda x: -x[1]):
|
||||
print(f" {t}: {count}")
|
||||
|
||||
# 保存到临时文件获取原始HTML
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False, encoding='utf-8') as tmp:
|
||||
net.save_graph(tmp.name)
|
||||
tmp_path = tmp.name
|
||||
|
||||
# 读取生成的HTML
|
||||
with open(tmp_path, 'r', encoding='utf-8') as f:
|
||||
html_content = f.read()
|
||||
|
||||
os.unlink(tmp_path)
|
||||
|
||||
# 构建自定义CSS和Header HTML
|
||||
custom_css = """
|
||||
<style>
|
||||
#graph-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 15px 20px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
||||
color: white;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
#graph-header h1 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
#graph-header .stats {
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.9;
|
||||
margin-top: 5px;
|
||||
}
|
||||
#graph-legend {
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
right: 20px;
|
||||
z-index: 1000;
|
||||
background: rgba(26, 26, 46, 0.95);
|
||||
border: 1px solid #333;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
max-width: 200px;
|
||||
color: white;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
#graph-legend h3 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 0.9rem;
|
||||
border-bottom: 1px solid #444;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
#graph-legend .item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 5px 0;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
#graph-legend .color-box {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 3px;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
#mynetwork {
|
||||
margin-top: 70px !important;
|
||||
}
|
||||
body {
|
||||
background: #1a1a2e !important;
|
||||
}
|
||||
</style>
|
||||
"""
|
||||
|
||||
# 构建图例HTML
|
||||
legend_items = ""
|
||||
for entity_type, color in ENTITY_COLORS.items():
|
||||
if entity_type in type_counts:
|
||||
legend_items += f'''
|
||||
<div class="item">
|
||||
<div class="color-box" style="background: {color};"></div>
|
||||
<span>{entity_type} ({type_counts.get(entity_type, 0)})</span>
|
||||
</div>'''
|
||||
|
||||
custom_html = f'''
|
||||
<div id="graph-header">
|
||||
<h1>Knowledge Graph Visualization</h1>
|
||||
<div class="stats">
|
||||
Graph ID: {graph_id} | 节点: {len(nodes)} | 边: {edge_count} | 生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
||||
</div>
|
||||
</div>
|
||||
<div id="graph-legend">
|
||||
<h3>图例</h3>
|
||||
{legend_items}
|
||||
</div>
|
||||
'''
|
||||
|
||||
# 在</head>前插入自定义CSS
|
||||
html_content = html_content.replace('</head>', custom_css + '</head>')
|
||||
|
||||
# 在<body>后插入自定义HTML
|
||||
html_content = html_content.replace('<body>', '<body>' + custom_html)
|
||||
|
||||
# 修改标题
|
||||
html_content = html_content.replace('<title>Network</title>', f'<title>Zep Graph: {graph_id}</title>')
|
||||
|
||||
# 写入文件
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
f.write(html_content)
|
||||
|
||||
print(f"\nHTML文件已生成: {output_file}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Zep Graph HTML渲染器')
|
||||
parser.add_argument(
|
||||
'--graph-id', '-g',
|
||||
default='graph_6e3697873495400d',
|
||||
help='Zep图谱ID'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--output', '-o',
|
||||
default='graph_visualization.html',
|
||||
help='输出HTML文件路径'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--api-key', '-k',
|
||||
default=None,
|
||||
help='Zep API Key (默认从环境变量ZEP_API_KEY读取)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 获取API Key
|
||||
api_key = args.api_key or os.environ.get('ZEP_API_KEY')
|
||||
if not api_key:
|
||||
print("错误: 请设置ZEP_API_KEY环境变量或通过--api-key参数提供")
|
||||
return 1
|
||||
|
||||
# 创建客户端
|
||||
client = Zep(api_key=api_key)
|
||||
|
||||
try:
|
||||
# 获取图数据
|
||||
nodes, edges = get_graph_data(client, args.graph_id)
|
||||
|
||||
if not nodes:
|
||||
print("警告: 图谱中没有节点")
|
||||
return 1
|
||||
|
||||
# 生成HTML
|
||||
create_html_graph(nodes, edges, args.graph_id, args.output)
|
||||
|
||||
print(f"\n完成! 请在浏览器中打开 {args.output} 查看图谱")
|
||||
return 0
|
||||
|
||||
except Exception as e:
|
||||
print(f"错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# Zep Cloud SDK
|
||||
zep-cloud>=2.0.0
|
||||
|
||||
# PDF处理
|
||||
PyMuPDF>=1.24.0
|
||||
|
||||
# Markdown处理
|
||||
markdown>=3.5.0
|
||||
|
||||
# Web可视化界面
|
||||
streamlit>=1.38.0
|
||||
|
||||
# 图可视化
|
||||
pyvis>=0.3.2
|
||||
|
||||
# 环境变量
|
||||
python-dotenv>=1.0.0
|
||||
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
"""
|
||||
文本提取模块
|
||||
支持从 .md, .txt, .pdf 文件中提取纯文本
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def extract_from_txt(file_path: str) -> str:
|
||||
"""从TXT文件提取文本"""
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def extract_from_md(file_path: str) -> str:
|
||||
"""从Markdown文件提取文本(保留原始格式,不转换HTML)"""
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def extract_from_pdf(file_path: str) -> str:
|
||||
"""从PDF文件提取文本"""
|
||||
try:
|
||||
import fitz # PyMuPDF
|
||||
except ImportError:
|
||||
raise ImportError("请安装 PyMuPDF: pip install PyMuPDF")
|
||||
|
||||
text_parts = []
|
||||
with fitz.open(file_path) as doc:
|
||||
for page_num, page in enumerate(doc):
|
||||
text = page.get_text()
|
||||
if text.strip():
|
||||
text_parts.append(f"--- 第 {page_num + 1} 页 ---\n{text}")
|
||||
|
||||
return "\n\n".join(text_parts)
|
||||
|
||||
|
||||
def extract_text(file_path: str) -> str:
|
||||
"""
|
||||
根据文件扩展名自动选择提取方法
|
||||
|
||||
Args:
|
||||
file_path: 文件路径
|
||||
|
||||
Returns:
|
||||
提取的纯文本内容
|
||||
|
||||
Raises:
|
||||
ValueError: 不支持的文件格式
|
||||
FileNotFoundError: 文件不存在
|
||||
"""
|
||||
path = Path(file_path)
|
||||
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"文件不存在: {file_path}")
|
||||
|
||||
suffix = path.suffix.lower()
|
||||
|
||||
extractors = {
|
||||
'.txt': extract_from_txt,
|
||||
'.md': extract_from_md,
|
||||
'.markdown': extract_from_md,
|
||||
'.pdf': extract_from_pdf,
|
||||
}
|
||||
|
||||
extractor = extractors.get(suffix)
|
||||
if extractor is None:
|
||||
supported = ', '.join(extractors.keys())
|
||||
raise ValueError(f"不支持的文件格式: {suffix}。支持的格式: {supported}")
|
||||
|
||||
return extractor(file_path)
|
||||
|
||||
|
||||
def split_text_into_chunks(text: str, max_chunk_size: int = 8000, overlap: int = 200) -> list[str]:
|
||||
"""
|
||||
将长文本分割成多个小块,适合Zep处理
|
||||
|
||||
Args:
|
||||
text: 原始文本
|
||||
max_chunk_size: 每个块的最大字符数
|
||||
overlap: 块之间的重叠字符数
|
||||
|
||||
Returns:
|
||||
文本块列表
|
||||
"""
|
||||
if len(text) <= max_chunk_size:
|
||||
return [text]
|
||||
|
||||
chunks = []
|
||||
start = 0
|
||||
|
||||
while start < len(text):
|
||||
end = start + max_chunk_size
|
||||
|
||||
# 尝试在句子边界处分割
|
||||
if end < len(text):
|
||||
# 查找最近的句子结束符
|
||||
for sep in ['。', '!', '?', '\n\n', '. ', '! ', '? ']:
|
||||
last_sep = text[start:end].rfind(sep)
|
||||
if last_sep != -1 and last_sep > max_chunk_size * 0.5:
|
||||
end = start + last_sep + len(sep)
|
||||
break
|
||||
|
||||
chunk = text[start:end].strip()
|
||||
if chunk:
|
||||
chunks.append(chunk)
|
||||
|
||||
# 下一个块从重叠位置开始
|
||||
start = end - overlap if end < len(text) else len(text)
|
||||
|
||||
return chunks
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 测试
|
||||
import sys
|
||||
if len(sys.argv) > 1:
|
||||
file_path = sys.argv[1]
|
||||
text = extract_text(file_path)
|
||||
print(f"提取了 {len(text)} 个字符")
|
||||
print(f"前500字符:\n{text[:500]}")
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
Loading…
Reference in a new issue