| from llm import chat_ari | |
| from assets import emotion_assets | |
| import gradio as gr | |
| CSS = """ | |
| body { | |
| background: #ffffff; | |
| overflow-x: hidden; | |
| font-family: | |
| "Comic Sans MS", | |
| "Chalkboard SE", | |
| "Trebuchet MS", | |
| sans-serif; | |
| } | |
| .gradio-container { | |
| background: #ffffff !important; | |
| } | |
| #app { | |
| max-width: 1400px; | |
| margin: auto; | |
| } | |
| #ari-layout { | |
| display: flex; | |
| flex-direction: row; | |
| align-items: flex-start; | |
| gap: 20px; | |
| padding: 24px; | |
| min-height: 520px; | |
| } | |
| /* remove gradio image frame */ | |
| #ari-character > .wrap, | |
| #ari-character img, | |
| #ari-character [data-testid="image"] { | |
| background: transparent !important; | |
| border: none !important; | |
| box-shadow: none !important; | |
| padding: 0 !important; | |
| } | |
| #ari-character * { | |
| background: transparent !important; | |
| border: none !important; | |
| box-shadow: none !important; | |
| } | |
| #ari-character img { | |
| width: 280px; | |
| height: auto; | |
| border-radius: 0; | |
| filter: | |
| drop-shadow(0px 8px 18px rgba(0,0,0,0.12)); | |
| } | |
| /* bubble wrapper */ | |
| #ari-bubble-wrapper { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 8px; | |
| padding-top: 10px; | |
| } | |
| /* ari label */ | |
| #ari-name { | |
| font-size: 14px; | |
| color: #7b61c9; | |
| font-weight: 800; | |
| letter-spacing: 0.5px; | |
| padding-left: 8px; | |
| text-shadow: | |
| 1px 1px 0px white; | |
| } | |
| /* cartoon bubble */ | |
| #ari-bubble { | |
| position: relative; | |
| background: #f7f3ff; | |
| color: #2d2440; | |
| padding: 22px 26px; | |
| border-radius: 30px; | |
| font-size: 17px; | |
| line-height: 1.8; | |
| max-width: 650px; | |
| min-height: 110px; | |
| display: flex; | |
| align-items: center; | |
| border: 3px solid #2d2440; | |
| box-shadow: | |
| 6px 6px 0px #d8ccff, | |
| 12px 12px 0px rgba(0,0,0,0.07); | |
| transform: rotate(-0.4deg); | |
| transition: | |
| transform 0.18s ease, | |
| box-shadow 0.18s ease; | |
| } | |
| /* bubble hover animation */ | |
| #ari-bubble:hover { | |
| transform: | |
| rotate(0deg) | |
| translateY(-2px); | |
| box-shadow: | |
| 8px 8px 0px #d8ccff, | |
| 14px 14px 0px rgba(0,0,0,0.07); | |
| } | |
| /* speech bubble tail */ | |
| #ari-bubble::before { | |
| content: ""; | |
| position: absolute; | |
| left: -16px; | |
| top: 36px; | |
| width: 28px; | |
| height: 28px; | |
| background: #f7f3ff; | |
| border-left: 3px solid #2d2440; | |
| border-bottom: 3px solid #2d2440; | |
| transform: rotate(45deg); | |
| z-index: 1; | |
| } | |
| /* keep text above tail */ | |
| #ari-bubble > * { | |
| position: relative; | |
| z-index: 2; | |
| } | |
| /* text input */ | |
| #message-box textarea { | |
| background: #ffffff !important; | |
| color: #2d2440 !important; | |
| border: 3px solid #2d2440 !important; | |
| border-radius: 18px !important; | |
| font-size: 16px !important; | |
| box-shadow: | |
| 4px 4px 0px #d8ccff !important; | |
| padding: 14px !important; | |
| } | |
| /* remove textarea glow */ | |
| #message-box textarea:focus { | |
| box-shadow: | |
| 6px 6px 0px #cabaff !important; | |
| border-color: #7b61c9 !important; | |
| } | |
| /* send button */ | |
| #send-btn { | |
| border-radius: 16px !important; | |
| height: 52px !important; | |
| font-size: 16px !important; | |
| font-weight: 700 !important; | |
| border: 3px solid #2d2440 !important; | |
| background: #8b6dff !important; | |
| color: white !important; | |
| box-shadow: | |
| 4px 4px 0px #2d2440 !important; | |
| transition: | |
| transform 0.15s ease, | |
| box-shadow 0.15s ease !important; | |
| } | |
| /* button press effect */ | |
| #send-btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: | |
| 6px 6px 0px #2d2440 !important; | |
| } | |
| #send-btn:active { | |
| transform: translateY(2px); | |
| box-shadow: | |
| 2px 2px 0px #2d2440 !important; | |
| } | |
| /* code block */ | |
| #code-block { | |
| margin-top: 12px; | |
| border-radius: 18px; | |
| overflow: hidden; | |
| border: 3px solid #2d2440; | |
| box-shadow: | |
| 6px 6px 0px #d8ccff; | |
| } | |
| """ | |
| def build_bubble(message: str) -> str: | |
| return f""" | |
| <div id="ari-bubble-wrapper"> | |
| <div id="ari-name">Ari ✨</div> | |
| <div id="ari-bubble">{message}</div> | |
| </div> | |
| """ | |
| def respond(message, history): | |
| res = chat_ari(message, history) | |
| history.append({"role": "user", "content": message}) | |
| history.append({"role": "assistant", "content": res.message}) | |
| if res.code: | |
| lang = res.code_language or "" | |
| code_md = f"```{lang}\n{res.code}\n```" | |
| else: | |
| code_md = "" | |
| return ( | |
| build_bubble(res.message), | |
| res.img, | |
| history, | |
| "", | |
| code_md, | |
| gr.update(visible=bool(res.code)), | |
| ) | |
| with gr.Blocks() as demo: | |
| history_state = gr.State([]) | |
| with gr.Row(elem_id="app"): | |
| with gr.Column(scale=1): | |
| message_input = gr.TextArea( | |
| placeholder="Talk to Ari...", | |
| label="Message", | |
| lines=6, | |
| elem_id="message-box", | |
| ) | |
| send_btn = gr.Button( | |
| "Send", | |
| variant="primary", | |
| elem_id="send-btn", | |
| ) | |
| with gr.Column(scale=2): | |
| with gr.Row(elem_id="ari-layout"): | |
| with gr.Column(scale=1, elem_id="ari-character"): | |
| ari_image = gr.Image( | |
| value=str(emotion_assets["neutral"]), | |
| show_label=False, | |
| interactive=False, | |
| type="filepath", | |
| ) | |
| with gr.Column(scale=2): | |
| ari_bubble = gr.HTML( | |
| value=build_bubble( | |
| "I-I'm Aria... but you can call me Ari. Don't get the wrong idea, I just thought I'd introduce myself! ✨" | |
| ) | |
| ) | |
| gr.Markdown("## Code") | |
| with gr.Row(visible=False, elem_id="code-block") as code_row: | |
| code_output = gr.Markdown(value="") | |
| send_btn.click( | |
| fn=respond, | |
| inputs=[message_input, history_state], | |
| outputs=[ | |
| ari_bubble, | |
| ari_image, | |
| history_state, | |
| message_input, | |
| code_output, | |
| code_row, | |
| ], | |
| ) | |
| demo.launch( | |
| css=CSS, | |
| allowed_paths=["assets", "."], | |
| ) | |