unknown commited on
Commit
1654557
·
1 Parent(s): 0c157e9

UI Structure Optimization: Split Pane & Empty State

Browse files
Files changed (2) hide show
  1. assets/style.css +167 -97
  2. streamlit_app.py +122 -156
assets/style.css CHANGED
@@ -1,146 +1,216 @@
1
- /* Nordic Minimalism Theme for Pharma K */
2
 
3
- /* Variables */
 
 
4
  :root {
5
- --primary-color: #008080; /* Teal 鸭翅绿 */
6
- --primary-light: #E6F2F2;
 
7
  --text-main: #2C3E50;
8
- --text-secondary: #7F8C8D;
9
- --bg-main: #F8F9FA;
 
10
  --card-bg: #FFFFFF;
11
- --shadow-soft: 0 4px 20px rgba(0,0,0,0.05);
12
- --radius-main: 12px;
 
13
  }
14
 
15
- /* Global Reset & Typography */
16
  .stApp {
17
  background-color: var(--bg-main);
18
- font-family: 'PingFang SC', 'Microsoft YaHei', 'Inter', sans-serif;
19
  color: var(--text-main);
20
  }
21
 
22
- /* Main Container Padding */
 
 
23
  .block-container {
24
- padding-top: 2rem;
25
- padding-bottom: 5rem;
26
- max-width: 1000px;
27
  }
28
 
29
- /* -------------------
30
- Sidebar Styling
31
- ------------------- */
32
- section[data-testid="stSidebar"] {
33
- background-color: #FFFFFF;
34
- border-right: 1px solid #EAEAEA;
35
- box-shadow: 2px 0 10px rgba(0,0,0,0.02);
 
36
  }
37
 
38
- section[data-testid="stSidebar"] .block-container {
39
- padding-top: 2rem;
 
 
 
 
40
  }
41
 
42
- /* Expander in Sidebar (for API Config) */
43
- .streamlit-expanderHeader {
44
- background-color: transparent;
45
- border: 1px solid #E0E0E0;
46
- border-radius: 8px;
47
- color: var(--text-main);
48
  font-weight: 500;
49
  }
50
- .streamlit-expanderContent {
51
- border: 1px solid #E0E0E0;
52
- border-top: none;
53
- border-radius: 0 0 8px 8px;
54
- background-color: #FAFAFA;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  }
56
 
57
- /* -------------------
58
- Main Workbench Area
59
- ------------------- */
 
60
 
61
- /* Header - Clean & Professional */
62
- h1 {
 
 
 
 
 
 
 
63
  font-weight: 700 !important;
64
- color: var(--primary-color) !important;
65
- font-size: 2.2rem !important;
66
- margin-bottom: 0.5rem !important;
67
- letter-spacing: -0.5px;
68
  }
69
 
70
- h2, h3 {
71
- font-weight: 600 !important;
72
- color: var(--text-main) !important;
 
 
 
73
  }
74
 
75
- /* Card Style Containers */
76
- .css-1r6slb0, .stTextArea, .stTextInput, .stFileUploader {
77
- background: var(--card-bg);
78
- border-radius: var(--radius-main);
79
  }
80
 
81
- /* Input Fields */
82
- .stTextInput > div > div > input, .stTextArea > div > div > textarea {
83
- background-color: #FFFFFF;
84
- border: 1px solid #E0E0E0;
 
 
 
 
85
  border-radius: 8px;
86
- padding: 10px 12px;
87
- transition: all 0.3s ease;
88
  }
89
 
90
- .stTextInput > div > div > input:focus, .stTextArea > div > div > textarea:focus {
 
91
  border-color: var(--primary-color);
92
- box-shadow: 0 0 0 2px rgba(0, 128, 128, 0.1);
 
93
  }
94
 
95
- /* Buttons */
96
- .stButton > button {
97
- background-color: var(--primary-color) !important;
98
- color: white !important;
 
 
99
  border: none !important;
100
- border-radius: 8px !important;
101
- padding: 0.6rem 1.5rem !important;
102
- font-weight: 600 !important;
103
- box-shadow: 0 4px 12px rgba(0, 128, 128, 0.2) !important;
104
- transition: all 0.2s ease !important;
105
  }
106
 
107
- .stButton > button:hover {
 
108
  transform: translateY(-1px);
109
- box-shadow: 0 6px 16px rgba(0, 128, 128, 0.3) !important;
110
- background-color: #006666 !important;
111
  }
112
 
113
- .stButton > button:active {
114
- transform: translateY(1px);
 
 
 
 
 
 
 
 
 
 
 
115
  }
116
 
117
- /* Secondary Buttons (Outlined) */
118
- button[kind="secondary"] {
119
- background-color: transparent !important;
120
- border: 1px solid var(--primary-color) !important;
121
  color: var(--primary-color) !important;
122
- box-shadow: none !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  }
124
 
125
- /* Footer Branding */
126
- footer {visibility: hidden;}
127
  .pharma-k-footer {
 
 
 
128
  text-align: center;
129
- padding: 40px 0;
130
- color: var(--text-secondary);
131
- font-size: 0.9rem;
132
- border-top: 1px solid #EAEAEA;
133
- margin-top: 60px;
134
- }
135
-
136
- /* -------------------
137
- Report Area (Preview)
138
- ------------------- */
139
- .report-preview-box {
140
- background: white;
141
- padding: 40px;
142
- border-radius: 4px; /* A4 paper look */
143
- box-shadow: 0 2px 15px rgba(0,0,0,0.08);
144
- border: 1px solid #EAEAEA;
145
- margin-top: 20px;
146
- }
 
1
+ /* Nordic Minimalism Theme for Pharma K - Final Polish */
2
 
3
+ /* -----------------------------------------------------------
4
+ 1. Global Variables & Reset
5
+ ----------------------------------------------------------- */
6
  :root {
7
+ --primary-color: #008080;
8
+ /* Teal */
9
+ --primary-dark: #005f5f;
10
  --text-main: #2C3E50;
11
+ --text-light: #7F8C8D;
12
+ --bg-main: #F4F6F8;
13
+ /* Light gray-blue hue for depth */
14
  --card-bg: #FFFFFF;
15
+ --shadow-card: 0 4px 20px rgba(0, 0, 0, 0.05);
16
+ --border-radius: 12px;
17
+ --font-stack: 'Inter', 'PingFang SC', 'Microsoft YaHei', sans-serif;
18
  }
19
 
 
20
  .stApp {
21
  background-color: var(--bg-main);
22
+ font-family: var(--font-stack);
23
  color: var(--text-main);
24
  }
25
 
26
+ /* -----------------------------------------------------------
27
+ 2. Layout Constraints (Max Width)
28
+ ----------------------------------------------------------- */
29
  .block-container {
30
+ max-width: 1280px !important;
31
+ padding-top: 3rem !important;
32
+ padding-bottom: 5rem !important;
33
  }
34
 
35
+ /* -----------------------------------------------------------
36
+ 3. Header Styling (Centered & Clean)
37
+ ----------------------------------------------------------- */
38
+ .main-header {
39
+ text-align: center;
40
+ margin-bottom: 3rem;
41
+ background: transparent;
42
+ padding: 0;
43
  }
44
 
45
+ .main-header h1 {
46
+ font-size: 2.2rem;
47
+ font-weight: 800;
48
+ color: var(--primary-color);
49
+ margin: 0;
50
+ letter-spacing: -0.02em;
51
  }
52
 
53
+ .main-header p {
54
+ font-size: 1rem;
55
+ color: var(--text-light);
56
+ margin-top: 0.5rem;
 
 
57
  font-weight: 500;
58
  }
59
+
60
+ /* -----------------------------------------------------------
61
+ 4. Column Cards (The "Paper" Look)
62
+ ----------------------------------------------------------- */
63
+ /*
64
+ We target the direct children of columns.
65
+ Note: This selector usually works for the container inside a column
66
+ */
67
+ [data-testid="column"] {
68
+ background-color: var(--card-bg);
69
+ border-radius: var(--border-radius);
70
+ padding: 1.5rem !important;
71
+ box-shadow: var(--shadow-card);
72
+ border: 1px solid rgba(0, 0, 0, 0.03);
73
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
74
+ }
75
+
76
+ /* Fix for nested columns (e.g. buttons) - Reset card style */
77
+ [data-testid="column"] [data-testid="column"] {
78
+ background-color: transparent !important;
79
+ box-shadow: none !important;
80
+ border: none !important;
81
+ padding: 0 !important;
82
+ border-radius: 0 !important;
83
  }
84
 
85
+ [data-testid="column"]:hover {
86
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08);
87
+ z-index: 10;
88
+ }
89
 
90
+ /* Disable hover effect for nested columns */
91
+ [data-testid="column"] [data-testid="column"]:hover {
92
+ box-shadow: none !important;
93
+ transform: none !important;
94
+ }
95
+
96
+ /* Adjust headings inside columns */
97
+ [data-testid="column"] h3 {
98
+ font-size: 1.1rem !important;
99
  font-weight: 700 !important;
100
+ padding-bottom: 1rem;
101
+ border-bottom: 1px solid #eee;
102
+ margin-bottom: 1.5rem;
103
+ color: var(--text-main);
104
  }
105
 
106
+ /* -----------------------------------------------------------
107
+ 5. Sidebar Styling
108
+ ----------------------------------------------------------- */
109
+ section[data-testid="stSidebar"] {
110
+ background-color: white;
111
+ border-right: 1px solid #eee;
112
  }
113
 
114
+ section[data-testid="stSidebar"] [data-testid="stMarkdownContainer"] h1 {
115
+ font-size: 1.2rem !important;
 
 
116
  }
117
 
118
+ /* -----------------------------------------------------------
119
+ 6. Input Fields & Form Elements
120
+ ----------------------------------------------------------- */
121
+ .stTextInput input,
122
+ .stTextArea textarea,
123
+ .stSelectbox div[data-baseweb="select"] {
124
+ background-color: #F9FAFB;
125
+ border: 1px solid #E5E7EB;
126
  border-radius: 8px;
127
+ color: var(--text-main);
128
+ font-size: 0.95rem;
129
  }
130
 
131
+ .stTextInput input:focus,
132
+ .stTextArea textarea:focus {
133
  border-color: var(--primary-color);
134
+ box-shadow: 0 0 0 3px rgba(0, 128, 128, 0.1);
135
+ background-color: white;
136
  }
137
 
138
+ /* -----------------------------------------------------------
139
+ 7. Buttons
140
+ ----------------------------------------------------------- */
141
+ /* Primary Action Button */
142
+ button[kind="primary"] {
143
+ background: var(--primary-color) !important;
144
  border: none !important;
145
+ box-shadow: 0 4px 6px rgba(0, 128, 128, 0.2);
146
+ font-weight: 600;
147
+ letter-spacing: 0.02em;
148
+ padding: 0.6rem 1.2rem;
149
+ transition: all 0.2s;
150
  }
151
 
152
+ button[kind="primary"]:hover {
153
+ background: var(--primary-dark) !important;
154
  transform: translateY(-1px);
155
+ box-shadow: 0 6px 12px rgba(0, 128, 128, 0.25);
 
156
  }
157
 
158
+ /* Secondary / Pill Buttons (Quick Actions) */
159
+ /* Requires type="secondary" in logic */
160
+ button[kind="secondary"] {
161
+ background: white !important;
162
+ border: 1px solid #E5E7EB !important;
163
+ color: var(--text-main) !important;
164
+ border-radius: 50px !important;
165
+ /* Pill shape */
166
+ font-size: 0.85rem !important;
167
+ padding: 0.25rem 0.75rem !important;
168
+ height: auto !important;
169
+ min-height: 2rem;
170
+ box-shadow: none !important;
171
  }
172
 
173
+ button[kind="secondary"]:hover {
174
+ border-color: var(--primary-color) !important;
 
 
175
  color: var(--primary-color) !important;
176
+ background-color: #F0FDFA !important;
177
+ }
178
+
179
+ /* -----------------------------------------------------------
180
+ 8. Tabs
181
+ ----------------------------------------------------------- */
182
+ .stTabs [data-baseweb="tab-list"] {
183
+ gap: 1rem;
184
+ border-bottom: 1px solid #eee;
185
+ margin-bottom: 1.5rem;
186
+ }
187
+
188
+ .stTabs [data-baseweb="tab"] {
189
+ height: 3rem;
190
+ font-weight: 500;
191
+ color: var(--text-light);
192
+ border: none;
193
+ background: transparent;
194
+ }
195
+
196
+ .stTabs [aria-selected="true"] {
197
+ color: var(--primary-color) !important;
198
+ border-bottom: 2px solid var(--primary-color) !important;
199
+ font-weight: 600;
200
+ }
201
+
202
+ /* -----------------------------------------------------------
203
+ 9. Footer
204
+ ----------------------------------------------------------- */
205
+ footer {
206
+ visibility: hidden;
207
  }
208
 
 
 
209
  .pharma-k-footer {
210
+ border-top: 1px solid #eee;
211
+ padding-top: 2rem;
212
+ margin-top: 3rem;
213
  text-align: center;
214
+ color: var(--text-light);
215
+ font-size: 0.85rem;
216
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
streamlit_app.py CHANGED
@@ -1091,31 +1091,38 @@ def main():
1091
 
1092
  st.markdown("---")
1093
 
1094
- # ========== Main Header ==========
1095
- # ========== Main Header ==========
1096
  st.markdown("""
1097
- <div class="main-header">
1098
- <h1>Pharma K 药物制剂相容性与稳定性分析专家系统</h1>
1099
- <p>Expert System for Drug Stability & Compatibility Analysis</p>
 
 
 
 
 
 
1100
  </div>
1101
  """, unsafe_allow_html=True)
1102
 
1103
- # Main content - Two columns
1104
- col_input, col_output = st.columns([1, 1.5])
1105
 
1106
  with col_input:
1107
- st.subheader("📋 输入信息")
 
 
1108
 
1109
  # Analysis mode tabs
1110
  tab_compat, tab_stability, tab_general = st.tabs([
1111
- "🧬 相容性分析", "📊 稳定性分析", "❓ 通用问答"
1112
  ])
1113
 
1114
  with tab_compat:
1115
- st.markdown("#### 药物-辅料相容性预测")
1116
  smiles = st.text_area(
1117
- "API SMILES 结构式",
1118
- placeholder="例如: CC(=O)Nc1ccc(O)cc1 (对乙酰氨基酚)",
1119
  height=100,
1120
  key="smiles_input"
1121
  )
@@ -1129,24 +1136,27 @@ def main():
1129
  if smiles:
1130
  mol_renderer = get_molecule_renderer()
1131
  if mol_renderer and mol_renderer.is_available:
1132
- svg = mol_renderer.render_2d_svg(smiles, 300, 200)
1133
  if svg:
1134
- st.markdown("**分子结构预览:**")
1135
  st.markdown(svg, unsafe_allow_html=True)
1136
 
1137
- compat_button = st.button("🔬 开始相容性分析", use_container_width=True, type="primary", key="compat_btn")
 
1138
 
1139
  with tab_stability:
1140
- st.markdown("#### 稳定性数据分析")
1141
  uploaded_files = st.file_uploader(
1142
- "上传稳定性数据文件",
1143
  type=["xlsx", "xls", "docx", "doc", "pdf", "csv"],
1144
  accept_multiple_files=True,
1145
- key="stability_files"
 
1146
  )
 
 
1147
 
1148
  # Quick Action Chips
1149
- st.markdown('<div style="margin-bottom: 8px; font-size: 14px; font-weight: 500;">快捷分析目标</div>', unsafe_allow_html=True)
1150
  chip_col1, chip_col2, chip_col3 = st.columns(3)
1151
 
1152
  def set_goal(text):
@@ -1156,37 +1166,62 @@ def main():
1156
  st.session_state.stability_goal_input = ""
1157
 
1158
  with chip_col1:
1159
- st.button("📈 趋势分析", use_container_width=True, on_click=set_goal, args=("请分析各批次的杂质增长趋势,并判断是否符合限度要求。",))
1160
  with chip_col2:
1161
- st.button("🔮 货架期预测", use_container_width=True, on_click=set_goal, args=("基于现有数据,请预测24个月时的含量及有关物质数据。",))
1162
  with chip_col3:
1163
- st.button("🏆 处方筛选", use_container_width=True, on_click=set_goal, args=("对比不同处方批次的稳定性数据,找出最稳定的处方。",))
1164
 
1165
  stability_goal = st.text_area(
1166
- "分析目标详情",
1167
  value=st.session_state.stability_goal_input,
1168
- placeholder="描述您想要进行的分析...\n或点击上方快捷按钮自动填充",
1169
- height=150,
1170
- key="stability_goal_input_area", # distinct key so we can sync manually if needed but value controls it
1171
  on_change=lambda: st.session_state.update({"stability_goal_input": st.session_state.stability_goal_input_area})
1172
  )
1173
-
1174
 
1175
- stability_button = st.button("📊 开始稳定性分析", use_container_width=True, type="primary", key="stability_btn")
 
1176
 
1177
  with tab_general:
1178
- st.markdown("#### 制剂研发问答")
1179
  question = st.text_area(
1180
- "您的问题",
1181
- placeholder="请输入您关于药物稳定性、制剂研发的问题...\n例如:\n- ICH指南对稳定性试验有什么要求?\n- 如何评估药物与辅料的相容性?\n- 什么情况下需要进行加速稳定性试验?",
1182
  height=150,
1183
- key="general_question"
 
1184
  )
1185
 
1186
- general_button = st.button("💡 获取回答", use_container_width=True, type="primary", key="general_btn")
1187
-
 
1188
  with col_output:
1189
  st.subheader("📊 分析结果")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1190
  result_container = st.container()
1191
 
1192
  # Handle button clicks
@@ -1198,7 +1233,7 @@ def main():
1198
  elif not api_key:
1199
  st.warning("请在侧边栏配置 API Key")
1200
  else:
1201
- with st.spinner("正在进行相容性分析..."):
1202
  html_report, _ = run_compatibility_analysis(
1203
  smiles=smiles,
1204
  excipient=excipient,
@@ -1206,152 +1241,83 @@ def main():
1206
  provider=provider
1207
  )
1208
 
1209
- # Wrap with Pharma K branding
1210
  branded_report = wrap_report_with_branding(html_report, "相容性分析")
1211
 
1212
- # Display report
1213
- st.components.v1.html(branded_report, height=800, scrolling=True)
1214
-
1215
- # Download buttons
1216
- col_html, col_pdf = st.columns(2)
1217
- with col_html:
1218
- st.download_button(
1219
- label="📥 下载 HTML 报告",
1220
- data=branded_report,
1221
- file_name=f"Pharma_K_相容性分析报告_{smiles[:10]}_{excipient}.html",
1222
- mime="text/html",
1223
- use_container_width=True
1224
- )
1225
- with col_pdf:
1226
  pdf_data = generate_pdf_from_html(branded_report)
1227
  if pdf_data:
1228
- st.download_button(
1229
- label="📄 下载 PDF 报告",
1230
- data=pdf_data,
1231
- file_name=f"Pharma_K_相容性分析报告_{smiles[:10]}_{excipient}.pdf",
1232
- mime="application/pdf",
1233
- use_container_width=True
1234
- )
1235
- else:
1236
- st.info("💡 PDF导出需要安装 weasyprint 或 pdfkit")
1237
 
1238
- # Stability Analysis
1239
  elif stability_button:
1240
- if not uploaded_files or not stability_goal:
1241
- st.warning("请上传数据文件并描述分析目标")
1242
  elif not api_key:
1243
- st.warning("请在侧边栏配置 API Key")
1244
  else:
1245
- with st.spinner("正在进行稳定性分析..."):
1246
- html_report = run_stability_analysis(
1247
- goal=stability_goal,
1248
- files=uploaded_files,
1249
- api_key=api_key,
1250
- provider=provider
1251
- )
1252
-
1253
- # Check if report has content
1254
- if html_report and len(html_report.strip()) > 100:
1255
- # Wrap with Pharma K branding
1256
- branded_report = wrap_report_with_branding(html_report, "稳定性分析")
1257
-
1258
- # Ensure chart data is rendered (in case it wasn't processed earlier)
1259
- branded_report = parse_and_render_charts(branded_report)
1260
-
1261
- st.session_state.analysis_result = branded_report
1262
- st.session_state.analysis_type = "stability"
1263
- # Force page rerun to display results
1264
- st.rerun()
1265
- else:
1266
- st.error("分析返回空结果,请检查 API 配置或重试")
1267
-
1268
  # Display stability analysis result from session state
1269
- if st.session_state.analysis_type == "stability" and st.session_state.analysis_result:
1270
  branded_report = st.session_state.analysis_result
1271
 
1272
- # Display report
1273
- st.components.v1.html(branded_report, height=800, scrolling=True)
1274
-
1275
- # Download buttons
1276
- col_html, col_pdf = st.columns(2)
1277
- with col_html:
1278
- st.download_button(
1279
- label="📥 下载 HTML 报告",
1280
- data=branded_report,
1281
- file_name=f"Pharma_K_稳定性分析报告_{datetime.now().strftime('%Y%m%d_%H%M')}.html",
1282
- mime="text/html",
1283
- use_container_width=True,
1284
- key="stability_html_download"
1285
- )
1286
- with col_pdf:
1287
  pdf_data = generate_pdf_from_html(branded_report)
1288
  if pdf_data:
1289
- st.download_button(
1290
- label="📄 下载 PDF 报告",
1291
- data=pdf_data,
1292
- file_name=f"Pharma_K_稳定性分析报告_{datetime.now().strftime('%Y%m%d_%H%M')}.pdf",
1293
- mime="application/pdf",
1294
- use_container_width=True,
1295
- key="stability_pdf_download"
1296
- )
1297
- else:
1298
- st.info("💡 PDF导出需要安装 weasyprint 或 pdfkit")
1299
 
1300
  # General Q&A
1301
  elif general_button:
1302
  if not question:
1303
- st.warning("请输入您的问题")
1304
  elif not api_key:
1305
- st.warning("请在侧边栏配置 API Key")
1306
  else:
1307
- with st.spinner("正在生成回答..."):
1308
- html_response = run_general_analysis(
1309
- question=question,
1310
- api_key=api_key,
1311
- provider=provider
1312
- )
1313
 
1314
- # Wrap with Pharma K branding
1315
  branded_response = wrap_report_with_branding(html_response, "专家问答")
1316
 
1317
- # Display response
1318
- st.components.v1.html(branded_response, height=600, scrolling=True)
1319
-
1320
- # Download buttons
1321
- col_html, col_pdf = st.columns(2)
1322
- with col_html:
1323
- st.download_button(
1324
- label="📥 下载 HTML",
1325
- data=branded_response,
1326
- file_name=f"Pharma_K_问答_{datetime.now().strftime('%Y%m%d_%H%M')}.html",
1327
- mime="text/html",
1328
- use_container_width=True
1329
- )
1330
- with col_pdf:
1331
  pdf_data = generate_pdf_from_html(branded_response)
1332
  if pdf_data:
1333
- st.download_button(
1334
- label="📄 下载 PDF",
1335
- data=pdf_data,
1336
- file_name=f"Pharma_K_问答_{datetime.now().strftime('%Y%m%d_%H%M')}.pdf",
1337
- mime="application/pdf",
1338
- use_container_width=True
1339
- )
1340
- else:
1341
- st.info("💡 PDF导出需安装 weasyprint")
1342
 
1343
- else:
1344
- # Default placeholder
1345
- st.markdown("""
1346
- <div style="text-align: center; padding: 100px 20px; color: #6b7280;">
1347
- <h3>👆 请在左侧选择分析类型并输入信息</h3>
1348
- <p style="margin-top: 20px;">
1349
- 系统支持三种分析模式:<br/>
1350
- <strong>相容性分析</strong> | <strong>稳定性分析</strong> | <strong>通用问答</strong>
1351
- </p>
1352
- </div>
1353
- """, unsafe_allow_html=True)
1354
-
1355
  # =============================================================================
1356
  # Entry Point
1357
  # =============================================================================
 
1091
 
1092
  st.markdown("---")
1093
 
1094
+ # ========== Main Header (Navbar Style) ==========
 
1095
  st.markdown("""
1096
+ <div class="main-header" style="text-align: left !important; padding: 1rem 0 !important; margin-bottom: 2rem !important; border-bottom: 1px solid #eee;">
1097
+ <div style="display: flex; align-items: center; justify-content: space-between;">
1098
+ <div style="font-size: 1.5rem; font-weight: 700; color: #008080;">
1099
+ Pharma K <span style="font-size: 0.9rem; font-weight: 400; color: #7F8C8D; margin-left: 10px;">专家系统</span>
1100
+ </div>
1101
+ <div style="font-size: 0.9rem; color: #7F8C8D;">
1102
+ Next-Gen Drug Stability Analysis
1103
+ </div>
1104
+ </div>
1105
  </div>
1106
  """, unsafe_allow_html=True)
1107
 
1108
+ # Main content - Split Pane Layout
1109
+ col_input, col_output = st.columns([3.8, 6.2], gap="large")
1110
 
1111
  with col_input:
1112
+ st.markdown('<div class="nordic-card">', unsafe_allow_html=True)
1113
+ # (Note: CSS targeting [data-testid="column"] handles the card look, but we keep this comment for clarity)
1114
+ st.subheader("📝 任务控制台")
1115
 
1116
  # Analysis mode tabs
1117
  tab_compat, tab_stability, tab_general = st.tabs([
1118
+ "相容性", "稳定性", "问答"
1119
  ])
1120
 
1121
  with tab_compat:
1122
+ st.caption("预测药物分子与辅料相容性风险")
1123
  smiles = st.text_area(
1124
+ "API SMILES",
1125
+ placeholder="例如: CC(=O)Nc1ccc(O)cc1",
1126
  height=100,
1127
  key="smiles_input"
1128
  )
 
1136
  if smiles:
1137
  mol_renderer = get_molecule_renderer()
1138
  if mol_renderer and mol_renderer.is_available:
1139
+ svg = mol_renderer.render_2d_svg(smiles, 300, 150)
1140
  if svg:
 
1141
  st.markdown(svg, unsafe_allow_html=True)
1142
 
1143
+ st.markdown("---")
1144
+ compat_button = st.button("🚀 开始分析", use_container_width=True, type="primary", key="compat_btn")
1145
 
1146
  with tab_stability:
1147
+ st.caption("基于已有数据文件进行趋势与货架期分析")
1148
  uploaded_files = st.file_uploader(
1149
+ "上传数据文件",
1150
  type=["xlsx", "xls", "docx", "doc", "pdf", "csv"],
1151
  accept_multiple_files=True,
1152
+ key="stability_files",
1153
+ label_visibility="collapsed"
1154
  )
1155
+ if not uploaded_files:
1156
+ st.info("👆 请先上传稳定性数据文件")
1157
 
1158
  # Quick Action Chips
1159
+ st.markdown('<div style="margin: 15px 0 8px; font-size: 13px; font-weight: 500; color: #666;">快捷指令</div>', unsafe_allow_html=True)
1160
  chip_col1, chip_col2, chip_col3 = st.columns(3)
1161
 
1162
  def set_goal(text):
 
1166
  st.session_state.stability_goal_input = ""
1167
 
1168
  with chip_col1:
1169
+ st.button("📈 趋势", use_container_width=True, type="secondary", on_click=set_goal, args=("请分析各批次的杂质增长趋势,并判断是否符合限度要求。",))
1170
  with chip_col2:
1171
+ st.button("🔮 货架期", use_container_width=True, type="secondary", on_click=set_goal, args=("基于现有数据,请预测24个月时的含量数据。",))
1172
  with chip_col3:
1173
+ st.button("🏆 筛选", use_container_width=True, type="secondary", on_click=set_goal, args=("对比不同处方批次,找出最稳定的处方。",))
1174
 
1175
  stability_goal = st.text_area(
1176
+ "详细分析目标",
1177
  value=st.session_state.stability_goal_input,
1178
+ placeholder="或在此手动描述...",
1179
+ height=100,
1180
+ key="stability_goal_input_area",
1181
  on_change=lambda: st.session_state.update({"stability_goal_input": st.session_state.stability_goal_input_area})
1182
  )
 
1183
 
1184
+ st.markdown("---")
1185
+ stability_button = st.button("🚀 开始分析", use_container_width=True, type="primary", key="stability_btn")
1186
 
1187
  with tab_general:
1188
+ st.caption("制剂研发领域的专业问答助手")
1189
  question = st.text_area(
1190
+ "问题描述",
1191
+ placeholder="例如: ICH指南对稳定性试验有什么要求?",
1192
  height=150,
1193
+ key="general_question",
1194
+ label_visibility="collapsed"
1195
  )
1196
 
1197
+ st.markdown("---")
1198
+ general_button = st.button("💡 咨询专家", use_container_width=True, type="primary", key="general_btn")
1199
+
1200
  with col_output:
1201
  st.subheader("📊 分析结果")
1202
+
1203
+ # Determine if we have any active result
1204
+ is_active = False
1205
+ if compat_button or stability_button or general_button:
1206
+ is_active = True
1207
+ if st.session_state.get('analysis_type') == 'stability' and st.session_state.get('analysis_result'):
1208
+ is_active = True
1209
+
1210
+ # Empty State
1211
+ if not is_active:
1212
+ st.markdown("""
1213
+ <div style="text-align: center; padding: 60px 20px; color: #999;">
1214
+ <div style="font-size: 60px; margin-bottom: 20px;">🧬</div>
1215
+ <h3 style="color: #666; margin-bottom: 10px;">准备就绪</h3>
1216
+ <p>请在左侧选择分析模式并输入信息<br>AI 专家系统将为您生成专业报告</p>
1217
+ <div style="margin-top: 30px; display: flex; justify-content: center; gap: 20px;">
1218
+ <div style="background: #f0f2f6; padding: 10px 20px; border-radius: 20px; font-size: 12px;">相容性预测</div>
1219
+ <div style="background: #f0f2f6; padding: 10px 20px; border-radius: 20px; font-size: 12px;">稳定性分析</div>
1220
+ <div style="background: #f0f2f6; padding: 10px 20px; border-radius: 20px; font-size: 12px;">专家问答</div>
1221
+ </div>
1222
+ </div>
1223
+ """, unsafe_allow_html=True)
1224
+
1225
  result_container = st.container()
1226
 
1227
  # Handle button clicks
 
1233
  elif not api_key:
1234
  st.warning("请在侧边栏配置 API Key")
1235
  else:
1236
+ with st.spinner("正在解析分子结构并进行相容性推演..."):
1237
  html_report, _ = run_compatibility_analysis(
1238
  smiles=smiles,
1239
  excipient=excipient,
 
1241
  provider=provider
1242
  )
1243
 
 
1244
  branded_report = wrap_report_with_branding(html_report, "相容性分析")
1245
 
1246
+ # Toolbar (Download Buttons)
1247
+ c1, c2, c3 = st.columns([2, 1, 1])
1248
+ with c2:
1249
+ st.download_button("📥 HTML", branded_report, f"Pharma_K_Compat_{smiles[:5]}.html", "text/html")
1250
+ with c3:
 
 
 
 
 
 
 
 
 
1251
  pdf_data = generate_pdf_from_html(branded_report)
1252
  if pdf_data:
1253
+ st.download_button("📄 PDF", pdf_data, f"Pharma_K_Compat_{smiles[:5]}.pdf", "application/pdf")
1254
+
1255
+ st.components.v1.html(branded_report, height=800, scrolling=True)
 
 
 
 
 
 
1256
 
1257
+ # Stability Analysis Logic
1258
  elif stability_button:
1259
+ if not uploaded_files or not st.session_state.stability_goal_input:
1260
+ st.warning("请上传数据文件并描述分析目标")
1261
  elif not api_key:
1262
+ st.warning("请在侧边栏配置 API Key")
1263
  else:
1264
+ with st.spinner("正在进行多维度稳定性数据分析..."):
1265
+ html_report = run_stability_analysis(
1266
+ goal=st.session_state.stability_goal_input,
1267
+ files=uploaded_files,
1268
+ api_key=api_key,
1269
+ provider=provider
1270
+ )
1271
+
1272
+ if html_report and len(html_report.strip()) > 100:
1273
+ branded_report = wrap_report_with_branding(html_report, "稳定性分析")
1274
+ branded_report = parse_and_render_charts(branded_report)
1275
+ st.session_state.analysis_result = branded_report
1276
+ st.session_state.analysis_type = "stability"
1277
+ st.rerun()
1278
+ else:
1279
+ st.error("分析未返回有效结果")
1280
+
 
 
 
 
 
 
1281
  # Display stability analysis result from session state
1282
+ if st.session_state.get('analysis_type') == "stability" and st.session_state.get('analysis_result'):
1283
  branded_report = st.session_state.analysis_result
1284
 
1285
+ # Toolbar
1286
+ c1, c2, c3 = st.columns([2, 1, 1])
1287
+ with c1:
1288
+ st.success("✅ 分析完成")
1289
+ with c2:
1290
+ st.download_button("📥 HTML", branded_report, "Stability_Report.html", "text/html")
1291
+ with c3:
 
 
 
 
 
 
 
 
1292
  pdf_data = generate_pdf_from_html(branded_report)
1293
  if pdf_data:
1294
+ st.download_button("📄 PDF", pdf_data, "Stability_Report.pdf", "application/pdf")
1295
+
1296
+ st.components.v1.html(branded_report, height=800, scrolling=True)
 
 
 
 
 
 
 
1297
 
1298
  # General Q&A
1299
  elif general_button:
1300
  if not question:
1301
+ st.warning("请输入问题")
1302
  elif not api_key:
1303
+ st.warning("请配置 API Key")
1304
  else:
1305
+ with st.spinner("专家系统正在思考..."):
1306
+ html_response = run_general_analysis(question, api_key, provider)
 
 
 
 
1307
 
 
1308
  branded_response = wrap_report_with_branding(html_response, "专家问答")
1309
 
1310
+ # Toolbar
1311
+ c1, c2, c3 = st.columns([2, 1, 1])
1312
+ with c2:
1313
+ st.download_button("📥 HTML", branded_response, "QA_Response.html", "text/html")
1314
+ with c3:
 
 
 
 
 
 
 
 
 
1315
  pdf_data = generate_pdf_from_html(branded_response)
1316
  if pdf_data:
1317
+ st.download_button("📄 PDF", pdf_data, "QA_Response.pdf", "application/pdf")
1318
+
1319
+ st.components.v1.html(branded_response, height=600, scrolling=True)
 
 
 
 
 
 
1320
 
 
 
 
 
 
 
 
 
 
 
 
 
1321
  # =============================================================================
1322
  # Entry Point
1323
  # =============================================================================