Publish current ComfyUI O1Key code baseline
Replace the prior release tree with the current plugin, frontend, tests, and documentation. Document retired node IDs and the public Gitea update source.
This commit is contained in:
+57
-15
@@ -2,10 +2,12 @@ import { app } from "../../../scripts/app.js";
|
||||
|
||||
// ─── State ───────────────────────────────────────────────────────────────────
|
||||
const STORAGE_KEY = "o1key-chat-conversations";
|
||||
const DEFAULT_MODEL = "gpt-6-sol";
|
||||
let conversations = [];
|
||||
let activeConvId = null;
|
||||
let currentModel = "gpt-5.5";
|
||||
let currentReasoning = "medium";
|
||||
let currentModel = DEFAULT_MODEL;
|
||||
let webSearchEnabled = false;
|
||||
let currentReasoning = "high";
|
||||
let isStreaming = false;
|
||||
let isThinking = false;
|
||||
let abortController = null;
|
||||
@@ -16,7 +18,7 @@ let showHistory = false;
|
||||
|
||||
const MAX_FILES = 4;
|
||||
const MAX_FILE_SIZE = 20 * 1024 * 1024;
|
||||
const ACCEPT_STRING = "image/*,video/*,audio/*,.pdf,.txt,.md,.csv,.json,.doc,.docx";
|
||||
const ACCEPT_STRING = "image/*,video/*,audio/*,.pdf,.txt,.md,.csv,.json,.doc,.docx,.xlsx,.xlsm";
|
||||
|
||||
function classifyFile(mimeType) {
|
||||
if (mimeType.startsWith("image/")) return "image";
|
||||
@@ -52,12 +54,13 @@ function truncateFilename(name, maxLen = 20) {
|
||||
}
|
||||
|
||||
const MODELS = [
|
||||
DEFAULT_MODEL,
|
||||
"gpt-6-astra",
|
||||
"gpt-5.6-sol",
|
||||
"gpt-5.5",
|
||||
"gemini-3.5-flash",
|
||||
"claude-fable-5",
|
||||
"gemini-3.1-pro-preview",
|
||||
"deepseek-v4-pro",
|
||||
"claude-opus-4-7",
|
||||
"claude-opus-4-6",
|
||||
"doubao-seed-2.0-pro",
|
||||
];
|
||||
|
||||
@@ -119,6 +122,9 @@ const CSS = `
|
||||
#o1key-chat-toolbar select:focus{border-color:rgba(255,255,255,.3);background-color:rgba(255,255,255,.1)}
|
||||
#o1key-chat-toolbar #o1k-reasoning-sel{flex:none;width:auto;padding-right:28px}
|
||||
#o1key-chat-toolbar select option{background:#2a2a2a;color:#ddd;padding:8px}
|
||||
#o1key-chat-toolbar .o1k-search-toggle{width:34px;height:34px;display:flex;align-items:center;justify-content:center;flex:none;background:rgba(255,255,255,.05);color:#777;border:1px solid rgba(255,255,255,.1);border-radius:8px;cursor:pointer;transition:all .15s}
|
||||
#o1key-chat-toolbar .o1k-search-toggle:hover{color:#ccc;background:rgba(255,255,255,.09);border-color:rgba(255,255,255,.2)}
|
||||
#o1key-chat-toolbar .o1k-search-toggle.active{color:#7eb8f7;background:rgba(126,184,247,.14);border-color:rgba(126,184,247,.45)}
|
||||
#o1key-chat-messages{flex:1;overflow-y:auto;padding:12px 14px;display:flex;flex-direction:column;gap:2px;scroll-behavior:smooth;min-height:0}
|
||||
#o1key-chat-messages::-webkit-scrollbar{width:4px}
|
||||
#o1key-chat-messages::-webkit-scrollbar-track{background:transparent}
|
||||
@@ -171,6 +177,11 @@ const CSS = `
|
||||
.o1k-reasoning .o1k-reasoning-body{padding:6px 10px 8px;font-size:12px;color:#777;line-height:1.5;border-top:1px solid rgba(255,255,255,.04);max-height:200px;overflow-y:auto}
|
||||
.o1k-reasoning .o1k-reasoning-body::-webkit-scrollbar{width:3px}
|
||||
.o1k-reasoning .o1k-reasoning-body::-webkit-scrollbar-thumb{background:rgba(255,255,255,.08);border-radius:2px}
|
||||
.o1k-search-trace{margin:0 0 8px;border:1px solid rgba(126,184,247,.16);border-radius:8px;overflow:hidden;background:rgba(126,184,247,.04)}
|
||||
.o1k-search-trace summary{padding:6px 10px;font-size:11px;color:#8dbff2;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.o1k-search-results{padding:4px 10px 8px;border-top:1px solid rgba(126,184,247,.1);display:flex;flex-direction:column;gap:5px}
|
||||
.o1k-search-results a{font-size:11px;line-height:1.35;color:#8dbff2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.o1k-search-error{font-size:11px;color:#d49a8f}
|
||||
#o1key-chat-history{flex:1;overflow-y:auto;padding:8px 14px;display:flex;flex-direction:column;gap:4px;min-height:0}
|
||||
.o1k-conv-item{display:flex;align-items:center;gap:8px;padding:10px 12px;border-radius:8px;cursor:pointer;transition:all .12s;border:1px solid transparent}
|
||||
.o1k-conv-item:hover{background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.08)}
|
||||
@@ -251,7 +262,7 @@ app.registerExtension({
|
||||
loadConversations();
|
||||
app.extensionManager.registerSidebarTab({
|
||||
id: "o1key-chat",
|
||||
title: "对话",
|
||||
title: "聊天",
|
||||
icon: "pi pi-comments",
|
||||
type: "custom",
|
||||
render: (container) => {
|
||||
@@ -272,7 +283,7 @@ function renderChatPanel(container) {
|
||||
root.id = "o1key-chat-root";
|
||||
root.innerHTML = `
|
||||
<div id="o1key-chat-header">
|
||||
<span class="chat-title">AI 对话</span>
|
||||
<span class="chat-title">聊天</span>
|
||||
<div class="hdr-btns">
|
||||
<button class="chat-hdr-btn" id="o1k-history-btn" title="对话记录">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 8v4l3 3"/><circle cx="12" cy="12" r="9"/></svg>
|
||||
@@ -285,6 +296,9 @@ function renderChatPanel(container) {
|
||||
<div id="o1key-chat-toolbar">
|
||||
<select id="o1k-model-sel">${MODELS.map(m => `<option value="${m}"${m === currentModel ? " selected" : ""}>${m}</option>`).join("")}</select>
|
||||
<select id="o1k-reasoning-sel"><option value="low"${currentReasoning === "low" ? " selected" : ""}>思考:低</option><option value="medium"${currentReasoning === "medium" ? " selected" : ""}>思考:中</option><option value="high"${currentReasoning === "high" ? " selected" : ""}>思考:高</option></select>
|
||||
<button class="o1k-search-toggle${webSearchEnabled ? " active" : ""}" id="o1k-web-search" title="联网搜索" aria-label="联网搜索" aria-pressed="${webSearchEnabled}">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a15 15 0 010 18M12 3a15 15 0 000 18"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="o1key-chat-messages"></div>
|
||||
<div id="o1key-chat-history" style="display:none"></div>
|
||||
@@ -312,6 +326,7 @@ function renderChatPanel(container) {
|
||||
function bindEvents(root) {
|
||||
const modelSel = root.querySelector("#o1k-model-sel");
|
||||
const reasoningSel = root.querySelector("#o1k-reasoning-sel");
|
||||
const webSearchBtn = root.querySelector("#o1k-web-search");
|
||||
const newBtn = root.querySelector("#o1k-new-chat");
|
||||
const histBtn = root.querySelector("#o1k-history-btn");
|
||||
const input = root.querySelector("#o1k-input");
|
||||
@@ -321,6 +336,11 @@ function bindEvents(root) {
|
||||
|
||||
modelSel.addEventListener("change", () => { currentModel = modelSel.value; });
|
||||
reasoningSel.addEventListener("change", () => { currentReasoning = reasoningSel.value; });
|
||||
webSearchBtn.addEventListener("click", () => {
|
||||
webSearchEnabled = !webSearchEnabled;
|
||||
webSearchBtn.classList.toggle("active", webSearchEnabled);
|
||||
webSearchBtn.setAttribute("aria-pressed", String(webSearchEnabled));
|
||||
});
|
||||
newBtn.addEventListener("click", startNewConversation);
|
||||
histBtn.addEventListener("click", toggleHistory);
|
||||
|
||||
@@ -976,7 +996,13 @@ async function sendMessage(text, files, rawContent) {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
signal: abortController.signal,
|
||||
body: JSON.stringify({ model: currentModel, messages: reqMsgs, stream: true, reasoning_effort: currentReasoning }),
|
||||
body: JSON.stringify({
|
||||
model: currentModel,
|
||||
messages: reqMsgs,
|
||||
stream: true,
|
||||
reasoning_effort: currentReasoning,
|
||||
web_search: webSearchEnabled,
|
||||
}),
|
||||
});
|
||||
if (!resp.ok) {
|
||||
isThinking = false;
|
||||
@@ -1004,6 +1030,10 @@ async function sendMessage(text, files, rawContent) {
|
||||
const delta = chunk.choices?.[0]?.delta;
|
||||
const finish = chunk.choices?.[0]?.finish_reason;
|
||||
const lastMsg = conv.messages[conv.messages.length-1];
|
||||
if (chunk.o1key_search) {
|
||||
lastMsg._search = chunk.o1key_search;
|
||||
updateLastMessage();
|
||||
}
|
||||
if (delta?.reasoning_content) {
|
||||
lastMsg._reasoning = (lastMsg._reasoning || "") + delta.reasoning_content;
|
||||
updateLastMessage();
|
||||
@@ -1084,6 +1114,18 @@ function renderMessages() {
|
||||
|
||||
function formatMsgContent(msg, idx, msgs) {
|
||||
const c = msg.content;
|
||||
let searchHtml = "";
|
||||
if (msg.role === "assistant" && msg._search) {
|
||||
const trace = msg._search;
|
||||
const results = Array.isArray(trace.results) ? trace.results : [];
|
||||
const resultHtml = results.map((item, index) =>
|
||||
`<a href="${escapeHtml(item.url || "")}" target="_blank" rel="noopener noreferrer" title="${escapeHtml(item.url || "")}">[${index + 1}] ${escapeHtml(item.title || item.url || "搜索结果")}</a>`
|
||||
).join("");
|
||||
const body = trace.error
|
||||
? `<span class="o1k-search-error">${escapeHtml(trace.error)}</span>`
|
||||
: resultHtml;
|
||||
searchHtml = `<details class="o1k-search-trace"><summary>联网搜索 · ${escapeHtml(trace.query || "")}</summary><div class="o1k-search-results">${body}</div></details>`;
|
||||
}
|
||||
let reasoningHtml = "";
|
||||
if (msg.role === "assistant" && msg._reasoning) {
|
||||
const isCurrentStreaming = isStreaming && idx === msgs.length - 1;
|
||||
@@ -1094,17 +1136,17 @@ function formatMsgContent(msg, idx, msgs) {
|
||||
reasoningHtml = `<details class="o1k-reasoning"${openAttr}><summary>思考过程</summary><div class="o1k-reasoning-body">${reasoningText}</div></details>`;
|
||||
}
|
||||
if (typeof c === "string") {
|
||||
if (msg.role === "assistant" && !isStreaming) return reasoningHtml + renderMd(c);
|
||||
if (msg.role === "assistant" && !isStreaming) return searchHtml + reasoningHtml + renderMd(c);
|
||||
if (msg.role === "assistant" && c === "" && idx === msgs.length - 1) {
|
||||
if (msg._reasoning) {
|
||||
return reasoningHtml;
|
||||
return searchHtml + reasoningHtml;
|
||||
}
|
||||
if (isThinking) {
|
||||
return `<div class="o1k-thinking"><div class="o1k-thinking-icon"></div><span class="o1k-thinking-text">思考中...</span></div>`;
|
||||
return searchHtml + `<div class="o1k-thinking"><div class="o1k-thinking-icon"></div><span class="o1k-thinking-text">思考中...</span></div>`;
|
||||
}
|
||||
return `<span class="typing-dot"></span><span class="typing-dot"></span><span class="typing-dot"></span>`;
|
||||
return searchHtml + `<span class="typing-dot"></span><span class="typing-dot"></span><span class="typing-dot"></span>`;
|
||||
}
|
||||
if (msg.role === "assistant") return reasoningHtml + escapeHtml(c).replace(/\n/g, "<br>");
|
||||
if (msg.role === "assistant") return searchHtml + reasoningHtml + escapeHtml(c).replace(/\n/g, "<br>");
|
||||
return escapeHtml(c).replace(/\n/g, "<br>");
|
||||
}
|
||||
if (Array.isArray(c)) {
|
||||
@@ -1198,4 +1240,4 @@ function renderMd(text) {
|
||||
function escapeHtml(str) {
|
||||
if (!str) return "";
|
||||
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user