LinkedIn1.0 / src /streamlit_app.py
422TOM's picture
Update src/streamlit_app.py
d803799 verified
Raw
History Blame Contribute Delete
6.35 kB
import streamlit as st
import streamlit.components.v1 as components
# 頁面配置
st.set_page_config(layout="wide", page_title="AlterEgo LinkedIn Lab")
# 1. CSS 樣式 (絕對靠左,防止誤判)
css_styles = """
<style>
/* --- 全局設定 --- */
html, body, .stApp {
height: 100vh;
overflow: hidden !important;
margin: 0;
}
.stApp {
background:
radial-gradient(circle at 20% 30%, rgba(70, 130, 255, 0.3) 0%, transparent 50%),
radial-gradient(circle at 80% 70%, rgba(255, 20, 147, 0.2) 0%, transparent 50%),
radial-gradient(circle at center, #4b1a8a 0%, #1a0b2e 60%, #000000 100%);
}
.block-container { padding: 0 !important; max-width: 100% !important; }
header, footer, .stDeployButton { display: none !important; }
/* --- 佈局外殼 --- */
.shell {
position: fixed;
top: 0; left: 0;
width: 100vw; height: 100vh;
display: flex;
flex-direction: column;
}
/* --- Header & Footer --- */
.header, .footer {
flex: 0 0 70px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 30px;
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(15px);
z-index: 100;
}
.header { border-bottom: 1px solid rgba(255, 255, 255, 0.15); }
.footer {
border-top: 1px solid rgba(255, 255, 255, 0.15);
position: fixed; bottom: 0; left: 0; width: 100%;
}
.header-title { color: white; font-weight: bold; font-size: 1.1rem; }
.step-indicator { font-size: 0.7rem; color: #45B0FF; margin-top: 2px; }
/* --- Main Content --- */
.main-content {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 70px;
}
.linkedin-box {
width: 800px;
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 16px;
padding: 30px;
box-shadow: 0 0 60px rgba(0, 0, 0, 0.6);
position: relative;
}
/* User Info */
.user-info { display: flex; align-items: center; margin-bottom: 20px; }
.avatar {
width: 64px; height: 64px;
border-radius: 50%;
background: #555;
border: 3px solid #FF69B4;
box-shadow: 0 0 15px #FF69B4;
margin-right: 18px;
}
.name-text { color: white; font-weight: 600; font-size: 1.3rem; }
.anyone-badge {
background: rgba(255,255,255,0.1);
border: 1px solid rgba(255,255,255,0.3);
border-radius: 20px;
padding: 4px 12px;
font-size: 0.85rem;
color: #ccc;
display: inline-block;
margin-top: 6px;
}
/* Magic Button */
.alterego-magic-btn {
position: absolute;
top: 55%; left: 50%;
transform: translate(-50%, -50%);
background: linear-gradient(135deg, #45B0FF, #FF69B4);
border: none;
border-radius: 35px;
padding: 15px 35px;
color: white;
font-weight: 900;
font-size: 1.2rem;
box-shadow: 0 0 25px rgba(69, 176, 255, 0.8);
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
z-index: 10;
white-space: nowrap;
animation: pulse 3s infinite ease-in-out;
}
@keyframes pulse {
0% { box-shadow: 0 0 20px rgba(69, 176, 255, 0.8); }
50% { box-shadow: 0 0 40px rgba(255, 105, 180, 0.8); transform: translate(-50%, -50%) scale(1.05); }
100% { box-shadow: 0 0 20px rgba(69, 176, 255, 0.8); }
}
/* Text Area */
.input-placeholder {
height: 180px;
color: rgba(255, 255, 255, 0.5);
font-size: 1.5rem;
padding-top: 10px;
}
/* Smiley Icon */
.smiley-icon {
font-size: 1.8rem;
color: #45B0FF;
margin-bottom: 15px;
cursor: pointer;
width: fit-content;
}
/* Bottom Icons Row */
.bottom-row {
display: flex;
justify-content: space-between;
align-items: center;
border-top: 1px solid rgba(255,255,255,0.1);
padding-top: 20px;
}
.icon-group {
display: flex;
gap: 30px;
font-size: 1.6rem;
color: #45B0FF;
}
/* Post Actions (Clock + Button) */
.post-actions {
display: flex;
align-items: center;
gap: 15px; /* 時鐘與按鈕的距離 */
}
.schedule-icon {
font-size: 1.5rem;
color: rgba(255, 255, 255, 0.6);
cursor: pointer;
}
/* Hide native buttons */
div[data-testid="stButton"] { display: none !important; }
.shell div[data-testid="stButton"] { display: inline-block !important; }
</style>
"""
st.markdown(css_styles, unsafe_allow_html=True)
# 2. HTML 骨架 (絕對靠左)
html_structure = f"""
<div class="shell">
<div class="header">
<div id="slot-back"></div>
<div style="text-align: center;">
<div class="header-title">新 LinkedIn 貼文</div>
<div class="step-indicator">STEP 2/3 : 專業賦能</div>
</div>
<div id="slot-next"></div>
</div>
<div class="main-content">
<div class="linkedin-box">
<div class="user-info">
<div class="avatar"></div>
<div>
<div class="name-text">Judy Wang</div>
<div class="anyone-badge">🌐 Anyone</div>
</div>
</div>
<div class="input-placeholder">分享您的意見想法......</div>
<div class="alterego-magic-btn">
<span>🤖</span> ✨ AlterEgo AI 智能潤飾
</div>
<div class="smiley-icon">🙂</div>
<div class="bottom-row">
<div class="icon-group">
<span>🖼️</span> <span>🗓️</span> <span>🏵️</span> <span>➕</span>
</div>
<div class="post-actions">
<span class="schedule-icon">🕒</span>
<div id="slot-post"></div>
</div>
</div>
</div>
</div>
<div class="footer"></div>
</div>
"""
st.markdown(html_structure, unsafe_allow_html=True)
# 3. Streamlit 按鈕
st.button("⬅ 返回上一步", key="btn_back")
st.button("下一步", key="btn_next")
# 注意:這裡的按鈕文字改成了 "發表"
st.button("發表", key="btn_post")
# 4. JS 搬運工
components.html("""
<script>
function move(btnText, targetId) {
const buttons = Array.from(window.parent.document.querySelectorAll('button'));
const targetBtn = buttons.find(b => b.innerText.includes(btnText));
const targetSlot = window.parent.document.getElementById(targetId);
if (targetBtn && targetSlot) {
const wrapper = targetBtn.closest('div[data-testid="stButton"]');
if (wrapper) targetSlot.appendChild(wrapper);
}
}
setTimeout(() => {
move("返回上一步", "slot-back");
move("下一步", "slot-next");
move("發表", "slot-post"); // JS 也要改成抓取 "發表"
}, 150);
</script>
""", height=0)