Spaces:
Sleeping
Sleeping
import streamlit as st | |
import streamlit.components.v1 as components | |
st.set_page_config(layout="wide", page_title="Streamlit LLM Playground") | |
st.markdown(""" | |
<style> | |
#MainMenu, header, footer { visibility: hidden; } | |
html, body, .main { | |
height: 100%; | |
width: 100%; | |
margin: 5px; | |
color :white; | |
overflow: hidden !important; | |
} | |
iframe { | |
height:690px !important; | |
width: 99.5vw !important; | |
border-radius: 5px ; | |
overflow: hidden !important; | |
margin-top:-15px; | |
} | |
div[data-testid="stMainBlockContainer"]{ | |
padding :0px !important; | |
margin-top:-15px; | |
} | |
</style> | |
""", unsafe_allow_html=True) | |
html_code=""" | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>LLM Studio Enhanced</title> | |
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> | |
<style> | |
/* General Styles */ | |
:root { | |
--bg-color-dark: #0D1117; | |
--bg-color-medium: #161B22; | |
--border-color: #30363D; | |
--text-color-primary: #c9d1d9; | |
--text-color-secondary: #8b949e; | |
--accent-color: #58a6ff; | |
--accent-glow: rgba(88, 166, 255, 0.3); | |
--user-bubble-bg: #21262d; | |
--model-bubble-bg: #161B22; | |
--error-color: #f85149; | |
--groq-color: #4CAF50; | |
--container-radius: 16px; | |
} | |
body { | |
height: 100vh; | |
width: 100%; | |
margin: 0; | |
padding: 0; | |
background: var(--bg-color-dark); | |
font-family: 'time new roman'; | |
color: var(--text-color-primary); | |
} | |
.studio-container { | |
width: 100%; | |
height: 100vh; | |
background-color: rgba(22, 27, 34, 0.8); | |
backdrop-filter: blur(10px); | |
border-radius: var(--container-radius); | |
border: 1px solid var(--border-color); | |
overflow: hidden; | |
display: flex; | |
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37); | |
} | |
/* MODIFIED: Final Polished Collapsible Sidebar */ | |
.sidebar { | |
width: 240px; /* Fixed width */ | |
background-color: rgba(13, 17, 23, 0.9); | |
border-right: 1px solid var(--border-color); | |
flex-shrink: 0; | |
display: flex; | |
flex-direction: column; | |
overflow: hidden; | |
transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1); | |
padding: 0; | |
box-shadow: 4px 0 20px -5px rgba(0,0,0,0.3), 1px 0 0 0 rgba(88, 166, 255, 0.1); | |
border-top-left-radius: var(--container-radius); | |
border-bottom-left-radius: var(--container-radius); | |
} | |
.sidebar-content { | |
height: 100%; | |
display: flex; | |
flex-direction: column; | |
padding: 16px; | |
min-width: 208px; | |
} | |
.sidebar-header { | |
display: flex; | |
align-items: center; | |
gap: 12px; | |
padding-bottom: 24px; | |
border-bottom: 1px solid var(--border-color); | |
margin-bottom: 24px; | |
flex-shrink: 0; | |
} | |
.sidebar-header .logo { | |
font-size: 28px; | |
color: var(--accent-color); | |
text-shadow: 0 0 10px var(--accent-glow); | |
min-width: 36px; | |
text-align: center; | |
} | |
.new-chat-btn { | |
background: linear-gradient(135deg, #58a6ff, #3a8dff); | |
border: none; | |
color: #ffffff; | |
padding: 12px; | |
border-radius: 8px; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
display: flex; | |
align-items: center; | |
justify-content: flex-start; | |
gap: 16px; | |
font-size: 14px; | |
font-weight: 600; | |
margin-bottom: 24px; | |
box-shadow: 0 4px 15px rgba(88, 166, 255, 0.2); | |
} | |
.new-chat-btn:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 6px 20px rgba(88, 166, 255, 0.3); | |
} | |
.nav-menu .nav-item { | |
display: flex; | |
align-items: center; | |
gap: 16px; | |
padding: 12px; | |
border-radius: 8px; | |
margin-bottom: 8px; | |
cursor: pointer; | |
transition: background-color 0.3s ease, color 0.3s ease; | |
font-weight: 500; | |
color: var(--text-color-primary); | |
} | |
.nav-menu .nav-item:hover { background-color: var(--bg-color-medium); } | |
.nav-menu .nav-item.active { | |
background-color: var(--accent-color); | |
color: var(--bg-color-dark); | |
font-weight: 600; | |
box-shadow: 0 0 15px var(--accent-glow); | |
} | |
.nav-menu .nav-item.active .material-icons { color: var(--bg-color-dark); } | |
.nav-menu .nav-item .material-icons { | |
color: var(--text-color-secondary); | |
transition: color 0.3s ease; | |
min-width: 24px; | |
} | |
.nav-menu .nav-item:hover .material-icons { color: var(--text-color-primary); } | |
.item-text { | |
opacity: 1; /* Always visible */ | |
width: auto; /* Always auto width */ | |
white-space: nowrap; | |
} | |
.history-section { | |
margin-top: 24px; | |
padding-top: 24px; | |
border-top: 1px solid var(--border-color); | |
flex-grow: 1; | |
overflow-y: auto; | |
min-height: 100px; | |
} | |
.history-title { | |
font-size: 12px; | |
font-weight: 600; | |
color: var(--text-color-secondary); | |
text-transform: uppercase; | |
letter-spacing: 0.5px; | |
margin-bottom: 12px; | |
padding-left: 12px; | |
} | |
.chat-history-list .history-item { | |
display: flex; | |
align-items: center; | |
padding: 10px 12px; | |
font-size: 14px; | |
border-radius: 6px; | |
cursor: pointer; | |
white-space: nowrap; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
margin-bottom: 4px; | |
color: var(--text-color-secondary); | |
position: relative; | |
} | |
.chat-history-list .history-item:hover { | |
background-color: var(--bg-color-medium); | |
color: var(--text-color-primary); | |
} | |
.chat-history-list .history-item.active { | |
background-color: var(--user-bubble-bg); | |
color: var(--text-color-primary); | |
font-weight: 500; | |
} | |
.history-item .history-title-text { | |
flex-grow: 1; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
margin-left: 16px; | |
} | |
.response-content{ | |
margin-top:-40px; | |
} | |
.history-item .history-title-input { | |
width: 100%; | |
background: var(--bg-color-dark); | |
border: 1px solid var(--accent-color); | |
border-radius: 4px; | |
color: var(--text-color-primary); | |
padding: 2px 4px; | |
} | |
.history-actions { | |
display: flex; | |
align-items: center; | |
gap: 8px; | |
margin-left: 8px; | |
} | |
.history-item:hover .history-actions { | |
display: flex; | |
} | |
.history-icon { | |
font-size: 16px; | |
cursor: pointer; | |
color: var(--text-color-secondary); | |
} | |
.history-icon:hover { | |
color: var(--accent-color); | |
} | |
.api-key-section { | |
padding-top: 24px; | |
margin-top: 24px; | |
border-top: 1px solid var(--border-color); | |
border-radius: 8px; | |
} | |
.api-key-group { | |
margin-bottom: 12px; | |
} | |
.api-key-group:last-child { | |
margin-bottom: 0; | |
} | |
.api-key-section label { | |
font-size: 12px; | |
font-weight: 600; | |
color: var(--text-color-primary); | |
letter-spacing: 0.5px; | |
margin-bottom: 6px; | |
display: block; | |
} | |
.api-key-section input { | |
width: 100%; | |
box-sizing: border-box; | |
background-color: var(--bg-color-dark); | |
border: 1px solid var(--border-color); | |
border-radius: 6px; | |
padding: 8px; | |
color: var(--text-color-primary); | |
font-size: 12px; | |
transition: border-color 0.3s, box-shadow 0.3s; | |
} | |
.api-key-section input:focus { | |
outline: none; | |
border-color: var(--accent-color); | |
box-shadow: 0 0 8px var(--accent-glow); | |
} | |
.input-error { | |
border-color: var(--error-color) !important; | |
box-shadow: 0 0 10px rgba(248, 81, 73, 0.5) !important; | |
} | |
.main-content { | |
flex-grow: 1; | |
display: flex; | |
flex-direction: column; | |
overflow: hidden; | |
background-image: radial-gradient(circle at 50% 0%, rgba(255, 255, 255, 0.05), transparent 40%); | |
} | |
.content-window { | |
flex-grow: 1; | |
display: flex; | |
flex-direction: column; | |
overflow: hidden; | |
} | |
.prompt-view { | |
display: none; | |
} | |
.prompt-view.active { | |
display: flex; | |
flex-direction: column; | |
} | |
#home-screen { | |
justify-content: center; | |
align-items: center; | |
text-align: center; | |
padding: 40px; | |
display: none; | |
} | |
#home-screen.active { display: flex; } | |
#home-screen .logo { font-size: 60px; color: var(--accent-color); } | |
#home-screen h1 { font-size: 32px; margin: 16px 0 8px; color: var(--text-color-primary); } | |
#home-screen p { color: var(--text-color-secondary); max-width: 450px; line-height: 1.6; } | |
/* MODIFIED: chat-history-display is now a simple container */ | |
.chat-history-display { | |
flex-grow: 1; | |
overflow: hidden; | |
position: relative; | |
} | |
/* ADDED: New class for individual chat session containers */ | |
.chat-session-container { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
padding: 24px; | |
overflow-y: auto; | |
display: flex; | |
flex-direction: column; | |
gap: 16px; | |
box-sizing: border-box; | |
} | |
.chat-message-container { | |
display: flex; | |
flex-direction: column; | |
max-width: 85%; | |
} | |
.chat-message { | |
display: flex; | |
gap: 6px; | |
align-items: flex-start; | |
} | |
.chat-message.user { justify-content: flex-end; } | |
.chat-message-container.user { align-items: flex-end; } | |
.chat-message-container.model { align-items: flex-start; } | |
.chat-avatar { | |
width: 36px; | |
height: 36px; | |
border-radius: 50%; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
flex-shrink: 0; | |
font-weight: 600; | |
border: 1px solid var(--border-color); | |
} | |
.chat-message.user .chat-avatar { background-color: var(--bg-color-medium); } | |
.chat-message.model .chat-avatar { background-color: var(--accent-color); } | |
.chat-message.user .chat-avatar .material-icons { color: var(--text-color-primary); } | |
.chat-message.model .chat-avatar .material-icons { color: var(--bg-color-dark); font-size: 20px; } | |
.chat-bubble { | |
padding: 12px 18px; | |
border-radius: 20px; | |
max-width: 100%; | |
line-height: 1.6; | |
word-wrap: break-word; | |
white-space: pre-wrap; | |
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); | |
border: 1px solid transparent; | |
position: relative; | |
} | |
.copy-icon-bubble { | |
position: absolute; | |
top: 8px; | |
right: 8px; | |
color: var(--text-color-secondary); | |
background-color: var(--bg-color-dark); | |
border-radius: 50%; | |
padding: 4px; | |
cursor: pointer; | |
opacity: 0; | |
transition: opacity 0.2s; | |
font-size: 18px; | |
} | |
.chat-bubble:hover .copy-icon-bubble { | |
opacity: 1; | |
} | |
.copy-icon-bubble:hover { | |
color: var(--accent-color); | |
} | |
.model-response-header { | |
font-size: 10px; | |
font-weight: 500; | |
color: var(--text-color-secondary); | |
margin-bottom: 4px; | |
padding-bottom: 4px; | |
margin-top:-50px; | |
display: flex; | |
gap: 2px; | |
align-items: center; | |
opacity: 0.7; | |
border-bottom: 1px solid var(--border-color); | |
} | |
.model-response-header .material-icons { font-size: 12px; } | |
.chat-message.user .chat-bubble { | |
background: linear-gradient(135deg, #58a6ff, #3a8dff); | |
color: #ffffff; | |
border-radius: 20px 4px 20px 20px; | |
} | |
.chat-message.model .chat-bubble { | |
background-color: var(--user-bubble-bg); | |
color: var(--text-color-primary); | |
border-color: var(--border-color); | |
border-radius: 4px 20px 20px 20px; | |
} | |
.chat-message.error .chat-bubble { | |
background-color: rgba(248, 81, 73, 0.1); | |
border-color: var(--error-color); | |
color: var(--error-color); | |
} | |
.chat-bubble pre { | |
background: var(--bg-color-dark) !important; | |
padding: 12px; | |
border-radius: 8px; | |
white-space: pre-wrap; | |
word-break: break-all; | |
font-family: 'Courier New', Courier, monospace; | |
font-size: 14px; | |
margin: 8px 0 0 0; | |
} | |
.chat-bubble img { | |
max-width: 100%; | |
border-radius: 8px; | |
margin-top: 8px; | |
} | |
.prompt-actions-bar { | |
display: flex; | |
align-items: center; | |
gap: 12px; | |
padding: 8px 0 0 0; | |
justify-content: flex-end; | |
margin-right: 52px; | |
} | |
.prompt-actions-bar .action-icon { | |
cursor: pointer; | |
color: var(--text-color-secondary); | |
transition: color 0.2s; | |
display: flex; | |
align-items: center; | |
gap: 4px; | |
font-size: 13px; | |
} | |
.prompt-actions-bar .action-icon:hover { | |
color: var(--accent-color); | |
} | |
.prompt-actions-bar .action-icon .material-icons { | |
font-size: 18px; | |
} | |
.prompt-area { | |
padding: 16px 24px; | |
border-top: 1px solid var(--border-color); | |
background: var(--bg-color-dark); | |
margin-top: auto; | |
} | |
.prompt-examples { | |
display: flex; | |
gap: 8px; | |
margin-bottom: 12px; | |
flex-wrap: wrap; | |
} | |
.prompt-example-btn { | |
background: var(--bg-color-medium); | |
border: 1px solid var(--border-color); | |
color: var(--text-color-secondary); | |
padding: 6px 12px; | |
border-radius: 6px; | |
cursor: pointer; | |
transition: all 0.2s; | |
font-size: 12px; | |
} | |
.prompt-example-btn:hover { | |
border-color: var(--accent-color); | |
color: var(--text-color-primary); | |
} | |
.input-wrapper { | |
display: flex; | |
gap: 12px; | |
align-items: flex-end; | |
} | |
textarea { | |
flex-grow: 1; | |
background-color: var(--bg-color-dark); | |
border: 1px solid var(--border-color); | |
border-radius: 8px; | |
padding: 12px 16px; | |
color: var(--text-color-primary); | |
font-family: 'Inter', sans-serif; | |
font-size: 16px; | |
resize: none; | |
height: 50px; | |
transition: height 0.2s; | |
} | |
textarea:focus { | |
outline: none; | |
border-color: var(--accent-color); | |
box-shadow: 0 0 8px var(--accent-glow); | |
} | |
.image-uploader { | |
width: 50px; | |
height: 50px; | |
border: 2px dashed var(--border-color); | |
border-radius: 8px; | |
cursor: pointer; | |
position: relative; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
transition: border-color 0.3s ease; | |
flex-shrink: 0; | |
overflow: hidden; | |
} | |
.image-uploader:hover { border-color: var(--accent-color); } | |
.image-uploader .material-icons { font-size: 24px; color: var(--text-color-secondary); transition: opacity 0.2s; } | |
.image-uploader.loading .spinner { display: block; } | |
.image-uploader.loading .material-icons, .image-uploader.has-image .material-icons { display: none; } | |
.spinner { | |
display: none; | |
width: 24px; | |
height: 24px; | |
border: 3px solid var(--border-color); | |
border-top-color: var(--accent-color); | |
border-radius: 50%; | |
animation: spin 1s linear infinite; | |
} | |
.image-preview { | |
display: none; | |
width: 100%; | |
height: 100%; | |
object-fit: cover; | |
} | |
.image-uploader.has-image .image-preview { display: block; } | |
@keyframes spin { to { transform: rotate(360deg); } } | |
.right-panel { | |
width: 250px; | |
padding: 24px; | |
border-left: 1px solid var(--border-color); | |
background-color: rgba(13, 17, 23, 0.9); | |
flex-shrink: 0; | |
display: flex; | |
flex-direction: column; | |
box-shadow: -4px 0 20px -5px rgba(0,0,0,0.3), -1px 0 0 0 rgba(88, 166, 255, 0.1); | |
border-top-right-radius: var(--container-radius); | |
border-bottom-right-radius: var(--container-radius); | |
} | |
.panel-header { | |
font-size: 14px; | |
color: var(--text-color-secondary); | |
font-weight: 500; | |
margin-bottom: 16px; | |
} | |
.right-panel ul { list-style: none; padding: 0; margin: 0; } | |
.model-item { | |
padding: 12px; | |
border-radius: 6px; | |
cursor: pointer; | |
font-size: 14px; | |
margin-bottom: 8px; | |
font-weight: 500; | |
transition: background-color 0.3s; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
white-space: nowrap; | |
color: var(--text-color-primary); | |
position: relative; | |
padding-left: 30px; | |
} | |
.model-item:hover { background-color: var(--bg-color-medium); } | |
.model-item.active { | |
background-color: var(--accent-color); | |
color: #ffffff; | |
font-weight: 600; | |
} | |
.model-item.groq-model::before { | |
content: 'G'; | |
position: absolute; | |
left: 10px; | |
top: 50%; | |
transform: translateY(-50%); | |
font-weight: bold; | |
color: var(--groq-color); | |
font-size: 14px; | |
} | |
.parameters-section { | |
margin-top: 24px; | |
padding-top: 24px; | |
border-top: 1px solid var(--border-color); | |
} | |
.parameter-control { margin-bottom: 16px; } | |
.parameter-control label { | |
display: flex; | |
justify-content: space-between; | |
font-size: 14px; | |
margin-bottom: 8px; | |
color: var(--text-color-primary); | |
} | |
.parameter-control label span:last-child { | |
font-weight: 600; | |
color: var(--text-color-primary); | |
} | |
.parameter-control input[type="range"] { | |
-webkit-appearance: none; | |
width: 100%; | |
height: 4px; | |
background: var(--border-color); | |
border-radius: 2px; | |
outline: none; | |
} | |
.parameter-control input[type="range"]::-webkit-slider-thumb { | |
-webkit-appearance: none; | |
appearance: none; | |
width: 16px; | |
height: 16px; | |
background: var(--accent-color); | |
cursor: pointer; | |
border-radius: 50%; | |
box-shadow: 0 0 8px var(--accent-glow); | |
} | |
.action-button { | |
background-color: var(--accent-color); | |
border: none; | |
color: white; | |
padding: 0 16px; | |
border-radius: 8px; | |
font-size: 16px; | |
cursor: pointer; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
transition: background-color 0.3s, box-shadow 0.3s; | |
height: 50px; | |
flex-shrink: 0; | |
} | |
.action-button:hover { | |
background-color: #4a9eff; | |
box-shadow: 0 0 15px var(--accent-glow); | |
} | |
.action-button:disabled { | |
background-color: var(--border-color); | |
cursor: not-allowed; | |
box-shadow: none; | |
} | |
.action-button.stop-generating { | |
background-color: var(--error-color); | |
} | |
.action-button.stop-generating:hover { | |
background-color: #d9443b; | |
box-shadow: 0 0 15px rgba(248, 81, 73, 0.5); | |
} | |
@media (max-width: 1100px) { | |
.studio-container { flex-direction: column; height: auto; max-height: none; } | |
.sidebar { | |
width: 100%; | |
border-right: none; border-bottom: 1px solid var(--border-color); | |
flex-direction: row; align-items: center; justify-content: space-between; padding: 16px; | |
overflow: visible; | |
} | |
.sidebar:hover { width: 100%; } | |
.sidebar-header { border: none; margin: 0; padding: 0; } | |
.nav-menu { display: flex; gap: 8px; } | |
.nav-menu .nav-item { flex-direction: column; padding: 8px; gap: 4px; font-size: 12px; } | |
.right-panel { width: 100%; border-left: none; border-top: 1px solid var(--border-color); } | |
} | |
@media (max-width: 768px) { | |
body { padding: 0; } | |
.studio-container { border-radius: 0; min-height: 100vh; } | |
.sidebar { flex-direction: column; align-items: stretch; } | |
.nav-menu { justify-content: center; } | |
} | |
</style> | |
</head> | |
<body> | |
<div class="studio-container"> | |
<aside class="sidebar"> | |
<div class="sidebar-content"> | |
<div class="sidebar-header"> | |
<span class="material-icons logo">auto_awesome</span><h1 class="item-text">LLM Studio</h1> | |
</div> | |
<button class="new-chat-btn" id="new-chat-btn"><span class="material-icons">add_circle</span><span class="item-text">New Chat</span></button> | |
<nav class="nav-menu"> | |
<div class="nav-item active" data-target="text-generation"><span class="material-icons">text_fields</span><span class="item-text">Text Generation</span></div> | |
<div class="nav-item" data-target="image-to-text"><span class="material-icons">image</span><span class="item-text">Image to Text</span></div> | |
<div class="nav-item" data-target="text-classification"><span class="material-icons">label</span><span class="item-text">Text Classification</span></div> | |
</nav> | |
<div class="history-section"> | |
<h3 class="history-title item-text">History</h3> | |
<div class="chat-history-list" id="chat-history-list"> | |
</div> | |
</div> | |
</div> | |
</aside> | |
<main class="main-content"> | |
<div id="home-screen" class="content-window active"> | |
<span class="material-icons logo">auto_awesome</span> | |
<h1>Welcome to LLM Studio</h1> | |
<p>Select a mode from the left, or click "New Chat" to begin a new conversation. Your chat history will be saved here for this session.</p> | |
</div> | |
<div id="chat-view" class="content-window" style="display:none;"> | |
<div class="chat-history-display" id="chat-history-display"></div> | |
<div id="prompt-container"> | |
<div id="text-generation" class="prompt-view active"> | |
<div class="prompt-area"> | |
<div class="prompt-examples" id="text-generation-examples"></div> | |
<div class="input-wrapper"> | |
<textarea id="text-generation-prompt" placeholder="Enter your prompt here..."></textarea> | |
<button class="action-button" data-target="text-generation"><span class="material-icons">send</span></button> | |
</div> | |
</div> | |
</div> | |
<div id="image-to-text" class="prompt-view"> | |
<div class="prompt-area"> | |
<div class="prompt-examples" id="image-to-text-examples"></div> | |
<div class="input-wrapper"> | |
<div id="image-uploader" class="image-uploader"> | |
<input type="file" id="image-upload-input" accept="image/*" style="display: none;"> | |
<span class="material-icons">add_photo_alternate</span> | |
<div class="spinner"></div> | |
<img class="image-preview" id="image-preview" src="" alt="Image Preview"/> | |
</div> | |
<textarea id="image-to-text-prompt" placeholder="Optionally, add instructions..."></textarea> | |
<button class="action-button" data-target="image-to-text" disabled><span class="material-icons">send</span></button> | |
</div> | |
</div> | |
</div> | |
<div id="text-classification" class="prompt-view"> | |
<div class="prompt-area"> | |
<div class="prompt-examples" id="text-classification-examples"></div> | |
<div class="input-wrapper"> | |
<textarea id="text-classification-prompt" placeholder="Enter text to classify..."></textarea> | |
<button class="action-button" data-target="text-classification"><span class="material-icons">send</span></button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</main> | |
<aside class="right-panel"> | |
<div> | |
<p class="panel-header">MODELS</p> | |
<ul id="model-list"> | |
</ul> | |
</div> | |
<div class="parameters-section"> | |
<p class="panel-header">PARAMETERS</p> | |
<div class="parameter-control"> | |
<label for="temperature"><span>Temperature</span><span id="temperature-value">0.9</span></label> | |
<input type="range" id="temperature" min="0" max="1" step="0.1" value="0.9"> | |
</div> | |
<div class="parameter-control"> | |
<label for="top-p"><span>Top-P</span><span id="top-p-value">1.0</span></label> | |
<input type="range" id="top-p" min="0" max="1" step="0.1" value="1.0"> | |
</div> | |
</div> | |
<div class="api-key-section"> | |
<div class="api-key-group"> | |
<label for="groq-token">Groq API Key</label> | |
<input type="password" id="groq-token" placeholder="gsk_..."> | |
</div> | |
<div class="api-key-group"> | |
<label for="hf-token">Hugging Face Token</label> | |
<input type="password" id="hf-token" placeholder="hf_..."> | |
</div> | |
</div> | |
</aside> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', () => { | |
const navItems = document.querySelectorAll('.nav-item'); | |
const promptViews = document.querySelectorAll('.prompt-view'); | |
const modelList = document.getElementById('model-list'); | |
const newChatBtn = document.getElementById('new-chat-btn'); | |
const chatHistoryList = document.getElementById('chat-history-list'); | |
const chatHistoryDisplay = document.getElementById('chat-history-display'); | |
const homeScreen = document.getElementById('home-screen'); | |
const chatView = document.getElementById('chat-view'); | |
const tempSlider = document.getElementById('temperature'); | |
const tempValue = document.getElementById('temperature-value'); | |
const topPSlider = document.getElementById('top-p'); | |
const topPValue = document.getElementById('top-p-value'); | |
const hfTokenInput = document.getElementById('hf-token'); | |
const groqTokenInput = document.getElementById('groq-token'); | |
const imageUploader = document.getElementById('image-uploader'); | |
const imagePreview = document.getElementById('image-preview'); | |
let activeMode = 'text-generation'; | |
let activeModelInfo = {}; | |
let temperature = 0.9; | |
let topP = 1.0; | |
let uploadedImageBase64 = null; | |
let uploadedImageDataUrl = null; | |
let chatSessions = {}; | |
let activeChatId = null; | |
let editingMessageId = null; | |
let currentRequestController = null; | |
let currentTypingInterval = null; | |
const taskConfig = { | |
'text-generation': { | |
examples: [ | |
{ title: "Explain quantum computing", prompt: "Explain quantum computing in simple terms." }, | |
{ title: "Python factorial function", prompt: "Write a Python function that calculates the factorial of a number." }, | |
{ title: "Email to boss", prompt: "Write a short, professional email to my boss requesting a meeting next week." } | |
], | |
models: [ | |
{ id: "llama3-8b-8192", name: "Llama3 8B (Groq)", url: "https://api.groq.com/openai/v1/chat/completions", type: "chat", isGroq: true }, | |
{ id: "qwen/qwen3-32b", name: "qwen/qwen3-32b (Groq)", url: "https://api.groq.com/openai/v1/chat/completions", type: "chat", isGroq: true }, | |
{ id: "gemma2-9b-it", name: "gemma2-9b-it (Groq)", url: "https://api.groq.com/openai/v1/chat/completions", type: "chat", isGroq: true }, | |
{ id: "deepseek-ai/DeepSeek-V3", name: "DeepSeek-V3 (HF)", url: "https://router.huggingface.co/nebius/v1/chat/completions", type: "chat" }, | |
] | |
}, | |
'image-to-text': { | |
examples: [ | |
{ title: "Describe scene", prompt: "Describe this scene in detail." }, | |
{ title: "Identify objects", prompt: "List all the objects you can identify." }, | |
{ title: "Suggest a caption", prompt: "Suggest a creative social media caption." } | |
], | |
models: [ | |
{ id: "meta-llama/llama-4-scout-17b-16e-instruct", name: "meta-llama/llama-4-scout (Groq)", url: "https://api.groq.com/openai/v1/chat/completions", type: "image-to-text",isGroq: true }, | |
{ id: "meta-llama/llama-4-maverick-17b-128e-instruct", name: "meta-llama/llama-4-maverick (Groq)", url: "https://api.groq.com/openai/v1/chat/completions", type: "image-to-text",isGroq: true } | |
] | |
}, | |
'text-classification': { | |
examples: [ | |
{ title: "Sentiment analysis", prompt: "I'm so frustrated with customer service!" }, | |
{ title: "Topic identification", prompt: "The new legislation aims to reduce carbon emissions." }, | |
{ title: "Spam detection", prompt: "CLICK HERE to claim your prize!" } | |
], | |
models: [ | |
{ id: "distilbert-base-uncased-finetuned-sst-2-english", name: "DistilBERT SST-2", url: "https://router.huggingface.co/hf-inference/models/distilbert/distilbert-base-uncased-finetuned-sst-2-english", type: "text-classification" }, | |
{ id: "SamLowe/roberta-base-go_emotions", name: "RoBERTa GoEmotions", url: "https://api-inference.huggingface.co/models/SamLowe/roberta-base-go_emotions", type: "text-classification" }, | |
{ id: "nlptown/bert-base-multilingual-uncased-sentiment", name: "BERT Multilingual", url: "https://api-inference.huggingface.co/models/nlptown/bert-base-multilingual-uncased-sentiment", type: "text-classification" } | |
] | |
} | |
}; | |
function init() { | |
populatePromptExamples(); | |
setupListeners(); | |
switchMode(activeMode); | |
updateAllSendButtonStates(); | |
} | |
function populatePromptExamples() { | |
for (const mode in taskConfig) { | |
const containerId = `${mode}-examples`; | |
const container = document.getElementById(containerId); | |
if(container) { | |
container.innerHTML = ''; | |
taskConfig[mode].examples.forEach(ex => { | |
const btn = document.createElement('button'); | |
btn.className = 'prompt-example-btn'; | |
btn.textContent = ex.title; | |
btn.onclick = () => { | |
const promptTextarea = document.getElementById(`${mode}-prompt`); | |
promptTextarea.value = ex.prompt; | |
promptTextarea.focus(); | |
autoGrowTextarea(promptTextarea); | |
updateSendButtonState(mode); | |
}; | |
container.appendChild(btn); | |
}); | |
} | |
} | |
} | |
function setupListeners() { | |
navItems.forEach(item => item.addEventListener('click', () => switchMode(item.dataset.target))); | |
newChatBtn.addEventListener('click', startNewChat); | |
tempSlider.addEventListener('input', (e) => { | |
temperature = parseFloat(e.target.value); | |
tempValue.textContent = temperature.toFixed(1); | |
}); | |
topPSlider.addEventListener('input', (e) => { | |
topP = parseFloat(e.target.value); | |
topPValue.textContent = topP.toFixed(1); | |
}); | |
document.querySelectorAll('textarea').forEach(textarea => { | |
textarea.addEventListener('input', () => { | |
autoGrowTextarea(textarea); | |
const mode = textarea.id.replace('-prompt', ''); | |
updateSendButtonState(mode); | |
}); | |
}); | |
[hfTokenInput, groqTokenInput].forEach(input => { | |
input.addEventListener('input', () => input.classList.remove('input-error')); | |
}); | |
setupImageUploader(); | |
setupActionButtons(); | |
} | |
function updateSendButtonState(mode) { | |
if (mode === 'image-to-text') return; | |
const promptTextarea = document.getElementById(`${mode}-prompt`); | |
const sendButton = document.querySelector(`#${mode} .action-button`); | |
if (promptTextarea && sendButton) { | |
sendButton.disabled = promptTextarea.value.trim() === ''; | |
} | |
} | |
function updateAllSendButtonStates() { | |
['text-generation', 'text-classification'].forEach(updateSendButtonState); | |
} | |
function setupImageUploader() { | |
const uploaderInput = document.getElementById('image-upload-input'); | |
imageUploader.addEventListener('click', () => uploaderInput.click()); | |
imageUploader.addEventListener('dragover', (e) => { e.preventDefault(); imageUploader.style.borderColor = 'var(--accent-color)'; }); | |
imageUploader.addEventListener('dragleave', (e) => { imageUploader.style.borderColor = 'var(--border-color)'; }); | |
imageUploader.addEventListener('drop', (e) => { | |
e.preventDefault(); | |
imageUploader.style.borderColor = 'var(--border-color)'; | |
if (e.dataTransfer.files.length > 0) handleImageFile(e.dataTransfer.files[0]); | |
}); | |
uploaderInput.addEventListener('change', (e) => { | |
if (e.target.files.length > 0) handleImageFile(e.target.files[0]); | |
}); | |
} | |
function autoGrowTextarea(element) { | |
element.style.height = 'auto'; | |
element.style.height = (element.scrollHeight) + 'px'; | |
} | |
function switchMode(targetMode) { | |
activeMode = targetMode; | |
navItems.forEach(i => i.classList.toggle('active', i.dataset.target === targetMode)); | |
promptViews.forEach(v => { | |
v.classList.toggle('active', v.id === targetMode) | |
v.style.display = v.id === targetMode ? 'flex' : 'none'; | |
}); | |
renderModelList(targetMode); | |
if (activeChatId) { | |
chatSessions[activeChatId].mode = targetMode; | |
} | |
} | |
function renderModelList(mode) { | |
modelList.innerHTML = ''; | |
const models = taskConfig[mode].models; | |
if (!models || models.length === 0) return; | |
setActiveModel(models[0]); | |
models.forEach(model => { | |
const item = document.createElement('li'); | |
item.className = 'model-item'; | |
item.textContent = model.name; | |
item.title = model.id; | |
item.dataset.modelId = model.id; | |
if (model.isGroq) { | |
item.classList.add('groq-model'); | |
} | |
if (model.id === activeModelInfo.id) { | |
item.classList.add('active'); | |
} | |
item.onclick = () => { | |
setActiveModel(model); | |
document.querySelectorAll('.model-item').forEach(i => i.classList.remove('active')); | |
item.classList.add('active'); | |
}; | |
modelList.appendChild(item); | |
}); | |
} | |
function setActiveModel(model) { | |
activeModelInfo = model; | |
} | |
// MODIFIED: startNewChat now creates a new session container and hides others | |
function startNewChat() { | |
editingMessageId = null; | |
const newChatId = `chat_${Date.now()}`; | |
activeChatId = newChatId; | |
chatSessions[newChatId] = { | |
id: newChatId, | |
title: "New Chat", | |
mode: activeMode, | |
messages: [] | |
}; | |
// Hide all other session containers | |
document.querySelectorAll('.chat-session-container').forEach(c => c.style.display = 'none'); | |
// Create and append a new container for this chat | |
const newSessionContainer = document.createElement('div'); | |
newSessionContainer.className = 'chat-session-container'; | |
newSessionContainer.id = `session-container-${newChatId}`; | |
newSessionContainer.style.display = 'flex'; // Make it visible | |
chatHistoryDisplay.appendChild(newSessionContainer); | |
renderChatHistory(); | |
homeScreen.classList.remove('active'); | |
homeScreen.style.display = 'none'; | |
chatView.style.display = 'flex'; | |
switchMode(activeMode); | |
resetImageUploader(); | |
updateAllSendButtonStates(); | |
} | |
// MODIFIED: loadChat now shows/hides containers instead of clearing the display | |
function loadChat(chatId) { | |
editingMessageId = null; | |
activeChatId = chatId; | |
const chat = chatSessions[chatId]; | |
// Hide all session containers | |
document.querySelectorAll('.chat-session-container').forEach(c => c.style.display = 'none'); | |
let targetContainer = document.getElementById(`session-container-${chatId}`); | |
// If the container for this chat doesn't exist, create and populate it | |
if (!targetContainer) { | |
targetContainer = document.createElement('div'); | |
targetContainer.className = 'chat-session-container'; | |
targetContainer.id = `session-container-${chatId}`; | |
chat.messages.forEach(msg => { | |
const messageEl = createMessageElement(msg); // Use helper to create element | |
if (messageEl) targetContainer.appendChild(messageEl); | |
}); | |
chatHistoryDisplay.appendChild(targetContainer); | |
} | |
// Show the target container | |
targetContainer.style.display = 'flex'; | |
homeScreen.classList.remove('active'); | |
homeScreen.style.display = 'none'; | |
chatView.style.display = 'flex'; | |
switchMode(chat.mode); | |
renderChatHistory(); | |
} | |
function renderChatHistory() { | |
chatHistoryList.innerHTML = ''; | |
Object.values(chatSessions).reverse().forEach(chat => { | |
const item = document.createElement('div'); | |
item.className = 'history-item'; | |
item.dataset.chatId = chat.id; | |
item.innerHTML = ` | |
<span class="material-icons">chat_bubble</span> | |
<span class="history-title-text item-text">${chat.title}</span> | |
<div class="history-actions"> | |
<span class="material-icons history-icon" onclick="editChatTitle('${chat.id}', event)">edit</span> | |
<span class="material-icons history-icon" onclick="deleteChat('${chat.id}', event)">delete</span> | |
</div> | |
`; | |
if(chat.id === activeChatId) { | |
item.classList.add('active'); | |
} | |
item.addEventListener('click', (e) => { | |
if (e.target.classList.contains('history-icon')) return; | |
loadChat(chat.id) | |
}); | |
chatHistoryList.appendChild(item); | |
}); | |
} | |
window.deleteChat = (chatId, event) => { | |
event.stopPropagation(); | |
if (confirm(`Are you sure you want to delete this chat?`)) { | |
delete chatSessions[chatId]; | |
// Also remove the chat container from the DOM | |
const chatContainer = document.getElementById(`session-container-${chatId}`); | |
if (chatContainer) chatContainer.remove(); | |
if (activeChatId === chatId) { | |
activeChatId = null; | |
chatView.style.display = 'none'; | |
homeScreen.style.display = 'flex'; | |
homeScreen.classList.add('active'); | |
} | |
renderChatHistory(); | |
} | |
}; | |
window.editChatTitle = (chatId, event) => { | |
event.stopPropagation(); | |
const item = event.target.closest('.history-item'); | |
const titleSpan = item.querySelector('.history-title-text'); | |
const currentTitle = titleSpan.textContent; | |
const input = document.createElement('input'); | |
input.type = 'text'; | |
input.className = 'history-title-input'; | |
input.value = currentTitle; | |
titleSpan.replaceWith(input); | |
input.focus(); | |
const saveTitle = () => { | |
const newTitle = input.value.trim(); | |
if (newTitle && newTitle !== currentTitle) { | |
chatSessions[chatId].title = newTitle; | |
} | |
renderChatHistory(); | |
}; | |
input.addEventListener('blur', saveTitle); | |
input.addEventListener('keydown', (e) => { | |
if (e.key === 'Enter') saveTitle(); | |
else if (e.key === 'Escape') renderChatHistory(); | |
}); | |
}; | |
function handleImageFile(file) { | |
if (!file.type.startsWith('image/')) return; | |
imageUploader.classList.remove('has-image'); | |
imageUploader.classList.add('loading'); | |
const reader = new FileReader(); | |
reader.onload = (e) => { | |
uploadedImageDataUrl = e.target.result; | |
uploadedImageBase64 = uploadedImageDataUrl.split(',')[1]; | |
imagePreview.src = uploadedImageDataUrl; | |
imageUploader.classList.remove('loading'); | |
imageUploader.classList.add('has-image'); | |
document.querySelector('#image-to-text .action-button').disabled = false; | |
} | |
reader.readAsDataURL(file); | |
} | |
function resetImageUploader() { | |
imageUploader.classList.remove('has-image', 'loading'); | |
imagePreview.src = ''; | |
uploadedImageDataUrl = null; | |
uploadedImageBase64 = null; | |
document.querySelector('#image-to-text .action-button').disabled = true; | |
} | |
function addMessageToSession(sender, content, options = {}) { | |
if (!activeChatId) return null; | |
const messageId = `msg_${Date.now()}`; | |
const message = { | |
id: messageId, | |
sender, | |
content, | |
isHtml: options.isHtml || false, | |
isError: options.isError || false, | |
}; | |
chatSessions[activeChatId].messages.push(message); | |
return messageId; | |
} | |
// ADDED: Refactored function to create a message element without displaying it | |
function createMessageElement(message, isTyping = false) { | |
const messageWrapper = document.createElement('div'); | |
messageWrapper.className = `chat-message-container ${message.sender}`; | |
messageWrapper.id = message.id; | |
const messageDiv = document.createElement('div'); | |
messageDiv.className = `chat-message ${message.sender}`; | |
if(message.isError) { messageDiv.classList.add('error'); } | |
const avatar = `<div class="chat-avatar"><span class="material-icons">${message.sender === 'user' ? 'person' : 'auto_awesome'}</span></div>`; | |
const bubble = document.createElement('div'); | |
bubble.className = 'chat-bubble'; | |
if (message.isHtml) { | |
bubble.innerHTML = message.content; | |
} else { | |
bubble.textContent = message.content; | |
} | |
if (message.sender === 'model' && !isTyping && !message.isError) { | |
bubble.innerHTML += `<span class="material-icons copy-icon-bubble" onclick="copyResponse(event)" title="Copy response">content_copy</span>`; | |
} | |
if (message.sender === 'user') { | |
messageDiv.innerHTML = bubble.outerHTML + avatar; | |
} else { | |
messageDiv.innerHTML = avatar + bubble.outerHTML; | |
} | |
messageWrapper.appendChild(messageDiv); | |
// Add edit/resend bar for user messages | |
if (message.sender === 'user' && !isTyping && chatSessions[activeChatId]?.mode !== 'image-to-text') { | |
const actionBar = document.createElement('div'); | |
actionBar.className = 'prompt-actions-bar'; | |
actionBar.innerHTML = ` | |
<span class="action-icon" onclick="editMessage('${message.id}')" title="Edit prompt"> | |
<span class="material-icons">edit</span> | |
</span> | |
`; | |
messageWrapper.appendChild(actionBar); | |
} | |
return messageWrapper; | |
} | |
// MODIFIED: displayMessage now finds the active container and appends the element | |
function displayMessage(message, isTyping = false) { | |
const activeSessionContainer = document.getElementById(`session-container-${activeChatId}`); | |
if (!activeSessionContainer) { | |
console.error('Active session container not found!'); | |
return null; | |
} | |
const messageWrapper = createMessageElement(message, isTyping); | |
if (messageWrapper) { | |
activeSessionContainer.appendChild(messageWrapper); | |
activeSessionContainer.scrollTop = activeSessionContainer.scrollHeight; | |
} | |
return messageWrapper; | |
} | |
function streamResponse(element, text, mode) { | |
let i = 0; | |
element.innerHTML = ''; | |
currentRequestController = new AbortController(); | |
const signal = currentRequestController.signal; | |
toggleSendStopButton(true, true); | |
const activeSessionContainer = document.getElementById(`session-container-${activeChatId}`); | |
currentTypingInterval = setInterval(() => { | |
if (signal.aborted) { | |
clearInterval(currentTypingInterval); | |
currentTypingInterval = null; | |
element.innerHTML += '... [Stopped]'; | |
toggleSendStopButton(false); | |
if (mode === 'image-to-text') { | |
resetImageUploader(); | |
} | |
return; | |
} | |
if (i < text.length) { | |
element.innerHTML += text.charAt(i); | |
i++; | |
if (activeSessionContainer) { | |
activeSessionContainer.scrollTop = activeSessionContainer.scrollHeight; | |
} | |
} else { | |
clearInterval(currentTypingInterval); | |
currentTypingInterval = null; | |
toggleSendStopButton(false); | |
if (mode === 'image-to-text') { | |
resetImageUploader(); | |
} | |
} | |
}, 10); | |
} | |
window.editMessage = (messageId) => { | |
if (!messageId) return; | |
const chat = chatSessions[activeChatId]; | |
if (!chat) return; | |
const message = chat.messages.find(m => m.id === messageId); | |
if (!message) return; | |
const promptTextarea = document.getElementById(`${activeMode}-prompt`); | |
const tempDiv = document.createElement('div'); | |
tempDiv.innerHTML = message.content; | |
promptTextarea.value = tempDiv.textContent || ""; | |
promptTextarea.focus(); | |
updateSendButtonState(activeMode); | |
editingMessageId = messageId; | |
}; | |
window.resendMessage = (messageId) => { | |
if (!messageId) return; | |
const chat = chatSessions[activeChatId]; | |
if (!chat) return; | |
const message = chat.messages.find(m => m.id === messageId); | |
if (!message) return; | |
displayMessage(message); | |
const tempDiv = document.createElement('div'); | |
tempDiv.innerHTML = message.content; | |
handleAction(null, tempDiv.textContent || ""); | |
}; | |
window.copyResponse = (event) => { | |
const icon = event.target; | |
const bubble = icon.closest('.chat-bubble'); | |
if (!bubble) return; | |
const contentClone = bubble.cloneNode(true); | |
contentClone.querySelector('.copy-icon-bubble').remove(); | |
const textToCopy = contentClone.querySelector('.response-content')?.textContent || contentClone.textContent; | |
navigator.clipboard.writeText(textToCopy).then(() => { | |
icon.textContent = 'check'; | |
setTimeout(() => { icon.textContent = 'content_copy'; }, 1500); | |
}).catch(err => console.error('Failed to copy: ', err)); | |
}; | |
function createModelResponseHeader() { | |
return ` | |
<div class="model-response-header"> | |
<span class="material-icons">memory</span> | |
<span>${activeModelInfo.name}</span> | |
<span>| Temp: ${temperature.toFixed(1)}</span> | | |
<span>Top-P: ${topP.toFixed(1)}</span> | |
</div> | |
`; | |
} | |
function setupActionButtons() { | |
document.querySelectorAll('.action-button').forEach(btn => { | |
btn.onclick = handleAction; | |
}); | |
} | |
function toggleSendStopButton(isGenerating, isTyping = false) { | |
document.querySelectorAll('.action-button').forEach(btn => { | |
const currentTextarea = document.getElementById(`${btn.dataset.target}-prompt`); | |
if (isGenerating || isTyping) { | |
btn.innerHTML = `<span class="material-icons">stop_circle</span>`; | |
btn.classList.add('stop-generating'); | |
btn.disabled = false; | |
if(currentTextarea) currentTextarea.disabled = true; | |
btn.onclick = () => { | |
if (currentRequestController) currentRequestController.abort(); | |
if(currentTypingInterval) clearInterval(currentTypingInterval); | |
toggleSendStopButton(false); | |
}; | |
} else { | |
btn.innerHTML = `<span class="material-icons">send</span>`; | |
btn.classList.remove('stop-generating'); | |
if(currentTextarea) currentTextarea.disabled = false; | |
updateSendButtonState(btn.dataset.target); | |
btn.onclick = handleAction; | |
} | |
}); | |
const activeSessionContainer = document.getElementById(`session-container-${activeChatId}`); | |
if (!activeSessionContainer) return; | |
const userMessageContainers = activeSessionContainer.querySelectorAll('.chat-message-container.user'); | |
if (userMessageContainers.length > 0) { | |
const lastUserMessage = userMessageContainers[userMessageContainers.length - 1]; | |
const actionBar = lastUserMessage.querySelector('.prompt-actions-bar'); | |
if (actionBar) { | |
actionBar.style.display = (isGenerating || isTyping) ? 'none' : 'flex'; | |
} | |
} | |
} | |
async function handleAction(e, overridePrompt = null) { | |
if(e) e.preventDefault(); | |
const targetMode = e ? e.target.closest('.action-button').dataset.target : activeMode; | |
if (!activeChatId) { | |
startNewChat(); | |
} | |
const isGroqModel = activeModelInfo.isGroq; | |
const apiKeyInput = isGroqModel ? groqTokenInput : hfTokenInput; | |
const apiKey = apiKeyInput.value.trim(); | |
const apiKeyName = isGroqModel ? "Groq API Key" : "Hugging Face API Token"; | |
if (!apiKey) { | |
const errorMsg = { id: `err_${Date.now()}`, sender: 'model', content: `Please enter your ${apiKeyName}.`, isError: true }; | |
displayMessage(errorMsg); | |
apiKeyInput.classList.add('input-error'); | |
return; | |
} | |
const promptTextarea = document.getElementById(`${targetMode}-prompt`); | |
let prompt = overridePrompt !== null ? overridePrompt : promptTextarea?.value.trim() || ''; | |
if (targetMode === 'image-to-text' && !uploadedImageBase64 && !editingMessageId) { | |
const errorMsg = { id: `err_${Date.now()}`, sender: 'model', content: 'Please upload an image first.', isError: true }; | |
displayMessage(errorMsg); | |
return; | |
} | |
if (!prompt && targetMode !== 'image-to-text' && overridePrompt === null) return; | |
let userMessageContent = prompt; | |
if (targetMode === 'image-to-text') { | |
userMessageContent = `<img src="${uploadedImageDataUrl}" alt="Uploaded preview" style="max-height: 150px; border-radius: 8px; margin-bottom: 8px;">${prompt ? `<br>${prompt}`: ''}`; | |
} | |
if (overridePrompt === null) { | |
let userMessageId; | |
if (editingMessageId) { | |
const messageToUpdate = chatSessions[activeChatId].messages.find(m => m.id === editingMessageId); | |
if (messageToUpdate) { | |
messageToUpdate.content = userMessageContent; | |
// Find the message in the DOM and update it | |
const messageElement = document.getElementById(editingMessageId); | |
if(messageElement) { | |
messageElement.querySelector('.chat-bubble').innerHTML = userMessageContent; | |
} | |
userMessageId = editingMessageId; | |
} | |
} else { | |
userMessageId = addMessageToSession('user', userMessageContent, { isHtml: true }); | |
const userMessage = chatSessions[activeChatId].messages.find(m => m.id === userMessageId); | |
displayMessage(userMessage); | |
} | |
} | |
if (chatSessions[activeChatId].messages.length <= 2 && prompt && !editingMessageId) { | |
chatSessions[activeChatId].title = prompt.substring(0, 25) + (prompt.length > 25 ? '...' : ''); | |
renderChatHistory(); | |
} | |
if(promptTextarea) { promptTextarea.value = ''; autoGrowTextarea(promptTextarea); } | |
updateAllSendButtonStates(); | |
currentRequestController = new AbortController(); | |
toggleSendStopButton(true); | |
const typingMsg = { id: `typing_${Date.now()}`, sender: 'model', content: '...' }; | |
const typingIndicator = displayMessage(typingMsg, true); | |
let requestBody; | |
let headers = { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }; | |
if (targetMode === 'image-to-text') { | |
if (isGroqModel) { | |
const payload = { | |
model: activeModelInfo.id, | |
messages: [ { role: "user", content: [ { type: "text", text: prompt }, { type: "image_url", image_url: { url: uploadedImageDataUrl } } ] } ], | |
temperature, max_tokens: 1024, stream: false | |
}; | |
requestBody = JSON.stringify(payload); | |
} else { | |
const res = await fetch(uploadedImageDataUrl, { signal: currentRequestController.signal }); | |
requestBody = await res.blob(); | |
headers['Content-Type'] = requestBody.type; | |
} | |
} else { | |
let payload; | |
if (isGroqModel) { | |
payload = { model: activeModelInfo.id, messages: [{"role": "user", "content": prompt}], temperature, top_p: topP, max_tokens: 1024, stream: false }; | |
} else if (targetMode === 'text-generation') { | |
payload = { model: activeModelInfo.id, messages: [{"role": "user", "content": prompt}], temperature, top_p: topP }; | |
} else { | |
payload = { inputs: prompt }; | |
} | |
requestBody = JSON.stringify(payload); | |
} | |
try { | |
const response = await fetch(activeModelInfo.url, { method: 'POST', headers: headers, body: requestBody, signal: currentRequestController.signal }); | |
const resultText = await response.text(); | |
if (!response.ok) { | |
try { | |
const errorJson = JSON.parse(resultText); | |
throw new Error(errorJson.error?.message || errorJson.error || resultText); | |
} catch(e) { throw new Error(resultText); } | |
} | |
let responseContent = ''; | |
let responseHtml = false; | |
const finalResponse = JSON.parse(resultText); | |
switch (targetMode) { | |
case 'text-generation': | |
responseContent = finalResponse.choices?.[0]?.message?.content || '[No content]'; | |
break; | |
case 'image-to-text': | |
if (isGroqModel) { | |
responseContent = finalResponse.choices?.[0]?.message?.content || '[No image caption]'; | |
} else { | |
responseContent = finalResponse?.[0]?.generated_text || '[No image caption]'; | |
} | |
break; | |
case 'text-classification': | |
responseContent = `<pre style="background:var(--bg-color-dark); padding: 12px; border-radius: 8px;">${JSON.stringify(finalResponse, null, 2)}</pre>`; | |
responseHtml = true; | |
break; | |
} | |
const responseHeader = createModelResponseHeader(); | |
const fullResponseHtml = responseHeader + `<div class="response-content"></div>`; | |
if (typingIndicator) typingIndicator.remove(); | |
const modelMessageId = addMessageToSession('model', fullResponseHtml, { isHtml: true }); | |
const finalMessage = chatSessions[activeChatId].messages.find(m => m.id === modelMessageId); | |
const finalMessageEl = displayMessage(finalMessage); | |
if (finalMessageEl) { | |
const responseContentEl = finalMessageEl.querySelector('.response-content'); | |
if (responseHtml) { | |
responseContentEl.innerHTML = responseContent; | |
toggleSendStopButton(false); | |
} else { | |
streamResponse(responseContentEl, responseContent, targetMode); | |
} | |
} | |
} catch (error) { | |
if (typingIndicator) { | |
let errorMessage = `Error: ${error.message}`; | |
if (error.name === 'AbortError') { | |
errorMessage = 'Response generation stopped by user.'; | |
} | |
typingIndicator.querySelector('.chat-bubble').textContent = errorMessage; | |
typingIndicator.classList.add('error'); | |
addMessageToSession('model', errorMessage, { isError: true }); | |
} | |
toggleSendStopButton(false); | |
} finally { | |
editingMessageId = null; | |
currentRequestController = null; | |
} | |
} | |
init(); | |
}); | |
</script> | |
</body> | |
</html> | |
""" | |
components.html( | |
html_code, | |
) |