init: AI-webUI-V1 first backup
This commit is contained in:
355
public/app.js
Normal file
355
public/app.js
Normal file
@@ -0,0 +1,355 @@
|
||||
const messagesEl = document.getElementById("messages");
|
||||
const inputEl = document.getElementById("input");
|
||||
const sendBtn = document.getElementById("sendBtn");
|
||||
const newChatBtn = document.getElementById("newChatBtn");
|
||||
const chatListEl = document.getElementById("chatList");
|
||||
const clearBtn = document.getElementById("clearBtn");
|
||||
|
||||
const sidebarToggleBtn = document.getElementById("sidebarToggle");
|
||||
const appRootEl = document.querySelector(".app");
|
||||
|
||||
let chats = JSON.parse(localStorage.getItem("ds_chats") || "{}");
|
||||
let currentChatId = localStorage.getItem("ds_current_chat");
|
||||
let sidebarHidden = localStorage.getItem("ds_sidebar_hidden") === "1";
|
||||
|
||||
/** ---------- 时间格式化 ---------- */
|
||||
function formatChatDate(ts) {
|
||||
try {
|
||||
return new Intl.DateTimeFormat("zh-CN", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
}).format(new Date(ts));
|
||||
} catch {
|
||||
const d = new Date(ts);
|
||||
const y = d.getFullYear();
|
||||
const m = String(d.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(d.getDate()).padStart(2, "0");
|
||||
return `${y}-${m}-${day}`;
|
||||
}
|
||||
}
|
||||
|
||||
function formatMsgDateTime(ts) {
|
||||
try {
|
||||
return new Intl.DateTimeFormat("zh-CN", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
}).format(new Date(ts));
|
||||
} catch {
|
||||
const d = new Date(ts);
|
||||
const y = d.getFullYear();
|
||||
const m = String(d.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(d.getDate()).padStart(2, "0");
|
||||
const hh = String(d.getHours()).padStart(2, "0");
|
||||
const mm = String(d.getMinutes()).padStart(2, "0");
|
||||
const ss = String(d.getSeconds()).padStart(2, "0");
|
||||
return `${y}-${m}-${day} ${hh}:${mm}:${ss}`;
|
||||
}
|
||||
}
|
||||
|
||||
/** ---------- 兼容旧数据:补齐 createdAt/updatedAt/ts ---------- */
|
||||
function normalizeData() {
|
||||
const now = Date.now();
|
||||
for (const id of Object.keys(chats)) {
|
||||
const chat = chats[id] || {};
|
||||
if (!chat.title) chat.title = "New Chat";
|
||||
if (!Array.isArray(chat.messages)) chat.messages = [];
|
||||
|
||||
if (!chat.createdAt) {
|
||||
const first = chat.messages[0];
|
||||
chat.createdAt = first?.ts || now;
|
||||
}
|
||||
if (!chat.updatedAt) {
|
||||
const last = chat.messages[chat.messages.length - 1];
|
||||
chat.updatedAt = last?.ts || chat.createdAt;
|
||||
}
|
||||
|
||||
for (const m of chat.messages) {
|
||||
if (!m.ts) m.ts = chat.createdAt;
|
||||
}
|
||||
|
||||
chats[id] = chat;
|
||||
}
|
||||
}
|
||||
normalizeData();
|
||||
|
||||
/** ---------- 保存 ---------- */
|
||||
function save() {
|
||||
localStorage.setItem("ds_chats", JSON.stringify(chats));
|
||||
localStorage.setItem("ds_current_chat", currentChatId || "");
|
||||
localStorage.setItem("ds_sidebar_hidden", sidebarHidden ? "1" : "0");
|
||||
}
|
||||
|
||||
/** ---------- 侧栏显示/隐藏 ---------- */
|
||||
function applySidebarState() {
|
||||
if (!appRootEl) return;
|
||||
appRootEl.classList.toggle("sidebar-hidden", sidebarHidden);
|
||||
}
|
||||
applySidebarState();
|
||||
|
||||
if (sidebarToggleBtn) {
|
||||
sidebarToggleBtn.onclick = () => {
|
||||
sidebarHidden = !sidebarHidden;
|
||||
applySidebarState();
|
||||
save();
|
||||
};
|
||||
}
|
||||
|
||||
/** ---------- Chat 操作 ---------- */
|
||||
function newChat() {
|
||||
const now = Date.now();
|
||||
currentChatId = crypto.randomUUID();
|
||||
chats[currentChatId] = {
|
||||
title: "New Chat",
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
messages: [],
|
||||
};
|
||||
save();
|
||||
renderChatList();
|
||||
renderMessages();
|
||||
}
|
||||
|
||||
function deleteChat(id) {
|
||||
if (!id || !chats[id]) return;
|
||||
|
||||
const title = chats[id].title || "This chat";
|
||||
const ok = confirm(`Delete "${title}" ?`);
|
||||
if (!ok) return;
|
||||
|
||||
delete chats[id];
|
||||
|
||||
// ✅ 关键:允许删除最后一个。删完后不自动 newChat()
|
||||
if (currentChatId === id) {
|
||||
const remainingIds = Object.keys(chats);
|
||||
currentChatId = remainingIds[0] || null;
|
||||
}
|
||||
|
||||
save();
|
||||
renderChatList();
|
||||
renderMessages();
|
||||
}
|
||||
|
||||
/** ---------- 渲染左侧列表(显示创建日期 + 删除按钮 + 按更新时间排序)---------- */
|
||||
function renderChatList() {
|
||||
chatListEl.innerHTML = "";
|
||||
|
||||
const entries = Object.entries(chats);
|
||||
entries.sort((a, b) => (b[1].updatedAt || 0) - (a[1].updatedAt || 0));
|
||||
|
||||
// 如果没有任何 chat,列表保持空(不用提示,提示放在右侧 messages)
|
||||
if (entries.length === 0) return;
|
||||
|
||||
for (const [id, chat] of entries) {
|
||||
const li = document.createElement("li");
|
||||
li.className = "chat-item";
|
||||
if (id === currentChatId) li.classList.add("active");
|
||||
|
||||
const left = document.createElement("div");
|
||||
left.className = "chat-item-left";
|
||||
|
||||
const title = document.createElement("div");
|
||||
title.className = "chat-title";
|
||||
title.textContent = chat.title || "New Chat";
|
||||
|
||||
const date = document.createElement("div");
|
||||
date.className = "chat-date";
|
||||
date.textContent = formatChatDate(chat.createdAt || Date.now());
|
||||
|
||||
left.appendChild(title);
|
||||
left.appendChild(date);
|
||||
|
||||
const del = document.createElement("button");
|
||||
del.className = "chat-del";
|
||||
del.type = "button";
|
||||
del.title = "Delete";
|
||||
del.textContent = "🗑";
|
||||
del.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
deleteChat(id);
|
||||
};
|
||||
|
||||
li.appendChild(left);
|
||||
li.appendChild(del);
|
||||
|
||||
li.onclick = () => {
|
||||
currentChatId = id;
|
||||
save();
|
||||
renderChatList();
|
||||
renderMessages();
|
||||
};
|
||||
|
||||
chatListEl.appendChild(li);
|
||||
}
|
||||
}
|
||||
|
||||
/** ---------- 空状态 UI ---------- */
|
||||
function renderEmptyState(kind) {
|
||||
// kind: "no-chat" | "no-messages"
|
||||
const div = document.createElement("div");
|
||||
div.className = "empty-state";
|
||||
|
||||
if (kind === "no-chat") {
|
||||
div.innerHTML = `
|
||||
<h3>还没有会话</h3>
|
||||
<div>你可以:</div>
|
||||
<ul>
|
||||
<li>点击左上角 <code>+</code> 创建新会话</li>
|
||||
<li>或者直接在下方输入内容并发送(会自动创建新会话)</li>
|
||||
<li>需要隐藏左侧列表:点顶部 <code>☰</code></li>
|
||||
</ul>
|
||||
`;
|
||||
} else {
|
||||
div.innerHTML = `
|
||||
<h3>这个会话还没有消息</h3>
|
||||
<div>在下方输入内容,按 <code>Enter</code> 发送;<code>Shift+Enter</code> 换行。</div>
|
||||
`;
|
||||
}
|
||||
|
||||
messagesEl.appendChild(div);
|
||||
}
|
||||
|
||||
/** ---------- 渲染消息(每条消息显示日期时间)---------- */
|
||||
function renderMessages() {
|
||||
messagesEl.innerHTML = "";
|
||||
|
||||
// 没有任何 chat
|
||||
if (!currentChatId || !chats[currentChatId]) {
|
||||
renderEmptyState("no-chat");
|
||||
return;
|
||||
}
|
||||
|
||||
const chat = chats[currentChatId];
|
||||
const msgs = chat.messages || [];
|
||||
|
||||
// 有 chat 但没消息
|
||||
if (msgs.length === 0) {
|
||||
renderEmptyState("no-messages");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const m of msgs) {
|
||||
const wrap = document.createElement("div");
|
||||
wrap.className = `message ${m.role} ${m.thinking ? "thinking" : ""}`;
|
||||
|
||||
const content = document.createElement("div");
|
||||
content.className = "msg-content";
|
||||
|
||||
if (m.role === "assistant" && !m.thinking) {
|
||||
content.innerHTML = marked.parse(m.content || "");
|
||||
content.querySelectorAll("pre code").forEach((block) => {
|
||||
if (window.hljs) window.hljs.highlightElement(block);
|
||||
});
|
||||
} else {
|
||||
content.textContent = m.content || "";
|
||||
}
|
||||
|
||||
const meta = document.createElement("div");
|
||||
meta.className = "msg-meta";
|
||||
meta.textContent = formatMsgDateTime(m.ts || Date.now());
|
||||
|
||||
wrap.appendChild(content);
|
||||
wrap.appendChild(meta);
|
||||
|
||||
messagesEl.appendChild(wrap);
|
||||
}
|
||||
|
||||
messagesEl.scrollTop = messagesEl.scrollHeight;
|
||||
}
|
||||
|
||||
/** ---------- 发送消息 ---------- */
|
||||
sendBtn.onclick = async () => {
|
||||
const text = inputEl.value.trim();
|
||||
if (!text) return;
|
||||
|
||||
sendBtn.disabled = true;
|
||||
sendBtn.textContent = "Sending...";
|
||||
|
||||
// ✅ 如果你把最后一个 chat 删光了:这里会自动新建
|
||||
if (!currentChatId || !chats[currentChatId]) {
|
||||
newChat();
|
||||
}
|
||||
const chat = chats[currentChatId];
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
// user 消息带时间戳
|
||||
chat.messages.push({ role: "user", content: text, ts: now });
|
||||
chat.updatedAt = now;
|
||||
inputEl.value = "";
|
||||
|
||||
// thinking 消息也带时间戳(后续只改内容/ts)
|
||||
const thinkingMsg = { role: "assistant", content: "🤔 思考中…", thinking: true, ts: Date.now() };
|
||||
chat.messages.push(thinkingMsg);
|
||||
chat.updatedAt = thinkingMsg.ts;
|
||||
|
||||
renderMessages();
|
||||
save();
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/chat", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
model: document.getElementById("modelSelect").value || "deepseek-chat",
|
||||
messages: chat.messages.filter((m) => !m.thinking),
|
||||
temperature: parseFloat(document.getElementById("tempInput").value || 0.7),
|
||||
}),
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
thinkingMsg.thinking = false;
|
||||
thinkingMsg.content = data.reply || "No response";
|
||||
thinkingMsg.ts = Date.now(); // ✅ 用“收到回复的时间”
|
||||
chat.updatedAt = thinkingMsg.ts;
|
||||
|
||||
if (chat.title === "New Chat") {
|
||||
chat.title = text.slice(0, 20);
|
||||
}
|
||||
} catch (e) {
|
||||
thinkingMsg.thinking = false;
|
||||
thinkingMsg.content = "❌ 请求失败";
|
||||
thinkingMsg.ts = Date.now();
|
||||
chat.updatedAt = thinkingMsg.ts;
|
||||
console.error(e);
|
||||
} finally {
|
||||
sendBtn.disabled = false;
|
||||
sendBtn.textContent = "Send";
|
||||
}
|
||||
|
||||
save();
|
||||
renderChatList();
|
||||
renderMessages();
|
||||
};
|
||||
|
||||
/** Enter=send, Shift+Enter=newline */
|
||||
inputEl.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
sendBtn.click();
|
||||
}
|
||||
});
|
||||
|
||||
newChatBtn.onclick = newChat;
|
||||
|
||||
clearBtn.onclick = () => {
|
||||
if (!currentChatId || !chats[currentChatId]) return;
|
||||
const chat = chats[currentChatId];
|
||||
chat.messages = [];
|
||||
chat.title = "New Chat";
|
||||
chat.updatedAt = Date.now();
|
||||
save();
|
||||
renderChatList();
|
||||
renderMessages();
|
||||
};
|
||||
|
||||
/** ---------- 初始化 ---------- */
|
||||
renderChatList();
|
||||
renderMessages();
|
||||
152
public/index.html
Normal file
152
public/index.html
Normal file
@@ -0,0 +1,152 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>My DeepSeek WebUI</title>
|
||||
|
||||
<!-- Local vendor (CSP-friendly) -->
|
||||
<link rel="stylesheet" href="/vendor/github.min.css">
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
|
||||
<!-- ✅ 新增:只在这里补一点点样式(避免你还要改第三个文件) -->
|
||||
<style>
|
||||
/* 顶部栏的图标按钮 */
|
||||
.icon-btn{
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 8px;
|
||||
line-height: 1;
|
||||
}
|
||||
.icon-btn:hover{
|
||||
background: rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
/* 隐藏侧栏:通过 .app.sidebar-hidden 控制 */
|
||||
.app.sidebar-hidden .sidebar{
|
||||
width: 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
/* 左侧 chat item:标题+日期+删除按钮 */
|
||||
#chatList li.chat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
.chat-item-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
.chat-title {
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.chat-date {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
}
|
||||
.chat-del {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
cursor: pointer;
|
||||
padding: 4px 6px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.chat-del:hover {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 消息时间戳 */
|
||||
.message .msg-meta {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
opacity: 0.65;
|
||||
}
|
||||
.message.user .msg-meta {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 空状态提示 */
|
||||
.empty-state{
|
||||
margin: 24px auto;
|
||||
max-width: 720px;
|
||||
padding: 18px 16px;
|
||||
border-radius: 12px;
|
||||
background: rgba(0,0,0,0.04);
|
||||
color: rgba(0,0,0,0.72);
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.empty-state h3{
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 16px;
|
||||
color: rgba(0,0,0,0.85);
|
||||
}
|
||||
.empty-state code{
|
||||
background: rgba(0,0,0,0.06);
|
||||
padding: 2px 6px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="app">
|
||||
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h2>Chats</h2>
|
||||
<button id="newChatBtn" type="button">+</button>
|
||||
</div>
|
||||
<ul id="chatList"></ul>
|
||||
</aside>
|
||||
|
||||
<main class="main">
|
||||
<header class="topbar">
|
||||
<div class="topbar-left">
|
||||
<!-- ✅ 新增:隐藏/展开侧栏按钮 -->
|
||||
<button id="sidebarToggle" type="button" class="icon-btn" title="Toggle sidebar">☰</button>
|
||||
|
||||
<label>Model:</label>
|
||||
<select id="modelSelect">
|
||||
<option value="deepseek-chat">deepseek-chat</option>
|
||||
<option value="deepseek-reasoner">deepseek-reasoner</option>
|
||||
</select>
|
||||
|
||||
<label>Temp:</label>
|
||||
<input id="tempInput" type="number" step="0.1" value="0.7" />
|
||||
</div>
|
||||
|
||||
<button id="clearBtn" type="button">Clear</button>
|
||||
</header>
|
||||
|
||||
<section id="messages" class="messages"></section>
|
||||
|
||||
<footer class="inputbar">
|
||||
<textarea id="input" placeholder="Type your message... (Enter=send, Shift+Enter=newline)"></textarea>
|
||||
<button id="sendBtn" type="button">Send</button>
|
||||
</footer>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Local vendor (CSP-friendly) -->
|
||||
<script src="/vendor/marked.min.js"></script>
|
||||
<script src="/vendor/highlight.js"></script>
|
||||
<script src="/app.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
247
public/style.css
Normal file
247
public/style.css
Normal file
@@ -0,0 +1,247 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont;
|
||||
background: #f7f7f8;
|
||||
}
|
||||
|
||||
.app {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
width: 260px;
|
||||
background: #202123;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sidebar-header button {
|
||||
background: #444;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#chatList {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#chatList li {
|
||||
padding: 10px 12px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #333;
|
||||
}
|
||||
|
||||
#chatList li.active {
|
||||
background: #343541;
|
||||
}
|
||||
|
||||
/* Main */
|
||||
.main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
padding: 10px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #ddd;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.messages {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
|
||||
.message {
|
||||
margin-bottom: 16px;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.message {
|
||||
max-width: 78%;
|
||||
padding: 12px 14px;
|
||||
border-radius: 16px;
|
||||
line-height: 1.6;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.message.user {
|
||||
align-self: flex-end;
|
||||
background: linear-gradient(135deg, #6fa8ff, #000000);
|
||||
color: #03173e;
|
||||
}
|
||||
|
||||
.message.assistant {
|
||||
align-self: flex-start;
|
||||
background: #fff;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
|
||||
.message.user {
|
||||
align-self: flex-end;
|
||||
background: #daf1ff;
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.message.assistant {
|
||||
background: #fff;
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
.message.thinking {
|
||||
color: #888;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.inputbar {
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
border-top: 1px solid #ddd;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
textarea {
|
||||
flex: 1;
|
||||
resize: none;
|
||||
padding: 12px 14px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid #ddd;
|
||||
min-height: 48px;
|
||||
max-height: 180px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
border-color: #4a90e2;
|
||||
box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.15);
|
||||
}
|
||||
|
||||
#sendBtn {
|
||||
min-width: 88px;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
|
||||
.topbar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.topbar select,
|
||||
.topbar input {
|
||||
height: 34px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #ddd;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.topbar label {
|
||||
color: #555;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
||||
textarea {
|
||||
flex: 1;
|
||||
resize: none;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 8px 14px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
background: #10a37f;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 左侧 chat item:标题+日期+删除按钮 */
|
||||
#chatList li.chat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.chat-item-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.chat-title {
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.chat-date {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
}
|
||||
|
||||
.chat-del {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
cursor: pointer;
|
||||
padding: 4px 6px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.chat-del:hover {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 消息:内容 + 时间戳 */
|
||||
.message .msg-meta {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
/* 让 user 的时间戳靠右一点更像聊天软件 */
|
||||
.message.user .msg-meta {
|
||||
text-align: right;
|
||||
}
|
||||
7
public/vendor/github.min.css
vendored
Normal file
7
public/vendor/github.min.css
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||
Theme: a11y-dark
|
||||
Author: @ericwbailey
|
||||
Maintainer: @ericwbailey
|
||||
|
||||
Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css
|
||||
*/.hljs{background:#2b2b2b;color:#f8f8f2}.hljs-comment,.hljs-quote{color:#d4d0ab}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#ffa07a}.hljs-built_in,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{color:#f5ab35}.hljs-attribute{color:gold}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#abe338}.hljs-section,.hljs-title{color:#00e0e0}.hljs-keyword,.hljs-selector-tag{color:#dcc6e0}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}@media screen and (-ms-high-contrast:active){.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-comment,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-quote,.hljs-string,.hljs-symbol,.hljs-type{color:highlight}.hljs-keyword,.hljs-selector-tag{font-weight:700}}
|
||||
6
public/vendor/marked.min.js
vendored
Normal file
6
public/vendor/marked.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user