q275343119 commited on
Commit
943f734
·
1 Parent(s): 47dd425

add - init

Browse files
.dockerignore ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Git 相关文件
2
+ .git
3
+ .gitignore
4
+ .gitattributes
5
+
6
+ # IDE 相关文件
7
+ .idea
8
+ .vscode
9
+ *.swp
10
+ *.swo
11
+
12
+ # Python 相关文件
13
+ __pycache__
14
+ *.pyc
15
+ *.pyo
16
+ *.pyd
17
+ .Python
18
+ env
19
+ pip-log.txt
20
+ pip-delete-this-directory.txt
21
+ .tox
22
+ .coverage
23
+ .coverage.*
24
+ .cache
25
+ nosetests.xml
26
+ coverage.xml
27
+ *.cover
28
+ *.log
29
+ .git
30
+ .mypy_cache
31
+ .pytest_cache
32
+ .hypothesis
33
+
34
+ # 虚拟环境
35
+ .venv
36
+ venv/
37
+ ENV/
38
+ env/
39
+ .venv/
40
+
41
+ # 系统文件
42
+ .DS_Store
43
+ .DS_Store?
44
+ ._*
45
+ .Spotlight-V100
46
+ .Trashes
47
+ ehthumbs.db
48
+ Thumbs.db
49
+
50
+ # 临时文件
51
+ *.tmp
52
+ *.temp
53
+ *.bak
54
+ *.backup
55
+
56
+ # 日志文件
57
+ *.log
58
+ logs/
59
+
60
+ # 文档文件
61
+ README.md
62
+ TODO.md
63
+ *.md
64
+
65
+ # 测试文件
66
+ test/
67
+ tests/
68
+ *_test.py
69
+ test_*.py
DEPLOYMENT.md ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Docker 部署指南
2
+
3
+ ## 项目概述
4
+ 这是一个基于 Streamlit 的 Web 应用,提供了多语言文本处理功能。
5
+
6
+ ## 部署方式
7
+
8
+ ### 方式一:使用 Docker Compose(推荐)
9
+
10
+ 1. **构建并启动服务**
11
+ ```bash
12
+ docker-compose up --build
13
+ ```
14
+
15
+ 2. **后台运行**
16
+ ```bash
17
+ docker-compose up -d --build
18
+ ```
19
+
20
+ 3. **停止服务**
21
+ ```bash
22
+ docker-compose down
23
+ ```
24
+
25
+ 4. **查看日志**
26
+ ```bash
27
+ docker-compose logs -f
28
+ ```
29
+
30
+ ### 方式二:使用 Docker 命令
31
+
32
+ 1. **构建镜像**
33
+ ```bash
34
+ docker build -t streamlit-rteb .
35
+ ```
36
+
37
+ 2. **运行容器**
38
+ ```bash
39
+ docker run -d -p 8501:8501 --name streamlit-rteb streamlit-rteb
40
+ ```
41
+
42
+ 3. **停止容器**
43
+ ```bash
44
+ docker stop streamlit-rteb
45
+ docker rm streamlit-rteb
46
+ ```
47
+
48
+ ## 访问应用
49
+
50
+ 部署成功后,在浏览器中访问:
51
+ - **本地访问**: http://localhost:8501
52
+ - **局域网访问**: http://[服务器IP]:8501
53
+
54
+ ## 环境变量
55
+
56
+ 可以通过环境变量自定义配置:
57
+
58
+ ```bash
59
+ # 设置端口
60
+ export STREAMLIT_SERVER_PORT=8501
61
+
62
+ # 设置地址
63
+ export STREAMLIT_SERVER_ADDRESS=0.0.0.0
64
+
65
+ # 设置 Python 路径
66
+ export PYTHONPATH=/app
67
+ ```
68
+
69
+ ## 健康检查
70
+
71
+ 应用内置了健康检查机制:
72
+ - 检查间隔:30秒
73
+ - 超时时间:10秒
74
+ - 重试次数:3次
75
+ - 启动等待时间:40秒
76
+
77
+ ## 故障排除
78
+
79
+ ### 1. 端口被占用
80
+ 如果 8501 端口被占用,可以修改端口映射:
81
+ ```bash
82
+ docker run -d -p 8502:8501 --name streamlit-rteb streamlit-rteb
83
+ ```
84
+
85
+ ### 2. 构建失败
86
+ 检查 Dockerfile 和依赖文件是否正确:
87
+ ```bash
88
+ docker build --no-cache -t streamlit-rteb .
89
+ ```
90
+
91
+ ### 3. 应用无法启动
92
+ 查看容器日志:
93
+ ```bash
94
+ docker logs streamlit-rteb
95
+ ```
96
+
97
+ ## 开发模式
98
+
99
+ 如果需要开发调试,可以挂载本地目录:
100
+
101
+ 1. **修改 docker-compose.yml**
102
+ ```yaml
103
+ volumes:
104
+ - ./app:/app/app
105
+ - ./utils:/app/utils
106
+ - ./app.py:/app/app.py
107
+ ```
108
+
109
+ 2. **重新启动服务**
110
+ ```bash
111
+ docker-compose up --build
112
+ ```
113
+
114
+ ## 性能优化
115
+
116
+ 1. **使用多阶段构建**(可选)
117
+ 2. **启用缓存**:Docker 会自动缓存构建层
118
+ 3. **优化镜像大小**:使用 slim 基础镜像
119
+
120
+ ## 安全建议
121
+
122
+ 1. **生产环境**:使用非 root 用户运行
123
+ 2. **网络安全**:配置防火墙规则
124
+ 3. **镜像安全**:定期更新基础镜像
125
+ 4. **依赖安全**:定期更新 Python 依赖包
Dockerfile ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 使用 Python 3.11 slim 镜像作为基础镜像
2
+ FROM python:3.11-slim
3
+
4
+ # 设置工作目录
5
+ WORKDIR /app
6
+
7
+ # 安装系统依赖
8
+ RUN apt-get update && apt-get install -y \
9
+ build-essential \
10
+ curl \
11
+ software-properties-common \
12
+ git \
13
+ && rm -rf /var/lib/apt/lists/*
14
+
15
+ # 复制依赖文件
16
+ COPY requirements.txt ./
17
+
18
+ # 安装 Python 依赖
19
+ RUN pip install --no-cache-dir -r requirements.txt
20
+
21
+ # 复制应用代码
22
+ COPY app/ ./app/
23
+ COPY utils/ ./utils/
24
+ COPY app.py ./
25
+
26
+ # 暴露 Streamlit 默认端口
27
+ EXPOSE 8501
28
+
29
+ # 健康检查
30
+ HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health || exit 1
31
+
32
+ # 设置环境变量
33
+ ENV PYTHONPATH=/app
34
+ ENV STREAMLIT_SERVER_PORT=8501
35
+ ENV STREAMLIT_SERVER_ADDRESS=0.0.0.0
36
+
37
+ # 启动命令
38
+ ENTRYPOINT ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]
app.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ # Compatibility fix - must be executed before importing streamlit
4
+ try:
5
+ import streamlit.components.v1 as components
6
+ if not hasattr(components, 'components'):
7
+ components.components = components
8
+ if not hasattr(components, 'MarshallComponentException'):
9
+ class MarshallComponentException(Exception):
10
+ pass
11
+ components.MarshallComponentException = MarshallComponentException
12
+ except Exception as e:
13
+ print(f"Compatibility fix warning: {e}")
14
+
15
+ import streamlit as st
16
+
17
+ from st_pages import get_nav_from_toml, add_page_title
18
+
19
+ from app.backend.app_init_func import LI_CSS, init_leaderboard, init_pages
20
+ from app.backend.data_engine import DataEngine
21
+
22
+
23
+ def main():
24
+ # init global data engine
25
+ data_engine = DataEngine()
26
+
27
+ st.session_state["data_engine"] = data_engine
28
+ st.set_page_config(layout="wide")
29
+
30
+ # init leaderboard and pages
31
+ leaderboard_change, page_change = init_leaderboard()
32
+
33
+ init_pages(leaderboard_change, page_change)
34
+
35
+ # load page tree
36
+ nav = get_nav_from_toml(
37
+ "app/ui/pages_sections.toml"
38
+ )
39
+
40
+ # Add custom CSS
41
+ css = "\n".join(LI_CSS)
42
+ st.markdown(f"""
43
+ <style>
44
+
45
+ div[data-testid="stToolbar"] {{visibility: hidden; height: 0px;}}
46
+
47
+ footer {{visibility: hidden;}}
48
+ </style>
49
+
50
+ <style>
51
+ {css}
52
+ </style>
53
+ """
54
+ , unsafe_allow_html=True)
55
+
56
+ pg = st.navigation(nav)
57
+
58
+ # add_page_title(pg)
59
+
60
+ pg.run()
61
+
62
+ if __name__ == '__main__':
63
+ main()
app/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+
app/backend/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+
app/backend/app_init_func.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import os
3
+
4
+ from app.backend.constant import LEADERBOARD_ICON_MAP
5
+ from app.backend.data_engine import DataEngine
6
+
7
+ LEADERBOARD_MAP = {}
8
+ LI_CSS = []
9
+ PAGE_SECTIONS = []
10
+
11
+
12
+ def init_leaderboard():
13
+ data_engine = st.session_state.get("data_engine",DataEngine())
14
+ leaderboard_map = {}
15
+ page_sections = []
16
+ li_css = []
17
+ sort_id = 0
18
+ leaderboard_change = False
19
+ page_change = False
20
+
21
+ for dataset in data_engine.datasets:
22
+ sort_id += 1
23
+ leaderboard = dataset["leaderboard"]
24
+ name = dataset["name"]
25
+
26
+ leaderboard_section = f"{leaderboard.capitalize()} Leaderboard"
27
+ if leaderboard_section not in leaderboard_map:
28
+ leaderboard_map[leaderboard_section] = []
29
+ if name.lower() == leaderboard.lower():
30
+ leaderboard_map[leaderboard_section].append((name, 0))
31
+ else:
32
+ leaderboard_map[leaderboard_section].append((name, sort_id))
33
+ li_css.append(f"""
34
+ ul[data-testid="stSidebarNavItems"] li:nth-child({sort_id}) {{
35
+ text-indent: 2rem;
36
+ }}
37
+ """)
38
+ page_name = leaderboard_section if name.lower() == leaderboard.lower() else name.capitalize()
39
+ page_sections.append(f"""
40
+ [[pages]]
41
+ path = "app/ui/pages/{name}.py"
42
+ name = "{page_name}"
43
+ icon = "{LEADERBOARD_ICON_MAP.get(page_name, "")}"
44
+ """)
45
+
46
+ # ensure leaderboard is first
47
+ for k, v in leaderboard_map.items():
48
+ v.sort(key=lambda x: x[1])
49
+
50
+ if leaderboard_map != LEADERBOARD_MAP:
51
+ LEADERBOARD_MAP.update(leaderboard_map)
52
+ leaderboard_change = True
53
+ if page_sections != PAGE_SECTIONS:
54
+ PAGE_SECTIONS.clear()
55
+ PAGE_SECTIONS.extend(page_sections)
56
+ page_change = True
57
+ if li_css != LI_CSS:
58
+ LI_CSS.clear()
59
+ LI_CSS.extend(li_css)
60
+
61
+ return leaderboard_change, page_change
62
+
63
+
64
+ def init_pages(leaderboard_change, page_change):
65
+ # init pages
66
+ if leaderboard_change:
67
+ with open("app/ui/pages/data_page.py", "r", encoding="utf-8") as f:
68
+ data_page = f.read()
69
+ for leaderboard, group_names in LEADERBOARD_MAP.items():
70
+
71
+ for group_name in group_names:
72
+ path = os.path.join("app/ui/pages", f"{group_name[0]}.py")
73
+ with open(path, "w", encoding="utf-8") as f:
74
+ f.write(data_page.replace("$group_name$", group_name[0])
75
+ )
76
+ if page_change:
77
+ with open("app/ui/pages_sections.toml", "w", encoding="utf-8") as f:
78
+ f.write("\n".join(PAGE_SECTIONS))
79
+
80
+
81
+ if __name__ == '__main__':
82
+ init_leaderboard()
83
+ init_pages()
84
+ print("\n".join(PAGE_SECTIONS))
85
+ print("\n".join(LI_CSS))
app/backend/component/__init__.py ADDED
File without changes
app/backend/component/header_tooltip_link_component.py ADDED
@@ -0,0 +1,654 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from st_aggrid import JsCode
2
+
3
+
4
+ def get_header_tooltip_link_component_simple(theme_type="light"):
5
+ """
6
+ 真正的最小化版本 - 只添加tooltip链接功能
7
+
8
+ 基于工作组件的实现,修复排序、菜单和抖动问题
9
+ """
10
+ return JsCode(f"""
11
+ class HeaderTooltipLinkSimple {{
12
+ init(params) {{
13
+ this.params = params;
14
+ this.themeType = "{theme_type}";
15
+ this.isDestroyed = false;
16
+ this.customTooltip = null;
17
+ this.hideTimeout = null;
18
+ this.eGui = null;
19
+ this.isInitialized = false;
20
+ this.eventListenersBound = false; // 添加事件监听器绑定标志
21
+ }}
22
+
23
+ getGui() {{
24
+ if (!this.eGui && !this.isDestroyed) {{
25
+ this.eGui = this.createGui();
26
+ this.isInitialized = true;
27
+ }}
28
+ return this.eGui || document.createElement('div');
29
+ }}
30
+
31
+ createGui() {{
32
+ try {{
33
+ // 🎯 创建基本的header容器
34
+ const container = document.createElement('div');
35
+ container.classList.add('ag-cell-label-container');
36
+ container.style.cssText = `
37
+ display: flex;
38
+ align-items: center;
39
+ justify-content: flex-start;
40
+ height: 100%;
41
+ width: 100%;
42
+ `;
43
+
44
+ // 🎯 创建header label
45
+ const headerLabel = document.createElement('div');
46
+ headerLabel.classList.add('ag-header-cell-label');
47
+ headerLabel.style.cssText = `
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: flex-start;
51
+ height: 100%;
52
+ width: 100%;
53
+ cursor: pointer;
54
+ `;
55
+
56
+ // 🎯 显示列名
57
+ const textSpan = document.createElement('span');
58
+ const displayText = this.params.displayName ||
59
+ (this.params.column && this.params.column.colDef ? this.params.column.colDef.headerName : '') ||
60
+ (this.params.column && this.params.column.colDef ? this.params.column.colDef.field : '') ||
61
+ '';
62
+ textSpan.textContent = displayText;
63
+ headerLabel.appendChild(textSpan);
64
+
65
+ // 🎯 排序图标
66
+ const sortContainer = document.createElement('span');
67
+ sortContainer.classList.add('ag-header-icon', 'ag-header-icon-sort');
68
+ this.sortIcon = document.createElement('span');
69
+ this.updateSortIcon();
70
+ sortContainer.appendChild(this.sortIcon);
71
+ headerLabel.appendChild(sortContainer);
72
+
73
+ // 🎯 菜单按钮 - 使用正确的样式
74
+ const menuButton = document.createElement('span');
75
+ menuButton.classList.add('ag-header-icon', 'ag-header-cell-menu-button');
76
+ menuButton.innerHTML = '<i class="ag-icon ag-icon-filter"></i>';
77
+ headerLabel.appendChild(menuButton);
78
+
79
+ container.appendChild(headerLabel);
80
+
81
+ // 🎯 创建tooltip - 只在第一次创建
82
+ if (!this.customTooltip) {{
83
+ this.createCustomTooltip();
84
+ }}
85
+
86
+ // 🎯 事件监听 - 只在第一次绑定
87
+ if (!this.eventListenersBound) {{
88
+ this.setupEventListeners(container, headerLabel, menuButton);
89
+ this.eventListenersBound = true;
90
+ }}
91
+
92
+ return container;
93
+ }} catch (error) {{
94
+ console.error('Error creating header component:', error);
95
+ // 返回一个简单的fallback
96
+ const fallback = document.createElement('div');
97
+ fallback.textContent = this.params.displayName || '';
98
+ return fallback;
99
+ }}
100
+ }}
101
+
102
+ setupEventListeners(container, headerLabel, menuButton) {{
103
+ // 🎯 Tooltip事件
104
+ container.addEventListener('mouseenter', (event) => {{
105
+ if (this.params.linkUrl && this.params.linkUrl !== '#' && !this.isDestroyed) {{
106
+ this.cancelHideTimeout();
107
+ this.showCustomTooltip(event);
108
+ }}
109
+ }});
110
+
111
+ container.addEventListener('mouseleave', () => {{
112
+ if (!this.isDestroyed) {{
113
+ this.startHideTimeout();
114
+ }}
115
+ }});
116
+
117
+ // 🎯 自定义tooltip事件
118
+ if (this.customTooltip) {{
119
+ this.customTooltip.addEventListener('mouseenter', () => {{
120
+ if (!this.isDestroyed) {{
121
+ this.cancelHideTimeout();
122
+ }}
123
+ }});
124
+
125
+ this.customTooltip.addEventListener('mouseleave', () => {{
126
+ if (!this.isDestroyed) {{
127
+ this.hideCustomTooltip();
128
+ }}
129
+ }});
130
+ }}
131
+
132
+ // 🎯 排序功能 - 使用正确的API
133
+ headerLabel.addEventListener('click', (event) => {{
134
+ if (this.params.enableSorting && !this.isDestroyed) {{
135
+ // 使用新的API调用方法
136
+ if (this.params.progressSort) {{
137
+ this.params.progressSort();
138
+ }} else if (this.params.column && this.params.column.setSort) {{
139
+ const currentSort = this.params.column.getSort();
140
+ const newSort = currentSort === 'asc' ? 'desc' :
141
+ currentSort === 'desc' ? null : 'asc';
142
+ this.params.column.setSort(newSort);
143
+ }}
144
+ this.updateSortIcon();
145
+ }}
146
+ }});
147
+
148
+ // 🎯 菜单功能 - 使用正确的API
149
+ menuButton.addEventListener('click', (event) => {{
150
+ event.stopPropagation();
151
+ if (!this.isDestroyed && this.params.api) {{
152
+ // 使用更兼容的菜单API
153
+ try {{
154
+ // 尝试使用新的API
155
+ if (this.params.api.showColumnMenuAfterButtonClick) {{
156
+ this.params.api.showColumnMenuAfterButtonClick(
157
+ this.params.column,
158
+ menuButton,
159
+ 'columnMenu'
160
+ );
161
+ }} else if (this.params.api.showColumnMenu) {{
162
+ // 备用API
163
+ this.params.api.showColumnMenu(this.params.column);
164
+ }} else {{
165
+ // 最后的备用方案 - 触发原生菜单事件
166
+ const menuEvent = new MouseEvent('click', {{
167
+ bubbles: true,
168
+ cancelable: true,
169
+ view: window
170
+ }});
171
+ menuButton.dispatchEvent(menuEvent);
172
+ }}
173
+ }} catch (error) {{
174
+ console.warn('Menu API not available:', error);
175
+ // 最后的备用方案 - 触发原生菜单事件
176
+ try {{
177
+ const menuEvent = new MouseEvent('click', {{
178
+ bubbles: true,
179
+ cancelable: true,
180
+ view: window
181
+ }});
182
+ menuButton.dispatchEvent(menuEvent);
183
+ }} catch (e) {{
184
+ console.warn('Fallback menu also failed:', e);
185
+ }}
186
+ }}
187
+ }}
188
+ }});
189
+ }}
190
+
191
+ createCustomTooltip() {{
192
+ // 确保tooltip只创建一次
193
+ if (this.customTooltip) return;
194
+
195
+ this.customTooltip = document.createElement('div');
196
+ this.customTooltip.classList.add('header-tooltip-link-simple');
197
+ this.customTooltip.style.cssText = `
198
+ position: fixed;
199
+ display: none;
200
+ z-index: 999999;
201
+ padding: 8px 12px;
202
+ border-radius: 4px;
203
+ font-size: 12px;
204
+ white-space: nowrap;
205
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
206
+ pointer-events: auto;
207
+ transition: opacity 0.2s;
208
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
209
+ `;
210
+
211
+ if (this.themeType === 'dark') {{
212
+ this.customTooltip.style.backgroundColor = '#1a202c';
213
+ this.customTooltip.style.color = '#ffffff';
214
+ this.customTooltip.style.border = '1px solid #2d3748';
215
+ }} else {{
216
+ this.customTooltip.style.backgroundColor = '#ffffff';
217
+ this.customTooltip.style.color = '#2d3748';
218
+ this.customTooltip.style.border = '1px solid #e2e8f0';
219
+ }}
220
+
221
+ const linkButton = document.createElement('button');
222
+ linkButton.style.cssText = `
223
+ background: none;
224
+ border: none;
225
+ cursor: pointer;
226
+ padding: 4px 8px;
227
+ border-radius: 3px;
228
+ font-size: 12px;
229
+ color: inherit;
230
+ `;
231
+
232
+ if (this.params.tooltipContent) {{
233
+ linkButton.innerHTML = `<span style="margin-right: 4px;">🔗</span>${{this.params.tooltipContent}}`;
234
+ }} else {{
235
+ linkButton.innerHTML = '<span style="margin-right: 4px;">🔗</span>Navigate Link';
236
+ }}
237
+
238
+ linkButton.addEventListener('click', (event) => {{
239
+ event.stopPropagation();
240
+ const url = this.params.linkUrl || '#';
241
+ if (url !== '#' && !this.isDestroyed) {{
242
+ window.open(url, '_blank');
243
+ }}
244
+ this.hideCustomTooltip();
245
+ }});
246
+
247
+ this.customTooltip.appendChild(linkButton);
248
+ document.body.appendChild(this.customTooltip);
249
+ }}
250
+
251
+ showCustomTooltip(event) {{
252
+ if (this.isDestroyed || !this.customTooltip) return;
253
+
254
+ try {{
255
+ const rect = event.currentTarget.getBoundingClientRect();
256
+ const mouseX = event.clientX;
257
+ const mouseY = rect.bottom + 5;
258
+
259
+ this.customTooltip.style.left = mouseX + 'px';
260
+ this.customTooltip.style.top = mouseY + 'px';
261
+ this.customTooltip.style.transform = 'translateX(-50%)';
262
+ this.customTooltip.style.display = 'block';
263
+ }} catch (error) {{
264
+ console.error('Error showing custom tooltip:', error);
265
+ }}
266
+ }}
267
+
268
+ startHideTimeout() {{
269
+ this.cancelHideTimeout();
270
+ this.hideTimeout = setTimeout(() => {{
271
+ this.hideCustomTooltip();
272
+ }}, 300);
273
+ }}
274
+
275
+ cancelHideTimeout() {{
276
+ if (this.hideTimeout) {{
277
+ clearTimeout(this.hideTimeout);
278
+ this.hideTimeout = null;
279
+ }}
280
+ }}
281
+
282
+ hideCustomTooltip() {{
283
+ this.cancelHideTimeout();
284
+ if (this.customTooltip && !this.isDestroyed) {{
285
+ this.customTooltip.style.display = 'none';
286
+ }}
287
+ }}
288
+
289
+ updateSortIcon() {{
290
+ try {{
291
+ const sortState = this.params.column.getSort();
292
+ if (this.sortIcon) {{
293
+ this.sortIcon.innerHTML = '';
294
+ if (sortState === 'asc') {{
295
+ this.sortIcon.innerHTML = '<i class="ag-icon ag-icon-asc"></i>';
296
+ }} else if (sortState === 'desc') {{
297
+ this.sortIcon.innerHTML = '<i class="ag-icon ag-icon-desc"></i>';
298
+ }}
299
+ }}
300
+ }} catch (error) {{
301
+ console.warn('Error updating sort icon:', error);
302
+ }}
303
+ }}
304
+
305
+ destroy() {{
306
+ this.isDestroyed = true;
307
+ this.cancelHideTimeout();
308
+ if (this.customTooltip && this.customTooltip.parentNode) {{
309
+ this.customTooltip.parentNode.removeChild(this.customTooltip);
310
+ }}
311
+ // Clean up event listeners
312
+ if (this.eGui) {{
313
+ this.eGui = null;
314
+ }}
315
+ this.eventListenersBound = false;
316
+ }}
317
+
318
+ refresh(params) {{
319
+ if (!this.isDestroyed) {{
320
+ this.params = params;
321
+ this.updateSortIcon();
322
+ }}
323
+ }}
324
+ }}
325
+ """)
326
+
327
+
328
+ def get_header_tooltip_link_component(theme_type="light"):
329
+ """
330
+ 完整版本 - 完全重写header组件(保留作为备选)
331
+ """
332
+ # print("xxx")
333
+ return JsCode(f"""
334
+ class HeaderTooltipLinkComponent {{
335
+ init(params) {{
336
+ this.params = params;
337
+ this.themeType = "{theme_type}";
338
+ this.isDestroyed = false;
339
+ this.customTooltip = null;
340
+ this.hideTimeout = null;
341
+ this.eGui = null;
342
+ this.isInitialized = false;
343
+ this.eventListenersBound = false; // 添加事件监听器绑定标志
344
+ }}
345
+
346
+ getGui() {{
347
+ if (!this.eGui && !this.isDestroyed) {{
348
+ this.eGui = this.createGui();
349
+ this.isInitialized = true;
350
+ }}
351
+ return this.eGui || document.createElement('div');
352
+ }}
353
+
354
+ createGui() {{
355
+ try {{
356
+ // 🎯 完全重写header组件
357
+ const container = document.createElement('div');
358
+ container.classList.add('ag-cell-label-container');
359
+ container.style.cssText = `
360
+ display: flex;
361
+ align-items: center;
362
+ justify-content: flex-start;
363
+ height: 100%;
364
+ width: 100%;
365
+ `;
366
+
367
+ // 🎯 创建header label
368
+ const headerLabel = document.createElement('div');
369
+ headerLabel.classList.add('ag-header-cell-label');
370
+ headerLabel.style.cssText = `
371
+ display: flex;
372
+ align-items: center;
373
+ justify-content: flex-start;
374
+ height: 100%;
375
+ width: 100%;
376
+ cursor: pointer;
377
+ `;
378
+
379
+ // 🎯 显示列名
380
+ const textSpan = document.createElement('span');
381
+ const displayText = this.params.displayName ||
382
+ (this.params.column && this.params.column.colDef ? this.params.column.colDef.headerName : '') ||
383
+ (this.params.column && this.params.column.colDef ? this.params.column.colDef.field : '') ||
384
+ '';
385
+ textSpan.textContent = displayText;
386
+
387
+ // 🎯 排序图标
388
+ const sortContainer = document.createElement('span');
389
+ sortContainer.classList.add('ag-header-icon', 'ag-header-icon-sort');
390
+ this.sortIcon = document.createElement('span');
391
+ this.updateSortIcon();
392
+ sortContainer.appendChild(this.sortIcon);
393
+
394
+ // 🎯 菜单按钮
395
+ const menuButton = document.createElement('span');
396
+ menuButton.classList.add('ag-header-icon', 'ag-header-cell-menu-button');
397
+ menuButton.innerHTML = '<i class="ag-icon ag-icon-menu"></i>';
398
+
399
+ // 🎯 组装结构
400
+ headerLabel.appendChild(textSpan);
401
+ headerLabel.appendChild(sortContainer);
402
+ headerLabel.appendChild(menuButton);
403
+ container.appendChild(headerLabel);
404
+
405
+ // 🎯 创建tooltip - 只在第一次创建
406
+ if (!this.customTooltip) {{
407
+ this.createCustomTooltip();
408
+ }}
409
+
410
+ // 🎯 事件监听 - 只在第一次绑定
411
+ if (!this.eventListenersBound) {{
412
+ this.setupEventListeners(container, headerLabel, menuButton);
413
+ this.eventListenersBound = true;
414
+ }}
415
+
416
+ return container;
417
+ }} catch (error) {{
418
+ console.error('Error creating header component:', error);
419
+ // 返回一个简单的fallback
420
+ const fallback = document.createElement('div');
421
+ fallback.textContent = this.params.displayName || '';
422
+ return fallback;
423
+ }}
424
+ }}
425
+
426
+ createCustomTooltip() {{
427
+ // 确保tooltip只创建一次
428
+ if (this.customTooltip) return;
429
+
430
+ this.customTooltip = document.createElement('div');
431
+ this.customTooltip.classList.add('header-tooltip-link-container');
432
+ this.customTooltip.style.cssText = `
433
+ position: fixed;
434
+ display: none;
435
+ z-index: 999999;
436
+ padding: 8px 12px;
437
+ border-radius: 4px;
438
+ font-size: 12px;
439
+ white-space: nowrap;
440
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
441
+ pointer-events: auto;
442
+ transition: opacity 0.2s;
443
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
444
+ `;
445
+
446
+ if (this.themeType === 'dark') {{
447
+ this.customTooltip.style.backgroundColor = '#1a202c';
448
+ this.customTooltip.style.color = '#ffffff';
449
+ this.customTooltip.style.border = '1px solid #2d3748';
450
+ }} else {{
451
+ this.customTooltip.style.backgroundColor = '#ffffff';
452
+ this.customTooltip.style.color = '#2d3748';
453
+ this.customTooltip.style.border = '1px solid #e2e8f0';
454
+ }}
455
+
456
+ const linkButton = document.createElement('button');
457
+ linkButton.classList.add('header-tooltip-link-button');
458
+ linkButton.style.cssText = `
459
+ background: none;
460
+ border: none;
461
+ cursor: pointer;
462
+ padding: 4px 8px;
463
+ border-radius: 3px;
464
+ font-size: 12px;
465
+ transition: background-color 0.2s;
466
+ display: flex;
467
+ align-items: center;
468
+ justify-content: center;
469
+ width: 100%;
470
+ color: inherit;
471
+ `;
472
+
473
+ if (this.params.tooltipContent) {{
474
+ linkButton.innerHTML = `<span style="margin-right: 4px;">🔗</span>${{this.params.tooltipContent}}`;
475
+ }} else {{
476
+ linkButton.innerHTML = '<span style="margin-right: 4px;">🔗</span>Navigate Link';
477
+ }}
478
+
479
+ linkButton.addEventListener('click', (event) => {{
480
+ event.stopPropagation();
481
+ const url = this.params.linkUrl || '#';
482
+ if (url !== '#' && !this.isDestroyed) {{
483
+ window.open(url, '_blank');
484
+ }}
485
+ this.hideCustomTooltip();
486
+ }});
487
+
488
+ this.customTooltip.appendChild(linkButton);
489
+ document.body.appendChild(this.customTooltip);
490
+ }}
491
+
492
+ setupEventListeners(container, headerLabel, menuButton) {{
493
+ // 🎯 Tooltip事件
494
+ container.addEventListener('mouseenter', (event) => {{
495
+ if (this.params.linkUrl && this.params.linkUrl !== '#' && !this.isDestroyed) {{
496
+ this.cancelHideTimeout();
497
+ this.showCustomTooltip(event);
498
+ }}
499
+ }});
500
+
501
+ container.addEventListener('mouseleave', () => {{
502
+ if (!this.isDestroyed) {{
503
+ this.startHideTimeout();
504
+ }}
505
+ }});
506
+
507
+ // 🎯 自定义tooltip事件
508
+ if (this.customTooltip) {{
509
+ this.customTooltip.addEventListener('mouseenter', () => {{
510
+ if (!this.isDestroyed) {{
511
+ this.cancelHideTimeout();
512
+ }}
513
+ }});
514
+
515
+ this.customTooltip.addEventListener('mouseleave', () => {{
516
+ if (!this.isDestroyed) {{
517
+ this.hideCustomTooltip();
518
+ }}
519
+ }});
520
+ }}
521
+
522
+ // 🎯 排序功能
523
+ headerLabel.addEventListener('click', (event) => {{
524
+ if (this.params.enableSorting && !this.isDestroyed) {{
525
+ if (this.params.progressSort) {{
526
+ this.params.progressSort();
527
+ }} else if (this.params.column && this.params.column.setSort) {{
528
+ const currentSort = this.params.column.getSort();
529
+ const newSort = currentSort === 'asc' ? 'desc' :
530
+ currentSort === 'desc' ? null : 'asc';
531
+ this.params.column.setSort(newSort);
532
+ }}
533
+ this.updateSortIcon();
534
+ }}
535
+ }});
536
+
537
+ // 🎯 菜单功能
538
+ menuButton.addEventListener('click', (event) => {{
539
+ event.stopPropagation();
540
+ if (!this.isDestroyed && this.params.api) {{
541
+ // 使用更兼容的菜单API
542
+ try {{
543
+ // 尝试使用新的API
544
+ if (this.params.api.showColumnMenuAfterButtonClick) {{
545
+ this.params.api.showColumnMenuAfterButtonClick(
546
+ this.params.column,
547
+ menuButton,
548
+ 'columnMenu'
549
+ );
550
+ }} else if (this.params.api.showColumnMenu) {{
551
+ // 备用API
552
+ this.params.api.showColumnMenu(this.params.column);
553
+ }} else {{
554
+ // 最后的备用方案 - 触发原生菜单事件
555
+ const menuEvent = new MouseEvent('click', {{
556
+ bubbles: true,
557
+ cancelable: true,
558
+ view: window
559
+ }});
560
+ menuButton.dispatchEvent(menuEvent);
561
+ }}
562
+ }} catch (error) {{
563
+ console.warn('Menu API not available:', error);
564
+ // 最后的备用方案 - 触发原生菜单事件
565
+ try {{
566
+ const menuEvent = new MouseEvent('click', {{
567
+ bubbles: true,
568
+ cancelable: true,
569
+ view: window
570
+ }});
571
+ menuButton.dispatchEvent(menuEvent);
572
+ }} catch (e) {{
573
+ console.warn('Fallback menu also failed:', e);
574
+ }}
575
+ }}
576
+ }}
577
+ }});
578
+ }}
579
+
580
+ showCustomTooltip(event) {{
581
+ if (this.isDestroyed || !this.customTooltip) return;
582
+
583
+ try {{
584
+ const rect = event.currentTarget.getBoundingClientRect();
585
+ const mouseX = event.clientX;
586
+ const mouseY = rect.bottom + 5;
587
+
588
+ this.customTooltip.style.left = mouseX + 'px';
589
+ this.customTooltip.style.top = mouseY + 'px';
590
+ this.customTooltip.style.transform = 'translateX(-50%)';
591
+ this.customTooltip.style.display = 'block';
592
+ }} catch (error) {{
593
+ console.error('Error showing custom tooltip:', error);
594
+ }}
595
+ }}
596
+
597
+ startHideTimeout() {{
598
+ this.cancelHideTimeout();
599
+ this.hideTimeout = setTimeout(() => {{
600
+ this.hideCustomTooltip();
601
+ }}, 300);
602
+ }}
603
+
604
+ cancelHideTimeout() {{
605
+ if (this.hideTimeout) {{
606
+ clearTimeout(this.hideTimeout);
607
+ this.hideTimeout = null;
608
+ }}
609
+ }}
610
+
611
+ hideCustomTooltip() {{
612
+ this.cancelHideTimeout();
613
+ if (this.customTooltip && !this.isDestroyed) {{
614
+ this.customTooltip.style.display = 'none';
615
+ }}
616
+ }}
617
+
618
+ updateSortIcon() {{
619
+ try {{
620
+ const sortState = this.params.column.getSort();
621
+ if (this.sortIcon) {{
622
+ this.sortIcon.innerHTML = '';
623
+ if (sortState === 'asc') {{
624
+ this.sortIcon.innerHTML = '<i class="ag-icon ag-icon-asc"></i>';
625
+ }} else if (sortState === 'desc') {{
626
+ this.sortIcon.innerHTML = '<i class="ag-icon ag-icon-desc"></i>';
627
+ }}
628
+ }}
629
+ }} catch (error) {{
630
+ console.warn('Error updating sort icon:', error);
631
+ }}
632
+ }}
633
+
634
+ destroy() {{
635
+ this.isDestroyed = true;
636
+ this.cancelHideTimeout();
637
+ if (this.customTooltip && this.customTooltip.parentNode) {{
638
+ this.customTooltip.parentNode.removeChild(this.customTooltip);
639
+ }}
640
+ // Clean up event listeners
641
+ if (this.eGui) {{
642
+ this.eGui = null;
643
+ }}
644
+ this.eventListenersBound = false;
645
+ }}
646
+
647
+ refresh(params) {{
648
+ if (!this.isDestroyed) {{
649
+ this.params = params;
650
+ this.updateSortIcon();
651
+ }}
652
+ }}
653
+ }}
654
+ """)
app/backend/constant.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from enum import Enum
2
+
3
+
4
+ class Navigation(Enum):
5
+ TEXT_LEADERBOARD = "Text Leaderboard"
6
+ MULTIMODAL_LEADERBOARD = "Multimodal Leaderboard"
7
+
8
+
9
+ class TaskCategory(Enum):
10
+ LAW = "Law"
11
+ CODE = "Code"
12
+ CONVERSATIONAL = "Conversational"
13
+ TECH = "Tech"
14
+ LONG_CONTEXT = "Long-context"
15
+ MULTILINGUAL = "Multilingual"
16
+
17
+
18
+ class ModelProvider(Enum):
19
+ OPENAI = "OpenAI"
20
+ VOYAGEAI = "VoyageAI"
21
+ COHERE = "Cohere"
22
+ OTHERS = "Others"
23
+
24
+
25
+ class EvaluationMetric(Enum):
26
+ NDCG_1 = "NDCG@1"
27
+ NDCG_3 = "NDCG@3"
28
+ NDCG_5 = "NDCG@5"
29
+ NDCG_10 = "NDCG@10"
30
+ NDCG_20 = "NDCG@20"
31
+ NDCG_50 = "NDCG@50"
32
+ NDCG_100 = "NDCG@100"
33
+ RECALL_1 = "RECALL@1"
34
+ RECALL_3 = "RECALL@3"
35
+ RECALL_5 = "RECALL@5"
36
+ RECALL_10 = "RECALL@10"
37
+ RECALL_20 = "RECALL@20"
38
+ RECALL_50 = "RECALL@50"
39
+ RECALL_100 = "RECALL@100"
40
+ PRECISION_1 = "PRECISION@1"
41
+ PRECISION_3 = "PRECISION@3"
42
+ PRECISION_5 = "PRECISION@5"
43
+ PRECISION_10 = "PRECISION@10"
44
+ PRECISION_20 = "PRECISION@20"
45
+ PRECISION_50 = "PRECISION@50"
46
+ PRECISION_100 = "PRECISION@100"
47
+
48
+
49
+ class EmbdDtype(Enum):
50
+ ALL = "all"
51
+ FLOAT_32 = "float32"
52
+ INT_8 = "int8"
53
+ BINARY = "binary"
54
+
55
+
56
+ class EmbdDim(Enum):
57
+ OP1 = "<=1k"
58
+ OP2 = "1k-2k"
59
+ OP3 = "2k-5k"
60
+ OP4 = ">=5k"
61
+
62
+
63
+ class Similarity(Enum):
64
+ ALL = "all"
65
+ COSINE = "cosine"
66
+ DOT = "dot"
67
+ EUCLIDEAN = "euclidean"
68
+
69
+
70
+ LEADERBOARD_ICON_MAP = {
71
+ "Text Leaderboard": "📚",
72
+ "Law": "⚖️",
73
+ "Multilingual": "🌎",
74
+ "German": "🇩🇪",
75
+ "Code": "💻",
76
+ "Tech": "🛠️",
77
+ "Legal": "📜",
78
+ "English": "🇬🇧",
79
+ "Healthcare": "🏥",
80
+ "Finance": "💰",
81
+ "French": "🇫🇷",
82
+ "Japanese": "🇯🇵",
83
+
84
+ }
85
+
86
+
87
+ # USERNAME = "embedding-benchmark"
88
+ # SPACENAME = "RTEB"
89
+ USERNAME = "q275343119"
90
+ SPACENAME = "streamlit-rteb"
91
+ # https://{UserName}-{SpaceName}.hf.space/
92
+ BASE_URL = f"https://{USERNAME}-{SPACENAME}.hf.space/"
app/backend/data_engine.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Data service provider
3
+ """
4
+ import json
5
+ from typing import List
6
+
7
+ import pandas as pd
8
+
9
+ from utils.cache_decorator import cache_df_with_custom_key, cache_dict_with_custom_key
10
+ from utils.http_utils import get
11
+
12
+ COLUMNS = ['model_name',
13
+ 'embd_dtype', 'embd_dim', 'num_params', 'max_tokens', 'similarity',
14
+ 'query_instruct', 'corpus_instruct',
15
+
16
+ ]
17
+ COLUMNS_TYPES = ["markdown",
18
+ 'str', 'str', 'number', 'number', 'str',
19
+ 'str', 'str',
20
+
21
+ ]
22
+
23
+ BRANCH = 'main'
24
+ GIT_URL = f"https://raw.githubusercontent.com/embedding-benchmark/rteb/refs/heads/{BRANCH}/results/"
25
+ DATASET_URL = f"{GIT_URL}datasets.json"
26
+ MODEL_URL = f"{GIT_URL}models.json"
27
+ RESULT_URL = f"{GIT_URL}results.json"
28
+
29
+
30
+ class DataEngine:
31
+
32
+ def __init__(self):
33
+ self.df = self.init_dataframe()
34
+
35
+ @property
36
+ @cache_dict_with_custom_key("models")
37
+ def models(self):
38
+ """
39
+ Get models data
40
+ """
41
+ res = get(MODEL_URL)
42
+ if res.status_code == 200:
43
+ return res.json()
44
+ return {}
45
+
46
+ @property
47
+ @cache_dict_with_custom_key("datasets")
48
+ def datasets(self):
49
+ """
50
+ Get tasks data
51
+ """
52
+ res = get(DATASET_URL)
53
+ if res.status_code == 200:
54
+ return res.json()
55
+ return {}
56
+
57
+ @property
58
+ @cache_dict_with_custom_key("results")
59
+ def results(self):
60
+ """
61
+ Get results data
62
+ """
63
+ res = get(RESULT_URL)
64
+ if res.status_code == 200:
65
+ return res.json()
66
+ return {}
67
+
68
+ def init_dataframe(self):
69
+ """
70
+ Initialize DataFrame
71
+ """
72
+ d = {"hello": [123], "world": [456]}
73
+ return pd.DataFrame(d)
74
+
75
+ @cache_df_with_custom_key("json_result")
76
+ def jsons_to_df(self):
77
+
78
+ results_list = self.results
79
+ df_results_list = []
80
+ for result_dict in results_list:
81
+ dataset_name = result_dict["dataset_name"]
82
+ df_result_row = pd.DataFrame(result_dict["results"])
83
+ df_result_row["dataset_name"] = dataset_name
84
+ df_results_list.append(df_result_row)
85
+ df_result = pd.concat(df_results_list)
86
+
87
+ df_result = df_result[["model_name", "dataset_name", "ndcg_at_10", "embd_dim", "embd_dtype"]]
88
+
89
+ df_result["ndcg_at_10"] = (df_result["ndcg_at_10"] * 100).round(2)
90
+
91
+ df_datasets_list = []
92
+ for item in self.datasets:
93
+ dataset_names = item["datasets"]
94
+ df_dataset_row = pd.DataFrame(
95
+ {
96
+ "group_name": [item["name"] for _ in range(len(dataset_names))],
97
+ "dataset_name": dataset_names,
98
+ "leaderboard": [item["leaderboard"] for _ in range(len(dataset_names))]
99
+ }
100
+ )
101
+ df_datasets_list.append(df_dataset_row)
102
+ df_dataset = pd.concat(df_datasets_list).drop_duplicates()
103
+
104
+ models_list = self.models
105
+
106
+ df_model = pd.DataFrame(models_list)
107
+
108
+ # Create mapping for model names/aliases
109
+ if 'alias' in df_model.columns:
110
+ # Create a lookup table for alias to model_name mapping
111
+ alias_mapping = df_model[df_model['alias'].notna()].set_index('alias')['model_name'].to_dict()
112
+
113
+ # Add rows for aliases to enable joining
114
+ alias_rows = []
115
+ for _, row in df_model[df_model['alias'].notna()].iterrows():
116
+ alias_row = row.copy()
117
+ alias_row['model_name'] = row['alias']
118
+ alias_rows.append(alias_row)
119
+
120
+ if alias_rows:
121
+ df_model_extended = pd.concat([df_model, pd.DataFrame(alias_rows)], ignore_index=True)
122
+ else:
123
+ df_model_extended = df_model
124
+ else:
125
+ df_model_extended = df_model
126
+
127
+ df = pd.merge(df_result, df_dataset, on=["dataset_name"], how="inner")
128
+
129
+ # set dataset default value to 0
130
+ df = df.pivot(index=["model_name", "embd_dim", "embd_dtype", "group_name"], columns="dataset_name",
131
+ values=["ndcg_at_10"]).fillna(0).stack(level=1).reset_index()
132
+ df = pd.merge(df, df_dataset, on=["group_name","dataset_name"], how="inner")
133
+
134
+ # dataset_num_map = {}
135
+ # grouped_dataset_count = df.groupby(["group_name"]).agg({
136
+ # "dataset_name": "nunique"
137
+ # }).reset_index()
138
+ #
139
+ # for _, row in grouped_dataset_count.iterrows():
140
+ # dataset_num_map[row["group_name"]] = row["dataset_name"]
141
+
142
+ grouped_model = df.groupby(["model_name", "group_name", "embd_dim", "embd_dtype"]).agg({
143
+ "ndcg_at_10": "mean",
144
+ }).reset_index()
145
+
146
+ pivot = grouped_model.pivot(index=["model_name", "embd_dim", "embd_dtype"], columns="group_name",
147
+ values=["ndcg_at_10"]).round(2).fillna(0)
148
+
149
+ # Rename columns
150
+ pivot.columns = list(
151
+ map(lambda x: f"{x[1].capitalize()} Average" if x[1] != 'text' else f"Average", pivot.columns))
152
+
153
+ pivot_dataset = df_result.pivot(index=["model_name", "embd_dim", "embd_dtype"], columns="dataset_name", values="ndcg_at_10").fillna(0)
154
+
155
+ df = pd.merge(df_model_extended, pivot, on=["model_name", "embd_dim", "embd_dtype"])
156
+ df = pd.merge(df, pivot_dataset, on=["model_name", "embd_dim", "embd_dtype"])
157
+
158
+ if df.empty:
159
+ return pd.DataFrame(columns=COLUMNS + ["reference"])
160
+ return df
161
+
162
+ def filter_df(self, group_name: str):
163
+ """
164
+ filter_by_providers
165
+ """
166
+ df = self.jsons_to_df()
167
+
168
+ return df[df["group_name"] == group_name][COLUMNS][:]
app/backend/data_page.py ADDED
@@ -0,0 +1,409 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ # @Date : 2025/2/5 16:26
3
+ # @Author : q275343119
4
+ # @File : data_page.py
5
+ # @Description:
6
+ import io
7
+
8
+ from st_aggrid import AgGrid, JsCode, ColumnsAutoSizeMode
9
+
10
+ import streamlit as st
11
+
12
+ from app.backend.data_engine import DataEngine
13
+ from app.backend.multi_header_util import get_header_options, tooltip_renderer
14
+ from utils.st_copy_to_clipboard import st_copy_to_clipboard
15
+ from streamlit_theme import st_theme
16
+
17
+ from app.backend.app_init_func import LEADERBOARD_MAP
18
+ from app.backend.constant import LEADERBOARD_ICON_MAP, BASE_URL
19
+ from app.backend.json_util import compress_msgpack, decompress_msgpack
20
+
21
+ COLUMNS = ['model_name',
22
+ 'embd_dtype', 'embd_dim', 'num_params', 'max_tokens', 'similarity',
23
+ 'query_instruct', 'corpus_instruct', 'reference'
24
+
25
+ ]
26
+ HEADER_STYLE = {'fontSize': '18px'}
27
+ CELL_STYLE = {'fontSize': '18px'}
28
+
29
+
30
+ def is_section(group_name):
31
+ for k, v in LEADERBOARD_MAP.items():
32
+ leaderboard_name = v[0][0]
33
+
34
+ if group_name == leaderboard_name:
35
+ return True
36
+ return False
37
+
38
+
39
+ def get_closed_dataset():
40
+ data_engine = st.session_state.get("data_engine",DataEngine())
41
+ closed_list = []
42
+ results = data_engine.results
43
+ for result in results:
44
+ if result.get("is_closed"):
45
+ closed_list.append(result.get("dataset_name"))
46
+ return closed_list
47
+
48
+
49
+ def convert_df_to_csv(df):
50
+ output = io.StringIO()
51
+ df.to_csv(output, index=False)
52
+ return output.getvalue()
53
+
54
+
55
+ def get_column_state():
56
+ """
57
+ get column state from url
58
+ """
59
+ query_params = st.query_params.get("grid_state", None)
60
+ sider_bar_hidden = st.query_params.get("sider_bar_hidden", "False")
61
+ table_only = st.query_params.get("table_only", "False")
62
+ if query_params:
63
+ grid_state = decompress_msgpack(query_params)
64
+ st.session_state.grid_state = grid_state
65
+ if sider_bar_hidden.upper() == 'FALSE':
66
+ st.session_state.sider_bar_hidden = False
67
+ if table_only.upper() == 'FALSE':
68
+ st.session_state.table_only = False
69
+ return None
70
+
71
+
72
+ def sidebar_css():
73
+ """
74
+
75
+ :return:
76
+ """
77
+ if st.session_state.get("sider_bar_hidden"):
78
+ st.markdown("""
79
+ <style>
80
+ [data-testid="stSidebar"] {
81
+ display: none !important;
82
+ }
83
+ [data-testid="stSidebarNav"] {
84
+ display: none !important;
85
+ }
86
+
87
+ [data-testid="stBaseButton-headerNoPadding"] {
88
+ display: none !important;
89
+ }
90
+
91
+ h1#retrieval-embedding-benchmark-rteb {
92
+ text-align: center;
93
+ }
94
+
95
+ </style>
96
+ """, unsafe_allow_html=True)
97
+
98
+
99
+ def table_only_css():
100
+ if st.session_state.get("table_only"):
101
+ st.markdown("""
102
+ <style>
103
+
104
+ [data-testid="stMainBlockContainer"] {
105
+ padding-top: 0px;
106
+ padding-left: 0px;
107
+ padding-bottom: 0px;
108
+ padding-right: 0px;
109
+ }
110
+
111
+ [data-testid="stHeader"] {
112
+ height: 0px;
113
+ }
114
+
115
+ [data-testid="stApp"] {
116
+ height: 456px;
117
+ }
118
+
119
+ .st-emotion-cache-1dp5vir {
120
+
121
+ height: 0px;
122
+
123
+ }
124
+ </style>
125
+ """, unsafe_allow_html=True)
126
+
127
+
128
+ def table_area(group_name, grid_state, data_engine=None, df=None):
129
+ """
130
+ table_area
131
+ :param group_name:
132
+ :param grid_state:
133
+ :param data_engine:
134
+ :param df:
135
+ :return:
136
+ """
137
+ table_only_css()
138
+
139
+ if data_engine is None:
140
+ data_engine = st.session_state.get("data_engine",DataEngine())
141
+ if df is None:
142
+ df = data_engine.jsons_to_df().copy()
143
+
144
+ # get columns
145
+ column_list = []
146
+ avg_column = None
147
+ if is_section(group_name):
148
+ avg_columns = []
149
+ for column in df.columns:
150
+
151
+ if column.startswith("Average"):
152
+ avg_columns.insert(0, column)
153
+ continue
154
+ if "Average" in column:
155
+ avg_columns.append(column)
156
+ continue
157
+ avg_column = avg_columns[0]
158
+ column_list.extend(avg_columns)
159
+ else:
160
+ for column in df.columns:
161
+
162
+ if column.startswith(group_name.capitalize() + " "):
163
+ avg_column = column
164
+
165
+ column_list.append(avg_column)
166
+
167
+ dataset_list = []
168
+
169
+ for dataset_dict in data_engine.datasets:
170
+ if dataset_dict["name"] == group_name:
171
+ dataset_list = dataset_dict["datasets"]
172
+ if not is_section(group_name):
173
+ column_list.extend(dataset_list)
174
+ closed_list = get_closed_dataset()
175
+ close_avg_list = list(set(dataset_list) & set(closed_list))
176
+ df["Closed average"] = df[close_avg_list].mean(axis=1).round(2)
177
+ column_list.append("Closed average")
178
+
179
+ open_avg_list = list(set(dataset_list) - set(closed_list))
180
+ df["Open average"] = df[open_avg_list].mean(axis=1).round(2)
181
+ column_list.append("Open average")
182
+
183
+ df = df[COLUMNS + column_list].sort_values(by=avg_column, ascending=False)
184
+
185
+ # rename avg column name
186
+ if not is_section(group_name):
187
+ new_column = avg_column.replace(group_name.capitalize(), "").strip()
188
+ df.rename(columns={avg_column: new_column}, inplace=True)
189
+ column_list.remove(avg_column)
190
+ avg_column = new_column
191
+
192
+ # setting column config
193
+ grid_options = st.session_state.get(f"{group_name}_grid_options")
194
+ if grid_options is None:
195
+ grid_options = get_header_options(column_list,avg_column,is_section(group_name))
196
+ st.session_state[f"{group_name}_grid_options"] = grid_options
197
+ grid_options["initialState"] = grid_state
198
+
199
+ custom_css = {
200
+ # Model Name Cell
201
+ ".a-cell": {
202
+ "display": "inline-block",
203
+ "white-space": "nowrap",
204
+ "overflow": "hidden",
205
+ "text-overflow": "ellipsis",
206
+ "width": "100%",
207
+ "min-width": "0"
208
+ },
209
+ # Header
210
+ ".multi-line-header": {
211
+ "text-overflow": "clip",
212
+ "overflow": "visible",
213
+ "white-space": "normal",
214
+ "height": "auto",
215
+ "font-family": 'Arial',
216
+ "font-size": "14px",
217
+ "font-weight": "bold",
218
+ "padding": "10px",
219
+ "text-align": "left",
220
+ },
221
+ # Custom header and cell styles to replace headerStyle and cellStyle
222
+ ".custom-header-style": {
223
+ "text-overflow": "clip",
224
+ "overflow": "visible",
225
+ "white-space": "normal",
226
+ "height": "auto",
227
+ "font-family": 'Arial',
228
+ "font-size": "14px",
229
+ "font-weight": "bold",
230
+ "padding": "10px",
231
+ "text-align": "left",
232
+ },
233
+ ".custom-cell-style": {
234
+ "font-size": "14px",
235
+ "color": "inherit",
236
+ },
237
+ # Filter Options and Input
238
+ ".ag-theme-streamlit .ag-popup": {
239
+ "font-family": 'Arial',
240
+ "font-size": "14px",
241
+
242
+ }
243
+ , ".ag-picker-field-display": {
244
+ "font-family": 'Arial',
245
+ "font-size": "14px",
246
+
247
+ },
248
+ ".ag-input-field-input .ag-text-field-input": {
249
+ "font-family": 'Arial',
250
+ "font-size": "14px",
251
+
252
+ }
253
+
254
+ }
255
+
256
+ grid = AgGrid(
257
+ df,
258
+ enable_enterprise_modules=False,
259
+ gridOptions=grid_options,
260
+ allow_unsafe_jscode=True,
261
+ columns_auto_size_mode=ColumnsAutoSizeMode.FIT_ALL_COLUMNS_TO_VIEW,
262
+ theme="streamlit",
263
+ custom_css=custom_css,
264
+ update_on=["stateUpdated"],
265
+ custom_js_components={"CustomTooltip": tooltip_renderer}
266
+ )
267
+
268
+ return grid
269
+
270
+
271
+ def main_page(group_name, grid_state):
272
+ """
273
+ main_page
274
+ :param group_name:
275
+ :param grid_state:
276
+ :return:
277
+ """
278
+
279
+ # Add theme color and grid styles
280
+ st.title("Retrieval Embedding Benchmark (RTEB)")
281
+ st.markdown("""
282
+ <style>
283
+ :root {
284
+ --theme-color: rgb(129, 150, 64);
285
+ --theme-color-light: rgba(129, 150, 64, 0.2);
286
+ }
287
+
288
+ /* AG Grid specific overrides */
289
+ .ag-theme-alpine {
290
+ --ag-selected-row-background-color: var(--theme-color-light) !important;
291
+ --ag-row-hover-color: var(--theme-color-light) !important;
292
+ --ag-selected-tab-color: var(--theme-color) !important;
293
+ --ag-range-selection-border-color: var(--theme-color) !important;
294
+ --ag-range-selection-background-color: var(--theme-color-light) !important;
295
+ }
296
+
297
+ .ag-row-hover {
298
+ background-color: var(--theme-color-light) !important;
299
+ }
300
+
301
+ .ag-row-selected {
302
+ background-color: var(--theme-color-light) !important;
303
+ }
304
+
305
+ .ag-row-focus {
306
+ background-color: var(--theme-color-light) !important;
307
+ }
308
+
309
+ .ag-cell-focus {
310
+ border-color: var(--theme-color) !important;
311
+ }
312
+
313
+ /* Keep existing styles */
314
+ .center-text {
315
+ text-align: center;
316
+ color: var(--theme-color);
317
+ }
318
+ .center-image {
319
+ display: block;
320
+ margin-left: auto;
321
+ margin-right: auto;
322
+ }
323
+ h2 {
324
+ color: var(--theme-color) !important;
325
+ }
326
+ .ag-header-cell {
327
+ background-color: var(--theme-color) !important;
328
+ color: white !important;
329
+ }
330
+ a {
331
+ color: var(--theme-color) !important;
332
+ }
333
+ a:hover {
334
+ color: rgba(129, 150, 64, 0.8) !important;
335
+ }
336
+ /* Download Button */
337
+ button[data-testid="stBaseButton-secondary"] {
338
+ float: right;
339
+
340
+ }
341
+ /* Toast On The Top*/
342
+ div[data-testid="stToastContainer"] {
343
+ position: fixed !important;
344
+ z-index: 2147483647 !important;
345
+ }
346
+
347
+ </style>
348
+
349
+ """, unsafe_allow_html=True)
350
+
351
+ # logo
352
+ # st.markdown('<img src="https://www.voyageai.com/logo.svg" class="center-image" width="200">', unsafe_allow_html=True)
353
+ title = f'<h2 class="center-text">{LEADERBOARD_ICON_MAP.get(group_name.capitalize(), "")} {group_name.capitalize()}</h2>'
354
+ if is_section(group_name):
355
+ title = f'<h2 class="center-text">{LEADERBOARD_ICON_MAP.get(group_name.capitalize() + " Leaderboard", "")} {group_name.capitalize() + " Leaderboard"}</h2>'
356
+ # title
357
+ st.markdown(title, unsafe_allow_html=True)
358
+
359
+ data_engine = st.session_state.get("data_engine",DataEngine())
360
+
361
+ df = data_engine.jsons_to_df().copy()
362
+
363
+ csv = convert_df_to_csv(df)
364
+ file_name = f"{group_name.capitalize()} Leaderboard" if is_section(group_name) else group_name.capitalize()
365
+ st.download_button(
366
+ label="Download CSV",
367
+ data=csv,
368
+ file_name=f"{file_name}.csv",
369
+ mime="text/csv",
370
+ icon=":material/download:",
371
+ )
372
+
373
+ grid = table_area(group_name, grid_state, data_engine, df)
374
+
375
+ @st.dialog("URL")
376
+ def share_url():
377
+ state = grid.grid_state
378
+ if state:
379
+ share_link = f'{BASE_URL.replace("_", "-")}{group_name}/?grid_state={compress_msgpack(state)}' if not is_section(
380
+ group_name) else f'{BASE_URL.replace("_", "-")}?grid_state={compress_msgpack(state)}'
381
+ else:
382
+ share_link = f'{BASE_URL.replace("_", "-")}{group_name}'
383
+ st.write(share_link)
384
+ theme = st_theme()
385
+ if theme:
386
+ theme = theme.get("base")
387
+ else:
388
+ theme = "light"
389
+ st_copy_to_clipboard(share_link, before_copy_label='📋Push to copy', after_copy_label='✅Text copied!',
390
+ theme=theme)
391
+
392
+ share_btn = st.button("Share this page", icon=":material/share:")
393
+
394
+ if share_btn:
395
+ share_url()
396
+
397
+
398
+ def render_page(group_name):
399
+ grid_state = st.session_state.get("grid_state", {})
400
+ st.session_state.sider_bar_hidden = True
401
+ st.session_state.table_only = True
402
+ get_column_state()
403
+
404
+ sidebar_css()
405
+
406
+ if st.session_state.get("table_only"):
407
+ table_area(group_name, grid_state)
408
+ else:
409
+ main_page(group_name, grid_state)
app/backend/json_util.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import msgpack
2
+ import gzip
3
+ import base64
4
+
5
+
6
+ def compress_msgpack(data):
7
+ packed = msgpack.packb(data)
8
+ compressed = gzip.compress(packed)
9
+ return base64.urlsafe_b64encode(compressed).decode('utf-8')
10
+
11
+
12
+ def decompress_msgpack(compressed_str):
13
+ compressed = base64.urlsafe_b64decode(compressed_str)
14
+ unpacked = gzip.decompress(compressed)
15
+ return msgpack.unpackb(unpacked, raw=False)
app/backend/multi_header_util.py ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ handle multi_header options
3
+ """
4
+ from st_aggrid import JsCode
5
+ from streamlit_theme import st_theme
6
+
7
+ from .component.header_tooltip_link_component import get_header_tooltip_link_component_simple
8
+
9
+ HEADER_STYLE = {'fontSize': '18px'}
10
+ CELL_STYLE = {'fontSize': '18px'}
11
+ LINK = ' https://huggingface.co/datasets/embedding-benchmark/'
12
+
13
+ tooltip_renderer = JsCode("""
14
+ class CustomTooltip {
15
+ init(params) {
16
+ const value = params.value || params.colDef.headerName;
17
+ const url = params.colDef.tooltipLink || "https://example.com";
18
+ this.eGui = document.createElement('div');
19
+ this.eGui.innerHTML = `
20
+ <a href="${url}" target="_blank" style="text-decoration: underline; color: #1a73e8;">
21
+ ${value}
22
+ </a>
23
+ `;
24
+ }
25
+ getGui() {
26
+ return this.eGui;
27
+ }
28
+ }
29
+ """)
30
+
31
+ def mutil_header_options(column_list: list, avg_column: str, is_section: bool):
32
+ """
33
+ get mutil_header_options
34
+ :param column_list:
35
+ :param avg_column:
36
+ :param is_section:
37
+ :return:
38
+ """
39
+ if is_section:
40
+ column_def_list = [
41
+ {'headerName': column if "Average" not in column else column.replace("Average", "").strip().capitalize(),
42
+ 'field': column,
43
+ 'headerClass': 'custom-header-style',
44
+ 'cellClass': 'custom-cell-style',
45
+ 'headerTooltip': column if "Average" not in column else column.replace("Average",
46
+ "").strip().capitalize()
47
+ # 'suppressSizeToFit': True
48
+ } for column in column_list if
49
+ column not in (avg_column, "Closed average", "Open average")]
50
+
51
+ return column_def_list
52
+
53
+ mutil_column_list = [column for column in column_list if
54
+ column not in (avg_column, "Closed average", "Open average")]
55
+ close_group_list = list(filter(lambda x: x.startswith('_'), mutil_column_list))
56
+ open_group_list = list(filter(lambda x: not x.startswith('_'), mutil_column_list))
57
+
58
+ theme = st_theme(key="st_theme_1")
59
+ if theme:
60
+ current_theme = theme.get("base", "light")
61
+ else:
62
+ current_theme = "light"
63
+
64
+ close_group_def = {
65
+ 'headerName': 'CLOSED DATASETS',
66
+ 'children': [
67
+ {'headerName': column.replace('_', ''),
68
+ 'field': column,
69
+ 'headerComponent': get_header_tooltip_link_component_simple(current_theme),
70
+ 'headerComponentParams': {
71
+ "linkUrl": LINK + column.replace('_', ''),
72
+ "tooltipContent": column.replace('_', ''),
73
+ "enableSorting": True,
74
+ "enableFiltering": True,
75
+ },
76
+
77
+ 'headerClass': 'custom-header-style',
78
+ 'cellClass': 'custom-cell-style',
79
+ 'sortable': True,
80
+ 'width': 105,
81
+ # "suppressColumnVirtualisation": True,
82
+
83
+ } for column in close_group_list
84
+ ],
85
+
86
+ }
87
+ open_group_def = {
88
+ 'headerName': 'OPEN DATASETS',
89
+ 'children': [
90
+ {'headerName': column,
91
+ 'field': column,
92
+ 'headerComponent': get_header_tooltip_link_component_simple(current_theme),
93
+ 'headerComponentParams': {
94
+ "linkUrl": LINK + column,
95
+ "tooltipContent": column,
96
+ "enableSorting": True,
97
+ "enableFiltering": True,
98
+ },
99
+ 'headerClass': 'custom-header-style',
100
+ 'cellClass': 'custom-cell-style',
101
+ 'sortable': True,
102
+ 'width': 105,
103
+ "suppressColumnVirtualisation": True,
104
+
105
+ } for column in open_group_list
106
+ ],
107
+
108
+ }
109
+ return [close_group_def, open_group_def]
110
+
111
+
112
+ def get_header_options(column_list: list, avg_column: str, is_section: bool):
113
+ grid_options = {
114
+ 'columnDefs': [
115
+ {
116
+ 'headerName': 'Model Name',
117
+ 'field': 'model_name',
118
+ 'pinned': 'left',
119
+ 'sortable': False,
120
+ 'headerClass': 'custom-header-style',
121
+ 'cellClass': 'custom-cell-style',
122
+ 'autoHeaderHeight': True,
123
+ "tooltipValueGetter": JsCode(
124
+ """function(p) {return p.value}"""
125
+ ),
126
+ "width": 250,
127
+ 'cellRenderer': JsCode("""class CustomHTML {
128
+ init(params) {
129
+ const link = params.data.reference;
130
+ this.eGui = document.createElement('div');
131
+ this.eGui.innerHTML = link ?
132
+ `<a href="${link}" class="a-cell" target="_blank">${params.value} </a>` :
133
+ params.value;
134
+ }
135
+
136
+ getGui() {
137
+ return this.eGui;
138
+ }
139
+ }"""),
140
+ 'suppressSizeToFit': True
141
+
142
+ },
143
+ {'headerName': "Overall Score",
144
+ 'field': avg_column,
145
+ 'headerClass': 'custom-header-style',
146
+ 'cellClass': 'custom-cell-style',
147
+ # 'suppressSizeToFit': True
148
+ },
149
+
150
+ # Add Open average column definition
151
+ {'headerName': 'Open Average',
152
+ 'field': 'Open average',
153
+ 'headerClass': 'custom-header-style',
154
+ 'cellClass': 'custom-cell-style',
155
+ # 'suppressSizeToFit': True
156
+ },
157
+
158
+ {'headerName': 'Closed Average',
159
+ 'field': 'Closed average',
160
+ 'headerClass': 'custom-header-style',
161
+ 'cellClass': 'custom-cell-style',
162
+ # 'suppressSizeToFit': True
163
+ },
164
+
165
+ {
166
+ 'headerName': 'Embd Dtype',
167
+ 'field': 'embd_dtype',
168
+ 'headerClass': 'custom-header-style',
169
+ 'cellClass': 'custom-cell-style',
170
+ # 'suppressSizeToFit': True,
171
+ },
172
+ {
173
+ 'headerName': 'Embd Dim',
174
+ 'field': 'embd_dim',
175
+ 'headerClass': 'custom-header-style',
176
+ 'cellClass': 'custom-cell-style',
177
+ # 'suppressSizeToFit': True,
178
+ },
179
+ {
180
+ 'headerName': 'Number of Parameters',
181
+ 'field': 'num_params',
182
+ 'cellDataType': 'number',
183
+ "colId": "num_params",
184
+ 'headerClass': 'custom-header-style',
185
+ 'cellClass': 'custom-cell-style',
186
+ 'valueFormatter': JsCode(
187
+ """function(params) {
188
+ const num = params.value;
189
+ if (num >= 1e9) return (num / 1e9).toFixed(2) + "B";
190
+ if (num >= 1e6) return (num / 1e6).toFixed(2) + "M";
191
+ if (num >= 1e3) return (num / 1e3).toFixed(2) + "K";
192
+ return num;
193
+ }"""
194
+ ),
195
+ "width": 120,
196
+ # 'suppressSizeToFit': True,
197
+ },
198
+ {
199
+ 'headerName': 'Context Length',
200
+ 'field': 'max_tokens',
201
+ 'headerClass': 'custom-header-style',
202
+ 'cellClass': 'custom-cell-style',
203
+ # 'suppressSizeToFit': True,
204
+ },
205
+
206
+ *mutil_header_options(column_list, avg_column, is_section)
207
+ ],
208
+ 'defaultColDef': {
209
+ 'filter': True,
210
+ 'sortable': True,
211
+ 'resizable': True,
212
+ 'headerClass': "multi-line-header",
213
+ 'autoHeaderHeight': True,
214
+ 'width': 105
215
+ },
216
+
217
+ "autoSizeStrategy": {
218
+ "type": 'fitCellContents',
219
+ "colIds": [column for column in column_list if column not in (avg_column, "Closed average", "Open average")]
220
+ },
221
+ "tooltipShowDelay": 500,
222
+
223
+ }
224
+
225
+ return grid_options
app/ui/__init__.py ADDED
File without changes
app/ui/pages/__init__.py ADDED
File without changes
app/ui/pages/data_page.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from app.backend.data_page import render_page
2
+
3
+ render_page("$group_name$")
docker-compose.yml ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ streamlit-app:
5
+ build: .
6
+ ports:
7
+ - "8501:8501"
8
+ environment:
9
+ - PYTHONPATH=/app
10
+ - STREAMLIT_SERVER_PORT=8501
11
+ - STREAMLIT_SERVER_ADDRESS=0.0.0.0
12
+ volumes:
13
+ # 可选:挂载本地目录用于开发调试
14
+ # - ./app:/app/app
15
+ # - ./utils:/app/utils
16
+ # - ./app.py:/app/app.py
17
+ restart: unless-stopped
18
+ healthcheck:
19
+ test: ["CMD", "curl", "-f", "http://localhost:8501/_stcore/health"]
20
+ interval: 30s
21
+ timeout: 10s
22
+ retries: 3
23
+ start_period: 40s
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ streamlit==1.41.1
2
+ streamlit-aggrid==1.1.5
3
+ st-pages==1.0.1
4
+ msgpack==1.1.0
5
+ zstandard==0.23.0
6
+ st-theme==1.2.3
utils/__init__.py ADDED
File without changes
utils/cache_decorator.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+ from functools import wraps
3
+ import pandas as pd
4
+
5
+ CACHE = {}
6
+ TTL = 3600
7
+
8
+
9
+ def cache_df_with_custom_key(cache_key: str):
10
+ def decorator(func):
11
+ @wraps(func)
12
+ def wrapper(*args, **kwargs):
13
+ if cache_key in CACHE and CACHE[cache_key].get("expiry") - time.time() < TTL:
14
+ return CACHE[cache_key]["data"]
15
+
16
+ result: pd.DataFrame = func(*args, **kwargs)
17
+ if result is not None and not result.empty:
18
+ d = {"expiry": time.time(), "data": result}
19
+ CACHE[cache_key] = d
20
+ return result
21
+
22
+ CACHE[cache_key]["expiry"] += TTL
23
+ return CACHE[cache_key]["data"]
24
+
25
+ return wrapper
26
+
27
+ return decorator
28
+
29
+
30
+ def cache_dict_with_custom_key(cache_key: str):
31
+ def decorator(func):
32
+ @wraps(func)
33
+ def wrapper(*args, **kwargs):
34
+ if cache_key in CACHE and time.time() - CACHE[cache_key].get("expiry") < TTL:
35
+ return CACHE[cache_key]["data"]
36
+
37
+ result: dict = func(*args, **kwargs)
38
+ if result:
39
+ d = {"expiry": time.time(), "data": result}
40
+ CACHE[cache_key] = d
41
+ return result
42
+
43
+ CACHE[cache_key]["expiry"] += TTL
44
+ return CACHE[cache_key]["data"]
45
+
46
+ return wrapper
47
+
48
+ return decorator
49
+
50
+
51
+ if __name__ == '__main__':
52
+ a = time.time()
53
+ time.sleep(5)
54
+ print(time.time() - a)
utils/http_utils.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import requests
2
+
3
+
4
+ def get(url: str, params: str = None, verify: bool = False):
5
+ return requests.get(url, params, verify=verify)
6
+
7
+
utils/st_copy_to_clipboard/__init__.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ from typing import Optional
3
+
4
+ import streamlit as st
5
+ import streamlit.components.v1 as components
6
+
7
+ # Tell streamlit that there is a component called streamlit_copy_to_clipboard,
8
+ # and that the code to display that component is in the "frontend" folder
9
+ frontend_dir = (Path(__file__).parent / "frontend").absolute()
10
+ _component_func = components.declare_component(
11
+ "streamlit_copy_to_clipboard", path=str(frontend_dir)
12
+ )
13
+
14
+
15
+ def st_copy_to_clipboard(
16
+ text: str,
17
+ before_copy_label: str = "📋",
18
+ after_copy_label: str = "✅",
19
+ show_text: bool = False,
20
+ key: Optional[str] = None,
21
+ theme: str = 'light', # default theme is 'light'
22
+
23
+ ):
24
+ """
25
+ Streamlit component to copy text to clipboard.
26
+
27
+ Parameters
28
+ ----------
29
+ text : str
30
+ The text to be copied to the clipboard.
31
+ before_copy_label : str
32
+ Label of the button before text is copied.
33
+ after_copy_label : str
34
+ Label of the button after text is copied.
35
+ show_text: bool
36
+ If True, show text right before the button and make it clickable as well
37
+ key : str or None
38
+ An optional key that uniquely identifies the component.
39
+ theme: str
40
+ Set the current theme for the button.
41
+ """
42
+ component_value = _component_func(
43
+ key=key,
44
+ text=text,
45
+ before_copy_label=before_copy_label,
46
+ after_copy_label=after_copy_label,
47
+ show_text=show_text,
48
+ theme=theme,
49
+ )
50
+
51
+ return component_value
52
+
53
+
54
+ def main():
55
+ st.write("## Example")
56
+ text = st.text_input("Enter text to copy to clipboard", value="Hello World")
57
+ st_copy_to_clipboard(text)
58
+ st_copy_to_clipboard(text, before_copy_label='📋Push to copy', after_copy_label='✅Text copied!')
59
+ st_copy_to_clipboard(text, before_copy_label='Push to copy', after_copy_label='Text copied!', show_text=True)
60
+ st_copy_to_clipboard(text, before_copy_label='Push to copy', after_copy_label='copied!', show_text=True, theme="dark")
61
+
62
+
63
+
64
+
65
+ if __name__ == "__main__":
66
+ main()
utils/st_copy_to_clipboard/frontend/index.html ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>st-copy-to-clipboard</title>
8
+ <script src="./streamlit-component-lib.js"></script>
9
+ <script src="./main.js"></script>
10
+ <link rel="stylesheet" href="./style.css" />
11
+ </head>
12
+
13
+ <body>
14
+ <div id="root">
15
+ <button id="text-element" class="st-copy-to-clipboard-btn"></button>
16
+ <button id="copy-button" class="st-copy-to-clipboard-btn">📋</button>
17
+ </div>
18
+ </body>
19
+ </html>
utils/st_copy_to_clipboard/frontend/main.js ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // The `Streamlit` object exists because our html file includes
2
+ // `streamlit-component-lib.js`.
3
+ // If you get an error about "Streamlit" not being defined, that
4
+ // means you're missing that file.
5
+
6
+ function sendValue(value) {
7
+ Streamlit.setComponentValue(value);
8
+ }
9
+
10
+ /**
11
+ * The component's render function. This will be called immediately after
12
+ * the component is initially loaded, and then again every time the
13
+ * component gets new data from Python.
14
+ */
15
+ function onRender(event) {
16
+ // Only run the render code the first time the component is loaded.
17
+ if (!window.rendered) {
18
+ const { text, before_copy_label, after_copy_label, show_text, theme } = event.detail.args;
19
+
20
+ const container = document.querySelector('#container');
21
+ const button = document.querySelector('#copy-button');
22
+ const textElement = document.querySelector('#text-element');
23
+
24
+ if (theme == 'dark') {
25
+ button.style.border = '1px solid rgba(250, 250, 250, 0.2)';
26
+ button.style.color = 'white';
27
+ }
28
+
29
+ button.textContent = before_copy_label; // Set initial label
30
+
31
+ // Show text if show_text is true
32
+ if (show_text) {
33
+ textElement.textContent = text;
34
+ textElement.style.display = 'inline';
35
+ } else {
36
+ textElement.style.display = 'none';
37
+ }
38
+
39
+ function copyToClipboard() {
40
+ navigator.clipboard.writeText(text);
41
+
42
+ button.textContent = after_copy_label; // Change label after copying
43
+
44
+ setTimeout(() => {
45
+ if (!button) return;
46
+ button.textContent = before_copy_label; // Revert to original label after 1 second
47
+ }, 1000);
48
+ }
49
+ button.addEventListener('click', copyToClipboard);
50
+ textElement.addEventListener('click', copyToClipboard);
51
+
52
+ window.rendered = true;
53
+ }
54
+ }
55
+
56
+ // Render the component whenever python send a "render event"
57
+ Streamlit.events.addEventListener(Streamlit.RENDER_EVENT, onRender);
58
+ // Tell Streamlit that the component is ready to receive events
59
+ Streamlit.setComponentReady();
60
+ // Render with the correct height, if this is a fixed-height component
61
+ Streamlit.setFrameHeight(100);
utils/st_copy_to_clipboard/frontend/streamlit-component-lib.js ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Borrowed minimalistic Streamlit API from Thiago
2
+ // https://discuss.streamlit.io/t/code-snippet-create-components-without-any-frontend-tooling-no-react-babel-webpack-etc/13064
3
+ function sendMessageToStreamlitClient(type, data) {
4
+ console.log(type, data);
5
+ const outData = Object.assign(
6
+ {
7
+ isStreamlitMessage: true,
8
+ type: type,
9
+ },
10
+ data
11
+ );
12
+ window.parent.postMessage(outData, '*');
13
+ }
14
+
15
+ const Streamlit = {
16
+ setComponentReady: function () {
17
+ sendMessageToStreamlitClient('streamlit:componentReady', { apiVersion: 1 });
18
+ },
19
+ setFrameHeight: function (height) {
20
+ sendMessageToStreamlitClient('streamlit:setFrameHeight', { height: height });
21
+ },
22
+ setComponentValue: function (value) {
23
+ sendMessageToStreamlitClient('streamlit:setComponentValue', { value: value });
24
+ },
25
+ RENDER_EVENT: 'streamlit:render',
26
+ events: {
27
+ addEventListener: function (type, callback) {
28
+ window.addEventListener('message', function (event) {
29
+ if (event.data.type === type) {
30
+ event.detail = event.data;
31
+ callback(event);
32
+ }
33
+ });
34
+ },
35
+ },
36
+ };
utils/st_copy_to_clipboard/frontend/style.css ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .st-copy-to-clipboard-btn {
2
+ display: inline-flex;
3
+ -moz-box-align: center;
4
+ align-items: center;
5
+ -moz-box-pack: center;
6
+ justify-content: center;
7
+ font-weight: 400;
8
+ padding: 0.25rem 0.75rem;
9
+ border-radius: 0.5rem;
10
+ min-height: 38.4px;
11
+ margin: 0px;
12
+ line-height: 1.6;
13
+ color: inherit;
14
+ width: auto;
15
+ user-select: none;
16
+ background-color: transparent; /* set bgcolor to transparent to adjust to any theme */
17
+ border: 1px solid rgba(49, 51, 63, 0.2);
18
+ cursor: pointer;
19
+ float: right;
20
+ }
21
+
22
+ .st-copy-to-clipboard-btn:hover {
23
+ border-color: rgb(255, 75, 75);
24
+ color: rgb(255, 75, 75);
25
+ }
26
+
27
+ .st-copy-to-clipboard-btn:active {
28
+ border-color: rgb(255, 75, 75);
29
+ background-color: rgb(255, 75, 75);
30
+ color: rgb(255, 255, 255);
31
+ }