Spaces:
Sleeping
Sleeping
Upload 4 files
Browse files
app.py
ADDED
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import io
|
3 |
+
from streamlit_ace import st_ace
|
4 |
+
from utils import convert_file
|
5 |
+
|
6 |
+
# Language dictionary
|
7 |
+
LANG = {
|
8 |
+
'en': {
|
9 |
+
'title': "📊 Jupytext Demo App",
|
10 |
+
'info': "This app demonstrates the main features of Jupytext. "
|
11 |
+
"You can convert between Python (.py), Jupyter Notebook (.ipynb), and Markdown (.md) files. "
|
12 |
+
"Try different conversion options to experience the flexibility of Jupytext.",
|
13 |
+
'upload_header': "📁 File Upload",
|
14 |
+
'upload_label': "Choose a file to convert",
|
15 |
+
'file_uploaded': "✅ File successfully uploaded!",
|
16 |
+
'conversion_options': "🛠️ Conversion Options",
|
17 |
+
'output_format': "Output Format",
|
18 |
+
'py_format': "Python File Format",
|
19 |
+
'py_format_help': "percent: Use Jupyter cell markers, light: Simple markers, nomarker: No markers",
|
20 |
+
'comment_magics': "Comment out magic commands",
|
21 |
+
'comment_magics_help': "Only applies to Python output",
|
22 |
+
'convert_button': "🔄 Convert",
|
23 |
+
'conversion_success': "✅ Conversion successful!",
|
24 |
+
'download_button': "📥 Download Converted File",
|
25 |
+
'preview_header': "👀 Conversion Result Preview",
|
26 |
+
'conversion_error': "❌ An error occurred during conversion: ",
|
27 |
+
'about_header': "ℹ️ About Jupytext",
|
28 |
+
'about_text': ("Jupytext is a tool for converting between Jupyter Notebooks and various text-based formats "
|
29 |
+
"(Python scripts, Markdown documents, etc.). Main features include:\n\n"
|
30 |
+
"- Read and write Jupyter Notebooks (.ipynb).\n"
|
31 |
+
"- Read and write Python scripts (.py) in Jupyter Notebook format.\n"
|
32 |
+
"- Read and write Markdown files (.md) in Jupyter Notebook format.\n"
|
33 |
+
"- Support multiple Python script formats (percent, light, nomarker, etc.).\n"
|
34 |
+
"- Advanced conversion options like magic command processing and metadata retention.\n\n"
|
35 |
+
"This demo app lets you experience Jupytext's basic conversion features. For more detailed information "
|
36 |
+
"and advanced usage, please refer to the "
|
37 |
+
"<a href='https://jupytext.readthedocs.io/' target='_blank'>official Jupytext documentation</a>."),
|
38 |
+
'usage_header': "📝 How to Use",
|
39 |
+
'usage_text': """
|
40 |
+
1. Upload a file (.py, .ipynb, .md) you want to convert.
|
41 |
+
2. Select the desired output format.
|
42 |
+
3. For Python files, choose the format (percent, light, nomarker).
|
43 |
+
4. Set additional options if needed.
|
44 |
+
5. Click the "Convert" button.
|
45 |
+
6. Download the converted file or check the content in the preview.
|
46 |
+
|
47 |
+
Try various input files and settings to explore Jupytext's capabilities!
|
48 |
+
"""
|
49 |
+
},
|
50 |
+
'ja': {
|
51 |
+
'title': "📊 Jupytext デモアプリ",
|
52 |
+
'info': "このアプリは Jupytext の主要な機能をデモンストレーションします。"
|
53 |
+
"Python (.py)、Jupyter Notebook (.ipynb)、Markdown (.md) ファイルを相互に変換できます。"
|
54 |
+
"さまざまな変換オプションを試して、Jupytext の柔軟性を体験してください。",
|
55 |
+
'upload_header': "📁 ファイルアップロード",
|
56 |
+
'upload_label': "変換するファイルを選択してください",
|
57 |
+
'file_uploaded': "✅ ファイルが正常にアップロードされました!",
|
58 |
+
'conversion_options': "🛠️ 変換オプション",
|
59 |
+
'output_format': "出力形式",
|
60 |
+
'py_format': "Python ファイルの形式",
|
61 |
+
'py_format_help': "percent: Jupyter の cell マーカーを使用, light: シンプルなマーカー, nomarker: マーカーなし",
|
62 |
+
'comment_magics': "マジックコマンドをコメントアウト",
|
63 |
+
'comment_magics_help': "Python出力の場合のみ適用されます",
|
64 |
+
'convert_button': "🔄 変換",
|
65 |
+
'conversion_success': "✅ 変換が成功しました!",
|
66 |
+
'download_button': "📥 変換されたファイルをダウンロード",
|
67 |
+
'preview_header': "👀 変換結果プレビュー",
|
68 |
+
'conversion_error': "❌ 変換中にエラーが発生しました: ",
|
69 |
+
'about_header': "ℹ️ Jupytext について",
|
70 |
+
'about_text': ("Jupytext は Jupyter Notebooks と様々なテキストベースの形式(Python スクリプト、Markdown ドキュメントなど)の間で"
|
71 |
+
"変換を行うツールです。主な特徴は以下の通りです:\n\n"
|
72 |
+
"- Jupyter Notebooks (.ipynb) を読み書きできます。\n"
|
73 |
+
"- Python スクリプト (.py) を Jupyter Notebooks 形式で読み書きできます。\n"
|
74 |
+
"- Markdown ファイル (.md) を Jupyter Notebooks 形式で読み書きできます。\n"
|
75 |
+
"- 複数の Python スクリプト形式をサポートしています(percent、light、nomarker など)。\n"
|
76 |
+
"- マジックコマンドの処理やメタデータの保持など、高度な変換オプションを提供します。\n\n"
|
77 |
+
"このデモアプリでは、Jupytext の基本的な変換機能を体験できます。より詳細な情報や高度な使用方法については、"
|
78 |
+
"<a href='https://jupytext.readthedocs.io/' target='_blank'>Jupytext の公式ドキュメント</a> を参照してください。"),
|
79 |
+
'usage_header': "📝 使用方法",
|
80 |
+
'usage_text': """
|
81 |
+
1. 変換したいファイル (.py, .ipynb, .md) をアップロードします。
|
82 |
+
2. 目的の出力形式を選択します。
|
83 |
+
3. Python ファイルの場合、形式(percent、light、nomarker)を選択します。
|
84 |
+
4. 必要に応じて、追加のオプションを設定します。
|
85 |
+
5. 「変換」ボタンをクリックします。
|
86 |
+
6. 変換されたファイルをダウンロードするか、プレビューで内容を確認します。
|
87 |
+
|
88 |
+
さまざまな入力ファイルと設定を試して、Jupytext の機能を探索してください!
|
89 |
+
"""
|
90 |
+
}
|
91 |
+
}
|
92 |
+
|
93 |
+
# ページ設定
|
94 |
+
st.set_page_config(page_title="Jupytext Demo App", page_icon="📊", layout="wide")
|
95 |
+
|
96 |
+
# スタイルシートの読み込み
|
97 |
+
with open('style.css') as f:
|
98 |
+
st.markdown(f'<style>{f.read()}</style>', unsafe_allow_html=True)
|
99 |
+
|
100 |
+
# 言語選択
|
101 |
+
lang = st.selectbox("Language / 言語", ["English", "日本語"])
|
102 |
+
lang_code = 'en' if lang == "English" else 'ja'
|
103 |
+
|
104 |
+
# メインアプリ
|
105 |
+
st.markdown(f"<h1 class='main-header'>{LANG[lang_code]['title']}</h1>", unsafe_allow_html=True)
|
106 |
+
|
107 |
+
st.markdown(f"""
|
108 |
+
<div class="info-box">
|
109 |
+
{LANG[lang_code]['info']}
|
110 |
+
</div>
|
111 |
+
""", unsafe_allow_html=True)
|
112 |
+
|
113 |
+
st.markdown(f"<h2 class='sub-header'>{LANG[lang_code]['upload_header']}</h2>", unsafe_allow_html=True)
|
114 |
+
|
115 |
+
col1, col2 = st.columns([2, 1])
|
116 |
+
|
117 |
+
with col1:
|
118 |
+
uploaded_file = st.file_uploader(LANG[lang_code]['upload_label'], type=["py", "ipynb", "md"])
|
119 |
+
|
120 |
+
with col2:
|
121 |
+
st.markdown("<br>", unsafe_allow_html=True) # 空白を追加してアライメントを調整
|
122 |
+
if uploaded_file is not None:
|
123 |
+
st.markdown(f"<div class='success-box'>{LANG[lang_code]['file_uploaded']}</div>", unsafe_allow_html=True)
|
124 |
+
|
125 |
+
if uploaded_file is not None:
|
126 |
+
st.markdown(f"<h2 class='sub-header'>{LANG[lang_code]['conversion_options']}</h2>", unsafe_allow_html=True)
|
127 |
+
|
128 |
+
col1, col2, col3 = st.columns(3)
|
129 |
+
|
130 |
+
with col1:
|
131 |
+
output_format = st.selectbox(
|
132 |
+
LANG[lang_code]['output_format'],
|
133 |
+
["py", "ipynb", "md"],
|
134 |
+
format_func=lambda x: f".{x}"
|
135 |
+
)
|
136 |
+
|
137 |
+
with col2:
|
138 |
+
py_format = st.selectbox(
|
139 |
+
LANG[lang_code]['py_format'],
|
140 |
+
["percent", "light", "nomarker"],
|
141 |
+
help=LANG[lang_code]['py_format_help']
|
142 |
+
)
|
143 |
+
|
144 |
+
with col3:
|
145 |
+
comment_magics = st.checkbox(LANG[lang_code]['comment_magics'], value=True, help=LANG[lang_code]['comment_magics_help'])
|
146 |
+
|
147 |
+
config = {
|
148 |
+
"py_format": py_format,
|
149 |
+
"comment_magics": comment_magics
|
150 |
+
}
|
151 |
+
|
152 |
+
if st.button(LANG[lang_code]['convert_button'], key="convert_button"):
|
153 |
+
try:
|
154 |
+
converted_content, output_filename = convert_file(uploaded_file, output_format, config)
|
155 |
+
|
156 |
+
st.markdown(f"<div class='success-box'>{LANG[lang_code]['conversion_success']}</div>", unsafe_allow_html=True)
|
157 |
+
|
158 |
+
st.download_button(
|
159 |
+
label=LANG[lang_code]['download_button'],
|
160 |
+
data=converted_content,
|
161 |
+
file_name=output_filename,
|
162 |
+
mime="application/octet-stream"
|
163 |
+
)
|
164 |
+
|
165 |
+
st.markdown(f"<h2 class='sub-header'>{LANG[lang_code]['preview_header']}</h2>", unsafe_allow_html=True)
|
166 |
+
preview = io.StringIO(converted_content.decode('utf-8')).getvalue()
|
167 |
+
|
168 |
+
# エディタでプレビューを表示
|
169 |
+
st_ace(value=preview, language=output_format, theme="tomorrow", key="preview_editor")
|
170 |
+
|
171 |
+
except Exception as e:
|
172 |
+
st.markdown(f"<div class='error-box'>{LANG[lang_code]['conversion_error']}{str(e)}</div>", unsafe_allow_html=True)
|
173 |
+
|
174 |
+
st.markdown(f"<h2 class='sub-header'>{LANG[lang_code]['about_header']}</h2>", unsafe_allow_html=True)
|
175 |
+
st.markdown(f"""
|
176 |
+
<div class="info-box">
|
177 |
+
{LANG[lang_code]['about_text']}
|
178 |
+
</div>
|
179 |
+
""", unsafe_allow_html=True)
|
180 |
+
|
181 |
+
st.markdown(f"<h2 class='sub-header'>{LANG[lang_code]['usage_header']}</h2>", unsafe_allow_html=True)
|
182 |
+
st.markdown(LANG[lang_code]['usage_text'])
|
requirements.txt
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
beautifulsoup4==4.9.3
|
2 |
+
markdown
|
3 |
+
PyGithub
|
4 |
+
loguru
|
5 |
+
litellm
|
6 |
+
pydantic_settings
|
style.css
ADDED
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
:root {
|
2 |
+
--primary-color: #F2C12E;
|
3 |
+
--background-color: #FFFFFF;
|
4 |
+
--secondary-bg-color: #F0F2F6;
|
5 |
+
--text-color: #1E1E1E;
|
6 |
+
--accent-color: #024959;
|
7 |
+
--success-color: #28A745;
|
8 |
+
--error-color: #DC3545;
|
9 |
+
}
|
10 |
+
|
11 |
+
body {
|
12 |
+
background-color: var(--background-color);
|
13 |
+
color: var(--text-color);
|
14 |
+
font-family: 'Roboto', sans-serif;
|
15 |
+
}
|
16 |
+
|
17 |
+
.stApp {
|
18 |
+
max-width: 1200px;
|
19 |
+
margin: 0 auto;
|
20 |
+
}
|
21 |
+
|
22 |
+
.main-header {
|
23 |
+
font-size: 2.5rem;
|
24 |
+
color: var(--accent-color);
|
25 |
+
text-align: center;
|
26 |
+
margin-bottom: 2rem;
|
27 |
+
padding: 1rem;
|
28 |
+
background-color: var(--primary-color);
|
29 |
+
border-radius: 10px;
|
30 |
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
31 |
+
}
|
32 |
+
|
33 |
+
.sub-header {
|
34 |
+
font-size: 1.8rem;
|
35 |
+
color: var(--accent-color);
|
36 |
+
margin-top: 2rem;
|
37 |
+
margin-bottom: 1rem;
|
38 |
+
padding: 0.5rem;
|
39 |
+
border-bottom: 2px solid var(--primary-color);
|
40 |
+
}
|
41 |
+
|
42 |
+
.info-box {
|
43 |
+
background-color: var(--secondary-bg-color);
|
44 |
+
color: var(--text-color);
|
45 |
+
padding: 1rem;
|
46 |
+
border-radius: 5px;
|
47 |
+
margin-bottom: 1rem;
|
48 |
+
border-left: 5px solid var(--primary-color);
|
49 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
50 |
+
}
|
51 |
+
|
52 |
+
.success-box {
|
53 |
+
background-color: #E6F4EA;
|
54 |
+
color: var(--success-color);
|
55 |
+
padding: 1rem;
|
56 |
+
border-radius: 5px;
|
57 |
+
margin-bottom: 1rem;
|
58 |
+
border-left: 5px solid var(--success-color);
|
59 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
60 |
+
}
|
61 |
+
|
62 |
+
.error-box {
|
63 |
+
background-color: #FCE8E6;
|
64 |
+
color: var(--error-color);
|
65 |
+
padding: 1rem;
|
66 |
+
border-radius: 5px;
|
67 |
+
margin-bottom: 1rem;
|
68 |
+
border-left: 5px solid var(--error-color);
|
69 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
70 |
+
}
|
71 |
+
|
72 |
+
.stButton>button {
|
73 |
+
background-color: var(--primary-color);
|
74 |
+
color: var(--accent-color);
|
75 |
+
border: none;
|
76 |
+
border-radius: 4px;
|
77 |
+
padding: 0.5rem 1rem;
|
78 |
+
font-weight: bold;
|
79 |
+
transition: all 0.3s ease;
|
80 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
81 |
+
}
|
82 |
+
|
83 |
+
.stButton>button:hover {
|
84 |
+
background-color: var(--accent-color);
|
85 |
+
color: var(--primary-color);
|
86 |
+
transform: translateY(-2px);
|
87 |
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
88 |
+
}
|
89 |
+
|
90 |
+
.stSelectbox [data-baseweb="select"] {
|
91 |
+
background-color: var(--background-color);
|
92 |
+
border-color: var(--primary-color);
|
93 |
+
border-radius: 4px;
|
94 |
+
color: var(--text-color);
|
95 |
+
}
|
96 |
+
|
97 |
+
.stCheckbox {
|
98 |
+
color: var(--text-color);
|
99 |
+
}
|
100 |
+
|
101 |
+
.stFileUploader {
|
102 |
+
background-color: var(--secondary-bg-color);
|
103 |
+
border-color: var(--primary-color);
|
104 |
+
border-radius: 4px;
|
105 |
+
padding: 1rem;
|
106 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
107 |
+
}
|
108 |
+
|
109 |
+
/* Dashboard-like appearance */
|
110 |
+
.stApp > header {
|
111 |
+
background-color: var(--accent-color);
|
112 |
+
padding: 1rem;
|
113 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
114 |
+
}
|
115 |
+
|
116 |
+
.stApp > header .decoration {
|
117 |
+
display: none;
|
118 |
+
}
|
119 |
+
|
120 |
+
.stApp > header #MainMenu {
|
121 |
+
color: var(--primary-color);
|
122 |
+
}
|
123 |
+
|
124 |
+
.stApp > footer {
|
125 |
+
background-color: var(--secondary-bg-color);
|
126 |
+
border-top: 1px solid var(--primary-color);
|
127 |
+
padding: 1rem;
|
128 |
+
text-align: center;
|
129 |
+
font-size: 0.8rem;
|
130 |
+
color: var(--text-color);
|
131 |
+
}
|
132 |
+
|
133 |
+
/* Additional dashboard-like elements */
|
134 |
+
.metric-card {
|
135 |
+
background-color: var(--background-color);
|
136 |
+
border-radius: 8px;
|
137 |
+
padding: 1rem;
|
138 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
139 |
+
margin-bottom: 1rem;
|
140 |
+
}
|
141 |
+
|
142 |
+
.metric-card h3 {
|
143 |
+
color: var(--accent-color);
|
144 |
+
font-size: 1.2rem;
|
145 |
+
margin-bottom: 0.5rem;
|
146 |
+
}
|
147 |
+
|
148 |
+
.metric-card p {
|
149 |
+
font-size: 2rem;
|
150 |
+
font-weight: bold;
|
151 |
+
color: var(--primary-color);
|
152 |
+
margin: 0;
|
153 |
+
}
|
utils.py
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import jupytext
|
2 |
+
import os
|
3 |
+
import tempfile
|
4 |
+
|
5 |
+
def convert_file(input_file, output_format, config):
|
6 |
+
with tempfile.TemporaryDirectory() as temp_dir:
|
7 |
+
base_name = os.path.splitext(input_file.name)[0]
|
8 |
+
output_file = os.path.join(temp_dir, f"{base_name}.{output_format}")
|
9 |
+
content = input_file.getvalue()
|
10 |
+
input_format = os.path.splitext(input_file.name)[1][1:]
|
11 |
+
|
12 |
+
read_options = {}
|
13 |
+
write_options = {}
|
14 |
+
|
15 |
+
if input_format in ["py", "md"]:
|
16 |
+
read_options["fmt"] = f"{input_format}:{config['py_format']}" if input_format == "py" else input_format
|
17 |
+
notebook = jupytext.reads(content.decode(), **read_options)
|
18 |
+
else:
|
19 |
+
notebook = jupytext.reads(content, fmt=input_format)
|
20 |
+
|
21 |
+
if output_format == "py":
|
22 |
+
write_options["fmt"] = f"{output_format}:{config['py_format']}"
|
23 |
+
if config["comment_magics"]:
|
24 |
+
write_options["comment_magics"] = True
|
25 |
+
elif output_format == "ipynb":
|
26 |
+
write_options["fmt"] = output_format
|
27 |
+
else:
|
28 |
+
write_options["fmt"] = output_format
|
29 |
+
|
30 |
+
jupytext.write(notebook, output_file, **write_options)
|
31 |
+
|
32 |
+
with open(output_file, "rb") as f:
|
33 |
+
converted_content = f.read()
|
34 |
+
|
35 |
+
return converted_content, f"{base_name}.{output_format}"
|