deep-research / index.html
finpy1789's picture
Update index.html
40754f7 verified
Raw
History Blame Contribute Delete
10.1 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Deep Research</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
color: #18212f;
background: #f5f7fa;
}
.shell {
display: grid;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 18px 24px;
border-bottom: 1px solid #d9e0e9;
background: #ffffff;
}
.brand h1 {
margin: 0;
font-size: 20px;
line-height: 1.2;
}
.brand p {
margin: 3px 0 0;
color: #657184;
font-size: 13px;
}
.mode-toggle {
display: inline-grid;
grid-template-columns: repeat(3, 1fr);
padding: 3px;
border: 1px solid #ccd5e1;
border-radius: 8px;
background: #eef2f6;
}
.mode-toggle button {
min-width: 78px;
border: 0;
border-radius: 6px;
padding: 8px 10px;
background: transparent;
color: #4b586a;
font: inherit;
font-size: 13px;
cursor: pointer;
}
.mode-toggle button.active {
background: #ffffff;
color: #0f1724;
box-shadow: 0 1px 3px rgba(15, 23, 36, 0.12);
}
.chat {
width: min(980px, 100%);
margin: 0 auto;
padding: 24px;
overflow-y: auto;
}
.empty {
display: grid;
align-content: center;
min-height: 52vh;
color: #657184;
}
.empty h2 {
margin: 0 0 8px;
color: #18212f;
font-size: clamp(28px, 5vw, 48px);
letter-spacing: 0;
}
.empty p {
max-width: 620px;
margin: 0;
line-height: 1.6;
}
.message {
width: fit-content;
max-width: min(760px, 100%);
margin: 0 0 14px;
padding: 13px 15px;
border-radius: 8px;
line-height: 1.55;
white-space: pre-wrap;
}
.message.user {
margin-left: auto;
background: #1f6feb;
color: #ffffff;
}
.message.assistant {
background: #ffffff;
border: 1px solid #d9e0e9;
}
.message.progress {
background: #fff8e6;
border: 1px solid #efd18a;
color: #705400;
}
.message h1,
.message h2,
.message h3 {
margin: 10px 0 8px;
font-size: 18px;
}
.message p {
margin: 0 0 10px;
}
.message ul,
.message ol {
margin: 0 0 10px 22px;
padding: 0;
}
.composer {
border-top: 1px solid #d9e0e9;
background: #ffffff;
padding: 16px 24px 22px;
}
.composer form {
display: grid;
grid-template-columns: 1fr auto;
gap: 10px;
width: min(980px, 100%);
margin: 0 auto;
}
textarea {
width: 100%;
min-height: 48px;
max-height: 160px;
resize: vertical;
border: 1px solid #c6d0dc;
border-radius: 8px;
padding: 13px 14px;
color: #18212f;
font: inherit;
line-height: 1.4;
outline: none;
}
textarea:focus {
border-color: #1f6feb;
box-shadow: 0 0 0 3px rgba(31, 111, 235, 0.14);
}
.send {
width: 48px;
height: 48px;
border: 0;
border-radius: 8px;
background: #1f6feb;
color: #ffffff;
font-size: 20px;
cursor: pointer;
}
.send:disabled {
opacity: 0.55;
cursor: not-allowed;
}
@media (max-width: 720px) {
.topbar {
align-items: stretch;
flex-direction: column;
}
.mode-toggle {
width: 100%;
}
.mode-toggle button {
min-width: 0;
}
.chat,
.composer {
padding-left: 14px;
padding-right: 14px;
}
}
</style>
</head>
<body>
<div class="shell">
<header class="topbar">
<div class="brand">
<h1>Deep Research</h1>
<p>Chat normally, or ask for sourced research when you need depth.</p>
</div>
<div class="mode-toggle" aria-label="Response mode">
<button type="button" class="active" data-mode="auto">Auto</button>
<button type="button" data-mode="chat">Chat</button>
<button type="button" data-mode="research">Research</button>
</div>
</header>
<main class="chat" id="chat">
<section class="empty" id="empty">
<h2>Ask anything.</h2>
<p>Quick questions stay in normal chat. Requests for research, current sources, reports, literature reviews, or market analysis trigger the deeper workflow.</p>
</section>
</main>
<footer class="composer">
<form id="form">
<textarea id="input" placeholder="Ask a quick question, or request deep research..." required></textarea>
<button class="send" id="send" type="submit" aria-label="Send"></button>
</form>
</footer>
</div>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script>
const chat = document.getElementById("chat");
const empty = document.getElementById("empty");
const form = document.getElementById("form");
const input = document.getElementById("input");
const send = document.getElementById("send");
const modeButtons = [...document.querySelectorAll("[data-mode]")];
const sessionId = crypto.randomUUID();
let mode = "auto";
modeButtons.forEach((button) => {
button.addEventListener("click", () => {
mode = button.dataset.mode;
modeButtons.forEach((item) => item.classList.toggle("active", item === button));
input.focus();
});
});
function addMessage(content, type, markdown = false) {
empty?.remove();
const node = document.createElement("article");
node.className = `message ${type}`;
node.innerHTML = markdown && window.marked ? marked.parse(content) : escapeHtml(content);
chat.appendChild(node);
chat.scrollTop = chat.scrollHeight;
return node;
}
function escapeHtml(value) {
const div = document.createElement("div");
div.textContent = value;
return div.innerHTML;
}
function setLoading(value) {
send.disabled = value;
input.disabled = value;
send.textContent = value ? "..." : "→";
}
form.addEventListener("submit", async (event) => {
event.preventDefault();
const topic = input.value.trim();
if (!topic) return;
addMessage(topic, "user");
input.value = "";
setLoading(true);
try {
const response = await fetch("/research", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({topic, session_id: sessionId, mode}),
});
if (!response.ok) {
const error = await response.text();
throw new Error(error || `Request failed with ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const {value, done} = await reader.read();
if (done) break;
buffer += decoder.decode(value, {stream: true});
const events = buffer.split("\n\n");
buffer = events.pop();
for (const eventText of events) {
const line = eventText.split("\n").find((item) => item.startsWith("data: "));
if (!line) continue;
const data = JSON.parse(line.slice(6));
if (data.status === "progress") {
addMessage(data.message, "progress");
}
if (data.status === "complete") {
addMessage(data.report, "assistant", true);
}
}
}
} catch (error) {
addMessage(`Error: ${error.message}`, "progress");
} finally {
setLoading(false);
input.focus();
}
});
input.addEventListener("keydown", (event) => {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
form.requestSubmit();
}
});
</script>
</body>
</html>