feat: enhance Quick Notes 5.0 with API integration and UI improvements
- Implemented API communication for WeKnora, allowing users to authenticate and interact with knowledge bases. - Added functionality for image uploads and previews in chat, enhancing multimodal interaction capabilities. - Refactored the popup and sidepanel interfaces to improve user experience and accessibility, including new toolbar elements for mode selection. - Introduced session management features, enabling users to manage chat sessions and knowledge base selections more effectively. - Enhanced styling and layout for better responsiveness and visual appeal across various components. These changes significantly improve the functionality and user experience of the Quick Notes extension, providing a more robust platform for managing web content and interactions.
This commit is contained in:
@@ -1,5 +1,94 @@
|
||||
// background.js — Service Worker
|
||||
// 存储管理 + 消息路由 + 右键菜单
|
||||
// 存储管理 + 消息路由 + 右键菜单 + API 通信
|
||||
|
||||
// === WeKnora API Helper ===
|
||||
// 构建请求头:API Key 使用 X-API-Key header,Bearer token 使用 Authorization header
|
||||
async function buildHeaders(config) {
|
||||
var headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Request-ID': Date.now().toString(36) + Math.random().toString(36).slice(2, 8)
|
||||
};
|
||||
if (config.apiKey) {
|
||||
// API Key (sk- 开头) 使用 X-API-Key header 进行租户级认证
|
||||
headers['X-API-Key'] = config.apiKey;
|
||||
}
|
||||
if (config.bearerToken) {
|
||||
// Bearer token (通过用户名密码登录获取) 使用 Authorization header
|
||||
headers['Authorization'] = 'Bearer ' + config.bearerToken;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
async function apiRequest(method, path, body, options) {
|
||||
var config = await getConfigData();
|
||||
if (!config || !config.baseUrl) {
|
||||
return { success: false, error: '未配置服务地址,请先在设置中配置' };
|
||||
}
|
||||
var baseUrl = config.baseUrl.replace(/\/+$/, '');
|
||||
var url = baseUrl + path;
|
||||
var headers = await buildHeaders(config);
|
||||
try {
|
||||
var fetchOpts = { method: method, headers: headers };
|
||||
if (body && method !== 'GET') {
|
||||
fetchOpts.body = JSON.stringify(body);
|
||||
}
|
||||
if (options && options.signal) {
|
||||
fetchOpts.signal = options.signal;
|
||||
}
|
||||
var resp = await fetch(url, fetchOpts);
|
||||
if (!resp.ok) {
|
||||
var errText = '';
|
||||
try { var errJson = await resp.json(); errText = errJson.error?.message || errJson.message || resp.statusText; } catch (e) { errText = resp.statusText; }
|
||||
return { success: false, error: errText, status: resp.status };
|
||||
}
|
||||
var data = await resp.json();
|
||||
return data;
|
||||
} catch (err) {
|
||||
return { success: false, error: err.message || '网络请求失败' };
|
||||
}
|
||||
}
|
||||
|
||||
// SSE streaming chat request — returns ReadableStream
|
||||
async function apiChatStream(path, body) {
|
||||
var config = await getConfigData();
|
||||
if (!config || !config.baseUrl) {
|
||||
return { success: false, error: '未配置服务地址' };
|
||||
}
|
||||
var baseUrl = config.baseUrl.replace(/\/+$/, '');
|
||||
var url = baseUrl + path;
|
||||
var headers = await buildHeaders(config);
|
||||
headers['Accept'] = 'text/event-stream';
|
||||
try {
|
||||
var resp = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify(body),
|
||||
cache: 'no-store' // 避免浏览器缓存导致 SSE 流被缓冲
|
||||
});
|
||||
if (!resp.ok) {
|
||||
var errText = '';
|
||||
try { var errJson = await resp.json(); errText = errJson.error?.message || errJson.message || resp.statusText; } catch (e) { errText = resp.statusText; }
|
||||
return { success: false, error: errText };
|
||||
}
|
||||
return { success: true, response: resp };
|
||||
} catch (err) {
|
||||
return { success: false, error: err.message || '网络请求失败' };
|
||||
}
|
||||
}
|
||||
|
||||
// 流式推送消息到 sidepanel / popup 等前端页面
|
||||
function notifyStream(msg) {
|
||||
chrome.runtime.sendMessage(msg).catch(function () {});
|
||||
}
|
||||
|
||||
// Helper to get raw config data
|
||||
async function getConfigData() {
|
||||
var data = await chrome.storage.local.get('ka_config');
|
||||
return data.ka_config || null;
|
||||
}
|
||||
|
||||
// Session cache per tab for sidepanel chat
|
||||
var chatSessions = {};
|
||||
|
||||
// === 右键菜单 ===
|
||||
// 防止并发注册
|
||||
@@ -58,9 +147,9 @@ async function updateContextMenuTitle() {
|
||||
} else if (auth && auth.type === 'ka') {
|
||||
askTitle = '使用知识助理提问';
|
||||
}
|
||||
try {
|
||||
chrome.contextMenus.update('ka-ask-selection', { title: askTitle });
|
||||
} catch (e) { /* 菜单还未创建时忽略 */ }
|
||||
chrome.contextMenus.update('ka-ask-selection', { title: askTitle }, function () {
|
||||
void chrome.runtime.lastError; // 菜单还未创建时静默忽略
|
||||
});
|
||||
}
|
||||
|
||||
chrome.contextMenus.onClicked.addListener(function (info, tab) {
|
||||
@@ -182,6 +271,255 @@ async function handleMessage(msg, sender) {
|
||||
} catch (err) {
|
||||
return { success: false, error: err.message || '截图失败' };
|
||||
}
|
||||
|
||||
// === WeKnora API 相关 ===
|
||||
case 'VALIDATE_CONFIG':
|
||||
// API Key 没有专门的验证端点,通过调用知识库列表接口来验证连通性和认证
|
||||
return apiRequest('GET', '/knowledge-bases');
|
||||
|
||||
case 'LOGIN': {
|
||||
// 用户名密码登录,获取 Bearer token
|
||||
var loginPayload = msg.payload || {};
|
||||
var loginResp = await apiRequest('POST', '/auth/login', {
|
||||
email: loginPayload.email,
|
||||
password: loginPayload.password
|
||||
});
|
||||
// 如果登录成功,将 token 存入 config
|
||||
if (loginResp && loginResp.success && loginResp.token) {
|
||||
var curConfig = await getConfigData() || {};
|
||||
curConfig.bearerToken = loginResp.token;
|
||||
curConfig.refreshToken = loginResp.refresh_token;
|
||||
await chrome.storage.local.set({ ka_config: curConfig });
|
||||
}
|
||||
return loginResp;
|
||||
}
|
||||
|
||||
case 'LIST_KNOWLEDGE_BASES':
|
||||
// agent_id 参数仅用于共享智能体(跨租户),本地/内置智能体不传
|
||||
var agentFilter = (msg.payload && msg.payload.sharedAgentId) ? '?agent_id=' + msg.payload.sharedAgentId : '';
|
||||
return apiRequest('GET', '/knowledge-bases' + agentFilter);
|
||||
|
||||
case 'LIST_AGENTS':
|
||||
return apiRequest('GET', '/agents');
|
||||
|
||||
case 'CREATE_KNOWLEDGE_BASE':
|
||||
return apiRequest('POST', '/knowledge-bases', msg.payload);
|
||||
|
||||
case 'CREATE_SESSION':
|
||||
return apiRequest('POST', '/sessions', msg.payload || {});
|
||||
|
||||
case 'LIST_SESSIONS':
|
||||
var p = msg.payload || {};
|
||||
return apiRequest('GET', '/sessions?page=' + (p.page || 1) + '&page_size=' + (p.page_size || 20));
|
||||
|
||||
case 'CLEAR_SESSION_MESSAGES': {
|
||||
var sid = (msg.payload || {}).sessionId;
|
||||
if (!sid) return { success: false, error: '缺少 sessionId' };
|
||||
return apiRequest('DELETE', '/sessions/' + sid + '/messages');
|
||||
}
|
||||
|
||||
case 'CHAT_QUERY': {
|
||||
// 真正的知识库问答 — 使用 SSE 流式输出
|
||||
var payload = msg.payload || {};
|
||||
var query = payload.query;
|
||||
if (!query) return { success: false, error: '请输入问题' };
|
||||
|
||||
// 获取或创建会话
|
||||
var sessionId = payload.sessionId;
|
||||
if (!sessionId) {
|
||||
var sessionResp = await apiRequest('POST', '/sessions', {});
|
||||
if (sessionResp && sessionResp.success && sessionResp.data) {
|
||||
sessionId = sessionResp.data.id;
|
||||
} else if (sessionResp && sessionResp.id) {
|
||||
sessionId = sessionResp.id;
|
||||
}
|
||||
if (!sessionId) {
|
||||
return { success: false, error: '创建会话失败: ' + (sessionResp.error || '未知错误') };
|
||||
}
|
||||
await chrome.storage.local.set({ ka_current_session: sessionId });
|
||||
}
|
||||
|
||||
// 确定使用知识库问答还是智能体问答
|
||||
var kbIds = payload.knowledgeBaseIds || [];
|
||||
var agentId = payload.agentId || '';
|
||||
var useAgent = payload.agentEnabled || false;
|
||||
var chatPath = useAgent
|
||||
? '/agent-chat/' + sessionId
|
||||
: '/knowledge-chat/' + sessionId;
|
||||
|
||||
// 构建完整请求体,参考 CreateKnowledgeQARequest
|
||||
var chatBody = { query: query };
|
||||
if (kbIds.length > 0) {
|
||||
chatBody.knowledge_base_ids = kbIds;
|
||||
}
|
||||
if (agentId) {
|
||||
chatBody.agent_id = agentId;
|
||||
}
|
||||
if (useAgent) {
|
||||
chatBody.agent_enabled = true;
|
||||
}
|
||||
if (payload.webSearchEnabled) {
|
||||
chatBody.web_search_enabled = true;
|
||||
}
|
||||
if (payload.mentionedItems) {
|
||||
chatBody.mentioned_items = payload.mentionedItems;
|
||||
}
|
||||
if (payload.images && payload.images.length > 0) {
|
||||
chatBody.images = payload.images;
|
||||
}
|
||||
|
||||
// 使用请求 ID 区分不同来源的流式推送
|
||||
var chatRequestId = payload._requestId || (Date.now().toString(36) + Math.random().toString(36).slice(2, 6));
|
||||
|
||||
// SSE 流式请求
|
||||
var streamResult = await apiChatStream(chatPath, chatBody);
|
||||
if (!streamResult.success) {
|
||||
return { success: false, error: streamResult.error };
|
||||
}
|
||||
|
||||
// 读取 SSE 流,逐块推送到前端
|
||||
try {
|
||||
var reader = streamResult.response.body.getReader();
|
||||
var decoder = new TextDecoder();
|
||||
var fullText = '';
|
||||
var buffer = '';
|
||||
|
||||
while (true) {
|
||||
var readResult = await reader.read();
|
||||
if (readResult.done) break;
|
||||
buffer += decoder.decode(readResult.value, { stream: true });
|
||||
|
||||
// SSE 格式: "event: message\ndata: {json}\n\n"
|
||||
// 按双换行分割完整事件块
|
||||
var eventBlocks = buffer.split('\n\n');
|
||||
buffer = eventBlocks.pop() || '';
|
||||
|
||||
for (var bi = 0; bi < eventBlocks.length; bi++) {
|
||||
var block = eventBlocks[bi].trim();
|
||||
if (!block) continue;
|
||||
|
||||
// 从事件块中提取 data 行
|
||||
var dataLine = '';
|
||||
var blockLines = block.split('\n');
|
||||
for (var li = 0; li < blockLines.length; li++) {
|
||||
var bline = blockLines[li];
|
||||
if (bline.startsWith('data:')) {
|
||||
dataLine = bline.substring(5).trim();
|
||||
}
|
||||
}
|
||||
if (!dataLine || dataLine === '[DONE]') continue;
|
||||
|
||||
try {
|
||||
var evt = JSON.parse(dataLine);
|
||||
var responseType = evt.response_type || '';
|
||||
|
||||
// 根据 response_type 处理不同事件
|
||||
if (responseType === 'answer') {
|
||||
var chunk = evt.content || '';
|
||||
if (chunk) {
|
||||
fullText += chunk;
|
||||
notifyStream({
|
||||
type: 'CHAT_STREAM_CHUNK',
|
||||
payload: { requestId: chatRequestId, sessionId: sessionId, responseType: 'answer', content: chunk, done: !!evt.done }
|
||||
});
|
||||
}
|
||||
} else if (responseType === 'thinking') {
|
||||
notifyStream({
|
||||
type: 'CHAT_STREAM_CHUNK',
|
||||
payload: { requestId: chatRequestId, sessionId: sessionId, responseType: 'thinking', content: evt.content || '', eventId: evt.data && evt.data.event_id }
|
||||
});
|
||||
} else if (responseType === 'tool_call') {
|
||||
notifyStream({
|
||||
type: 'CHAT_STREAM_CHUNK',
|
||||
payload: { requestId: chatRequestId, sessionId: sessionId, responseType: 'tool_call', content: evt.content || '', toolName: evt.data && evt.data.tool_name, eventId: evt.data && (evt.data.event_id || evt.data.tool_call_id) }
|
||||
});
|
||||
} else if (responseType === 'tool_result') {
|
||||
notifyStream({
|
||||
type: 'CHAT_STREAM_CHUNK',
|
||||
payload: { requestId: chatRequestId, sessionId: sessionId, responseType: 'tool_result', content: evt.content || '', toolName: evt.data && evt.data.tool_name, eventId: evt.data && (evt.data.event_id || evt.data.tool_call_id), success: evt.data && evt.data.success }
|
||||
});
|
||||
} else if (responseType === 'references') {
|
||||
var kRefs = evt.knowledge_references;
|
||||
if (Array.isArray(kRefs) && kRefs.length > 0) {
|
||||
notifyStream({
|
||||
type: 'CHAT_STREAM_CHUNK',
|
||||
payload: { requestId: chatRequestId, sessionId: sessionId, responseType: 'references', references: kRefs }
|
||||
});
|
||||
}
|
||||
} else if (responseType === 'error') {
|
||||
notifyStream({
|
||||
type: 'CHAT_STREAM_CHUNK',
|
||||
payload: { requestId: chatRequestId, sessionId: sessionId, responseType: 'error', content: evt.content || '请求出错' }
|
||||
});
|
||||
} else if (responseType === 'complete') {
|
||||
notifyStream({
|
||||
type: 'CHAT_STREAM_CHUNK',
|
||||
payload: { requestId: chatRequestId, sessionId: sessionId, responseType: 'complete', done: true }
|
||||
});
|
||||
}
|
||||
if (responseType === 'session_title') {
|
||||
notifyStream({
|
||||
type: 'CHAT_STREAM_CHUNK',
|
||||
payload: { requestId: chatRequestId, sessionId: sessionId, responseType: 'session_title', content: evt.content || '' }
|
||||
});
|
||||
}
|
||||
// agent_query 等事件静默忽略
|
||||
} catch (e) {
|
||||
// 非 JSON data 行,忽略
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true, data: fullText || '未获取到回复内容', sessionId: sessionId, requestId: chatRequestId };
|
||||
} catch (streamErr) {
|
||||
return { success: false, error: '读取回复流失败: ' + streamErr.message };
|
||||
}
|
||||
}
|
||||
|
||||
case 'SAVE_CLIP_TO_KB': {
|
||||
// 保存剪藏内容到知识库(作为手动知识条目)
|
||||
var pl = msg.payload || {};
|
||||
if (!pl.kbId || !pl.content) return { success: false, error: '缺少知识库 ID 或内容' };
|
||||
var contentWithMeta = pl.content;
|
||||
if (pl.url) {
|
||||
contentWithMeta = '> 来源: ' + pl.url + '\n\n' + pl.content;
|
||||
}
|
||||
return apiRequest('POST', '/knowledge-bases/' + pl.kbId + '/knowledge/manual', {
|
||||
title: pl.title || '快速笔记剪藏',
|
||||
content: contentWithMeta,
|
||||
status: 'publish'
|
||||
});
|
||||
}
|
||||
|
||||
case 'FETCH_FILE': {
|
||||
// 通过 background 代理带认证头请求文件(图片等),返回 data URL
|
||||
var filePath = (msg.payload || {}).filePath;
|
||||
if (!filePath) return { success: false, error: '缺少 filePath' };
|
||||
var cfg = await getConfigData();
|
||||
if (!cfg || !cfg.baseUrl) return { success: false, error: '未配置服务地址' };
|
||||
var fileBaseUrl = cfg.baseUrl.replace(/\/+$/, '').replace(/\/api\/v\d+$/, '');
|
||||
var fileUrl = fileBaseUrl + '/files?file_path=' + encodeURIComponent(filePath);
|
||||
var fileHeaders = await buildHeaders(cfg);
|
||||
try {
|
||||
var fileResp = await fetch(fileUrl, { method: 'GET', headers: fileHeaders });
|
||||
if (!fileResp.ok) return { success: false, error: 'HTTP ' + fileResp.status };
|
||||
var blob = await fileResp.blob();
|
||||
// 转为 data URL 以便跨上下文传递
|
||||
var reader2 = new FileReader();
|
||||
var dataUrl = await new Promise(function (resolve, reject) {
|
||||
reader2.onload = function () { resolve(reader2.result); };
|
||||
reader2.onerror = function () { reject(new Error('FileReader error')); };
|
||||
reader2.readAsDataURL(blob);
|
||||
});
|
||||
return { success: true, dataUrl: dataUrl };
|
||||
} catch (err) {
|
||||
return { success: false, error: err.message || '文件请求失败' };
|
||||
}
|
||||
}
|
||||
|
||||
case 'GET_USER_INFO':
|
||||
return apiRequest('GET', '/auth/me');
|
||||
|
||||
default:
|
||||
return { success: false, error: '未知消息类型' };
|
||||
}
|
||||
|
||||
@@ -385,22 +385,35 @@ body { width: 380px; max-height: 680px; font-family: -apple-system, "PingFang SC
|
||||
.note-save svg { width: 14px; height: 14px; }
|
||||
|
||||
/* ===== 知识问答 — 单行输入栏 ===== */
|
||||
.chat-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 底部输入区域整体包裹 */
|
||||
.chat-bar-wrapper {
|
||||
background: var(--white);
|
||||
border-radius: 50px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06), 0 0 1px rgba(0, 0, 0, 0.04);
|
||||
padding: 5px 5px 5px 6px;
|
||||
gap: 0;
|
||||
margin-top: 4px;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
transition: box-shadow 0.2s;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.chat-bar:focus-within {
|
||||
.chat-bar-wrapper:focus-within {
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08), 0 0 0 1.5px rgba(7, 193, 96, 0.15);
|
||||
}
|
||||
|
||||
/* 顶部工具栏:知识库 + 模式选择 */
|
||||
.chat-bar-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 6px 8px 0;
|
||||
}
|
||||
|
||||
.chat-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 5px 5px 8px;
|
||||
gap: 4px;
|
||||
position: relative;
|
||||
}
|
||||
/* 知识库选择按钮 */
|
||||
.chat-bar-kb {
|
||||
display: flex;
|
||||
@@ -422,6 +435,21 @@ body { width: 380px; max-height: 680px; font-family: -apple-system, "PingFang SC
|
||||
.chat-bar-kb svg { width: 13px; height: 13px; flex-shrink: 0; }
|
||||
.chat-bar-arrow { width: 10px !important; height: 10px !important; opacity: 0.5; }
|
||||
|
||||
/* 模式选择按钮 */
|
||||
.chat-bar-mode {
|
||||
display: flex; align-items: center; gap: 3px;
|
||||
border: none; background: #f3f4f5; border-radius: 50px;
|
||||
padding: 6px 8px 6px 10px; font-size: 11px; font-weight: 500;
|
||||
color: var(--text2); cursor: pointer; white-space: nowrap;
|
||||
transition: all 0.15s; flex-shrink: 0;
|
||||
}
|
||||
.chat-bar-mode:hover { background: #eaebed; color: var(--text); }
|
||||
.chat-bar-mode svg { width: 13px; height: 13px; flex-shrink: 0; }
|
||||
|
||||
/* 模式下拉菜单 */
|
||||
.popup-mode-menu { position: fixed; background: var(--white); border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.12), 0 0 1px rgba(0,0,0,0.06); padding: 6px 0; min-width: 160px; display: none; z-index: 100; }
|
||||
.popup-mode-menu.show { display: block; }
|
||||
|
||||
/* 分隔竖线 */
|
||||
.chat-bar-divider { width: 1px; height: 16px; background: #e5e5e5; margin: 0 8px; flex-shrink: 0; }
|
||||
|
||||
@@ -434,9 +462,13 @@ body { width: 380px; max-height: 680px; font-family: -apple-system, "PingFang SC
|
||||
font-family: inherit;
|
||||
outline: none;
|
||||
color: var(--text);
|
||||
padding: 0;
|
||||
padding: 6px 4px;
|
||||
min-width: 0;
|
||||
height: 30px;
|
||||
max-height: 100px;
|
||||
resize: none;
|
||||
overflow-y: auto;
|
||||
line-height: 18px;
|
||||
}
|
||||
.chat-bar-input::placeholder { color: #c0c0c0; }
|
||||
|
||||
@@ -491,28 +523,79 @@ body { width: 380px; max-height: 680px; font-family: -apple-system, "PingFang SC
|
||||
.kb-mode-item svg { width: 14px; height: 14px; }
|
||||
|
||||
/* 附件选项 */
|
||||
.kb-attach-item { padding: 8px 14px; font-size: 13px; color: var(--text2); cursor: pointer; display: flex; align-items: center; gap: 8px; transition: all 0.15s; }
|
||||
.kb-attach-item:hover { background: rgba(7,193,96,0.06); color: var(--primary); }
|
||||
.kb-attach-item svg { width: 14px; height: 14px; }
|
||||
/* 图片上传按钮 */
|
||||
.popup-image-btn {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
border: none; background: none; color: #999; cursor: pointer;
|
||||
width: 28px; height: 28px; padding: 0; flex-shrink: 0; border-radius: 6px; transition: all 0.15s;
|
||||
}
|
||||
.popup-image-btn:hover { color: var(--primary); background: rgba(7,193,96,0.06); }
|
||||
.popup-image-btn svg { width: 16px; height: 16px; }
|
||||
|
||||
/* 图片预览条 */
|
||||
.popup-image-previews {
|
||||
display: flex; gap: 6px; padding: 4px 12px; overflow-x: auto; flex-shrink: 0;
|
||||
}
|
||||
.popup-img-item { position: relative; flex-shrink: 0; }
|
||||
.popup-img-thumb { width: 40px; height: 40px; border-radius: 6px; object-fit: cover; border: 1px solid #e8e8e8; }
|
||||
.popup-img-remove {
|
||||
position: absolute; top: -4px; right: -4px;
|
||||
width: 14px; height: 14px; border-radius: 50%; background: rgba(0,0,0,0.5); color: #fff;
|
||||
font-size: 10px; line-height: 14px; text-align: center; cursor: pointer; border: none; padding: 0;
|
||||
}
|
||||
.popup-img-remove:hover { background: #e44; }
|
||||
|
||||
/* Settings Overlay */
|
||||
.settings-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.3); display: none; z-index: 100; backdrop-filter: blur(2px); }
|
||||
.settings-overlay.show { display: flex; align-items: center; justify-content: center; }
|
||||
.settings-panel { background: var(--white); border-radius: 14px; padding: 18px 20px; width: 340px; box-shadow: 0 8px 32px rgba(0,0,0,0.15); }
|
||||
.settings-handle { display: none; }
|
||||
.settings-title { font-size: 15px; font-weight: 700; margin-bottom: 12px; color: var(--text); }
|
||||
.settings-title { font-size: 15px; font-weight: 700; margin-bottom: 12px; color: var(--text); display: flex; align-items: center; justify-content: space-between; }
|
||||
.settings-close { border: none; background: none; color: var(--text3); cursor: pointer; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border-radius: 8px; }
|
||||
.settings-close:hover { background: #f0f0f0; color: var(--text); }
|
||||
.settings-close svg { width: 16px; height: 16px; }
|
||||
.settings-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 0; border-bottom: 1px solid #f2f2f2; }
|
||||
.settings-item:last-child { border-bottom: none; }
|
||||
.settings-label { font-size: 13px; color: var(--text3); }
|
||||
.settings-value { font-size: 13px; color: var(--text); font-weight: 500; max-width: 200px; text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.settings-logout { width: 100%; height: 40px; border: none; border-radius: 10px; background: #fff0f0; color: #e44; font-size: 13px; font-weight: 500; cursor: pointer; margin-top: 12px; transition: all 0.2s; }
|
||||
.settings-logout:hover { background: #ffe5e5; }
|
||||
.settings-item-clickable { cursor: pointer; border-radius: 6px; margin: 0 -6px; padding: 10px 6px !important; transition: background 0.15s; }
|
||||
.settings-item-clickable:hover { background: rgba(7,193,96,0.06); }
|
||||
.settings-item-clickable .settings-value { color: var(--primary); }
|
||||
.clip-kb-card { padding: 8px 10px; border: 1px solid #eee; border-radius: 8px; margin-bottom: 6px; cursor: pointer; transition: all 0.15s; }
|
||||
.clip-kb-card:hover { border-color: var(--primary); background: rgba(7,193,96,0.03); }
|
||||
.clip-kb-card.selected { border-color: var(--primary); background: rgba(7,193,96,0.06); }
|
||||
.clip-kb-card-name { font-size: 12px; font-weight: 600; color: var(--text); margin-bottom: 1px; }
|
||||
.clip-kb-card-desc { font-size: 10px; color: var(--text3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.clip-kb-card-meta { font-size: 10px; color: #ccc; margin-top: 2px; }
|
||||
|
||||
/* Toast */
|
||||
.toast { position: fixed; top: 20px; left: 50%; transform: translateX(-50%) translateY(-80px); background: var(--text); color: #fff; padding: 10px 22px; border-radius: 22px; font-size: 13px; z-index: 200; transition: transform 0.3s ease; pointer-events: none; box-shadow: 0 4px 12px rgba(0,0,0,0.15); }
|
||||
.toast.show { transform: translateX(-50%) translateY(0); }
|
||||
.toast.success { background: var(--primary); }
|
||||
.toast.error { background: #e44; }
|
||||
|
||||
/* === Markdown 渲染样式 === */
|
||||
.md-pre { background: #1e1e2e; color: #cdd6f4; border-radius: 8px; padding: 10px 12px; overflow-x: auto; font-size: 12px; margin: 6px 0; line-height: 1.5; }
|
||||
.md-pre code { font-family: "SF Mono", "Fira Code", "Consolas", monospace; white-space: pre; }
|
||||
.md-code { background: #f3f4f6; padding: 1px 5px; border-radius: 4px; font-size: 12px; font-family: "SF Mono", "Fira Code", "Consolas", monospace; }
|
||||
.md-h { margin: 8px 0 4px; line-height: 1.4; }
|
||||
h1.md-h { font-size: 18px; font-weight: 700; }
|
||||
h2.md-h { font-size: 16px; font-weight: 700; }
|
||||
h3.md-h { font-size: 14px; font-weight: 600; }
|
||||
h4.md-h, h5.md-h, h6.md-h { font-size: 13px; font-weight: 600; }
|
||||
.md-bq { border-left: 3px solid var(--primary); padding: 4px 12px; margin: 6px 0; background: #f8faf8; color: #666; border-radius: 0 4px 4px 0; font-size: 13px; }
|
||||
.md-list { margin: 4px 0; padding-left: 20px; font-size: 13px; line-height: 1.6; }
|
||||
.md-list li { margin: 2px 0; }
|
||||
.md-list .md-list { margin: 2px 0 2px 0; }
|
||||
.md-p { margin: 4px 0; line-height: 1.6; font-size: 13px; }
|
||||
.md-p a, .md-list a, .md-bq a { color: var(--primary); text-decoration: none; }
|
||||
.md-p a:hover, .md-list a:hover { text-decoration: underline; }
|
||||
.md-table { border-collapse: collapse; margin: 6px 0; font-size: 12px; width: 100%; }
|
||||
.md-table th { border: 1px solid #e5e5e5; padding: 4px 8px; background: #f7f8fa; font-weight: 600; text-align: left; }
|
||||
.md-table td { border: 1px solid #e5e5e5; padding: 4px 8px; }
|
||||
.md-table tr:hover td { background: rgba(7,193,96,0.02); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -634,14 +717,14 @@ body { width: 380px; max-height: 680px; font-family: -apple-system, "PingFang SC
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="19" cy="12" r="2"/></svg>
|
||||
</button>
|
||||
<div class="more-menu" id="more-menu">
|
||||
<button class="more-menu-item" id="btn-home">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 10.5L12 3l9 7.5"/><path d="M5 9.5V19a1 1 0 001 1h4v-5h4v5h4a1 1 0 001-1V9.5"/></svg>
|
||||
首页
|
||||
</button>
|
||||
<button class="more-menu-item" id="btn-clips">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5v-15A2.5 2.5 0 016.5 2H19v16H6.5a2.5 2.5 0 000 5H19"/></svg>
|
||||
笔记
|
||||
</button>
|
||||
<button class="more-menu-item" id="btn-sidebar">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||||
对话面板
|
||||
</button>
|
||||
<button class="more-menu-item" id="btn-settings">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 00-2 2v.18a2 2 0 01-1 1.73l-.43.25a2 2 0 01-2 0l-.15-.08a2 2 0 00-2.73.73l-.22.38a2 2 0 00.73 2.73l.15.1a2 2 0 011 1.72v.51a2 2 0 01-1 1.74l-.15.09a2 2 0 00-.73 2.73l.22.38a2 2 0 002.73.73l.15-.08a2 2 0 012 0l.43.25a2 2 0 011 1.73V20a2 2 0 002 2h.44a2 2 0 002-2v-.18a2 2 0 011-1.73l.43-.25a2 2 0 012 0l.15.08a2 2 0 002.73-.73l.22-.39a2 2 0 00-.73-2.73l-.15-.08a2 2 0 01-1-1.74v-.5a2 2 0 011-1.74l.15-.09a2 2 0 00.73-2.73l-.22-.38a2 2 0 00-2.73-.73l-.15.08a2 2 0 01-2 0l-.43-.25a2 2 0 01-1-1.73V4a2 2 0 00-2-2z"/><circle cx="12" cy="12" r="3"/></svg>
|
||||
设置
|
||||
@@ -755,41 +838,49 @@ body { width: 380px; max-height: 680px; font-family: -apple-system, "PingFang SC
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-bar">
|
||||
<div class="kb-dropdown" id="kb-dropdown">
|
||||
<button class="chat-bar-kb" id="btn-kb-select">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>
|
||||
<span id="kb-name">全部</span>
|
||||
<div class="popup-image-previews" id="popup-image-previews" style="display:none;"></div>
|
||||
<div class="chat-bar-wrapper">
|
||||
<div class="chat-bar-toolbar">
|
||||
<div class="kb-dropdown" id="kb-dropdown">
|
||||
<button class="chat-bar-kb" id="btn-kb-select">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>
|
||||
<span id="kb-name">全部</span>
|
||||
<svg class="chat-bar-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</button>
|
||||
<div class="kb-dropdown-menu" id="kb-menu">
|
||||
<div class="kb-dropdown-item selected" data-kb="all">
|
||||
<span class="kb-radio"></span>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
|
||||
全部知识库
|
||||
</div>
|
||||
<div class="kb-dropdown-item" data-kb="quick-note">
|
||||
<span class="kb-radio"></span>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
|
||||
快速笔记
|
||||
</div>
|
||||
<div class="kb-dropdown-item" data-kb="web-collect">
|
||||
<span class="kb-radio"></span>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/></svg>
|
||||
网页收藏
|
||||
</div>
|
||||
<div class="kb-dropdown-item" data-kb="product-doc">
|
||||
<span class="kb-radio"></span>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>
|
||||
产品文档
|
||||
</div>
|
||||
<div class="kb-dropdown-item" data-kb="tech-spec">
|
||||
<span class="kb-radio"></span>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
|
||||
技术规范
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="chat-bar-mode" id="btn-mode-select">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
||||
<span id="mode-name">快速回答</span>
|
||||
<svg class="chat-bar-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</button>
|
||||
<div class="kb-dropdown-menu" id="kb-menu">
|
||||
<div class="kb-dropdown-item selected" data-kb="all">
|
||||
<span class="kb-radio"></span>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
|
||||
全部知识库
|
||||
</div>
|
||||
<div class="kb-dropdown-item" data-kb="quick-note">
|
||||
<span class="kb-radio"></span>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
|
||||
快速笔记
|
||||
</div>
|
||||
<div class="kb-dropdown-item" data-kb="web-collect">
|
||||
<span class="kb-radio"></span>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/></svg>
|
||||
网页收藏
|
||||
</div>
|
||||
<div class="kb-dropdown-item" data-kb="product-doc">
|
||||
<span class="kb-radio"></span>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>
|
||||
产品文档
|
||||
</div>
|
||||
<div class="kb-dropdown-item" data-kb="tech-spec">
|
||||
<span class="kb-radio"></span>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
|
||||
技术规范
|
||||
</div>
|
||||
<div class="kb-dropdown-divider"></div>
|
||||
<div class="kb-dropdown-section">模式</div>
|
||||
<div class="popup-mode-menu" id="popup-mode-menu">
|
||||
<div class="kb-mode-item selected" data-mode="fast">
|
||||
<span class="kb-radio"></span>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
||||
@@ -805,19 +896,19 @@ body { width: 380px; max-height: 680px; font-family: -apple-system, "PingFang SC
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg>
|
||||
精确
|
||||
</div>
|
||||
<div class="kb-dropdown-divider"></div>
|
||||
<div class="kb-attach-item" id="btn-attach">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
|
||||
上传图片
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-bar-divider"></div>
|
||||
<input class="chat-bar-input" id="chat-input" type="text" placeholder="向知识库提问…" autocomplete="off">
|
||||
<button class="chat-bar-send" id="btn-chat-send">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8-8-8z"/></svg>
|
||||
</button>
|
||||
<div class="chat-bar">
|
||||
<button class="popup-image-btn" id="popup-image-btn" style="display:none;" title="上传图片">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
|
||||
</button>
|
||||
<textarea class="chat-bar-input" id="chat-input" placeholder="向知识库提问…" autocomplete="off" rows="1"></textarea>
|
||||
<button class="chat-bar-send" id="btn-chat-send">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8-8-8z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<input type="file" id="popup-image-input" accept="image/jpeg,image/png,image/gif,image/webp" multiple style="display:none;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -826,7 +917,12 @@ body { width: 380px; max-height: 680px; font-family: -apple-system, "PingFang SC
|
||||
<div class="settings-overlay" id="settings-overlay">
|
||||
<div class="settings-panel">
|
||||
<div class="settings-handle"></div>
|
||||
<div class="settings-title">设置</div>
|
||||
<div class="settings-title">
|
||||
<span>设置</span>
|
||||
<button class="settings-close" id="btn-settings-close">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="settings-content">
|
||||
<div class="settings-item">
|
||||
<span class="settings-label">账号类型</span>
|
||||
@@ -844,6 +940,10 @@ body { width: 380px; max-height: 680px; font-family: -apple-system, "PingFang SC
|
||||
<span class="settings-label">API Key</span>
|
||||
<span class="settings-value" id="set-key">-</span>
|
||||
</div>
|
||||
<div class="settings-item settings-item-clickable" id="set-clip-kb-row">
|
||||
<span class="settings-label">剪藏知识库</span>
|
||||
<span class="settings-value" id="set-clip-kb">未选择 ›</span>
|
||||
</div>
|
||||
<div class="settings-item">
|
||||
<span class="settings-label">版本</span>
|
||||
<span class="settings-value">v5.0.0</span>
|
||||
@@ -853,6 +953,23 @@ body { width: 380px; max-height: 680px; font-family: -apple-system, "PingFang SC
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 剪藏知识库选择面板 -->
|
||||
<div class="settings-overlay" id="clip-kb-overlay">
|
||||
<div class="settings-panel" style="max-height:300px;display:flex;flex-direction:column;width:300px;padding:14px 16px;">
|
||||
<div class="settings-title" style="display:flex;align-items:center;justify-content:space-between;font-size:14px;margin-bottom:8px;">
|
||||
<span>选择剪藏知识库</span>
|
||||
<button id="clip-kb-close" style="border:none;background:none;font-size:18px;color:#999;cursor:pointer;padding:0 4px;">×</button>
|
||||
</div>
|
||||
<div class="clip-kb-desc" style="font-size:11px;color:#999;margin-bottom:8px;">剪藏的网页内容将存储到此知识库</div>
|
||||
<div id="clip-kb-list" style="flex:1;overflow-y:auto;"></div>
|
||||
<div id="clip-kb-empty" style="display:none;text-align:center;padding:16px 0;">
|
||||
<div style="font-size:13px;color:#999;margin-bottom:8px;">暂无知识库</div>
|
||||
<div style="font-size:12px;color:#bbb;margin-bottom:12px;">请前往 WeKnora 创建知识库后使用剪藏功能</div>
|
||||
<button id="clip-kb-goto" style="height:32px;padding:0 16px;border:none;border-radius:16px;background:#07C160;color:#fff;font-size:12px;cursor:pointer;">前往创建</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast -->
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
// === State ===
|
||||
var currentUser = null; // { type: 'ka'|'wk', name, avatar, badge }
|
||||
var clipKbId = ''; // 剪藏目标知识库 ID
|
||||
var clipKbName = ''; // 剪藏目标知识库名称
|
||||
|
||||
// === DOM Helpers ===
|
||||
function $(id) { return document.getElementById(id); }
|
||||
@@ -167,8 +169,14 @@
|
||||
msg.textContent = '正在验证…';
|
||||
msg.className = 'ka-test-msg';
|
||||
|
||||
setTimeout(function () {
|
||||
sendMsg({ type: 'SET_CONFIG', payload: { baseUrl: url, apiKey: key } }).then(function () {
|
||||
// 先保存配置
|
||||
sendMsg({ type: 'SET_CONFIG', payload: { baseUrl: url, apiKey: key } }).then(function () {
|
||||
// 通过调用知识库列表来验证 API Key 和服务地址是否有效
|
||||
return sendMsg({ type: 'VALIDATE_CONFIG' });
|
||||
}).then(function (resp) {
|
||||
// VALIDATE_CONFIG 现在实际调用 GET /knowledge-bases,成功说明连通且认证有效
|
||||
if (resp && resp.success) {
|
||||
// API Key 认证成功,不需要调用 /auth/me(那个需要 Bearer token)
|
||||
sendMsg({ type: 'SET_AUTH', payload: { type: 'ka', name: '快速笔记用户', avatar: kaAvatarUrl } });
|
||||
msg.textContent = '验证通过';
|
||||
msg.className = 'ka-test-msg ok';
|
||||
@@ -176,8 +184,11 @@
|
||||
enterMain('ka', '快速笔记用户', kaAvatarUrl, '快速笔记 5.0');
|
||||
toast('登录成功', 'success');
|
||||
}, 500);
|
||||
});
|
||||
}, 800);
|
||||
} else {
|
||||
msg.textContent = '验证未通过,请检查服务地址和 API Key';
|
||||
msg.className = 'ka-test-msg err';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// WeKnora 配置页
|
||||
@@ -215,27 +226,58 @@
|
||||
$('wk-test-msg').className = 'wk-test-result';
|
||||
// 保存配置
|
||||
sendMsg({ type: 'SET_CONFIG', payload: { baseUrl: url, apiKey: key } }).then(function () {
|
||||
sendMsg({ type: 'SET_AUTH', payload: { type: 'wk', name: 'WeKnora 用户', avatar: '' } });
|
||||
$('wk-test-msg').textContent = '配置已保存';
|
||||
$('wk-test-msg').className = 'wk-test-result ok';
|
||||
setTimeout(function () {
|
||||
enterMain('wk', 'WeKnora 用户', '', 'WeKnora');
|
||||
}, 500);
|
||||
// 验证连接
|
||||
return sendMsg({ type: 'VALIDATE_CONFIG' });
|
||||
}).then(function (resp) {
|
||||
if (resp && resp.success) {
|
||||
// API Key 验证成功
|
||||
sendMsg({ type: 'SET_AUTH', payload: { type: 'wk', name: 'WeKnora 用户', avatar: '' } });
|
||||
$('wk-test-msg').textContent = '验证通过,配置已保存';
|
||||
$('wk-test-msg').className = 'wk-test-result ok';
|
||||
setTimeout(function () {
|
||||
enterMain('wk', 'WeKnora 用户', '', 'WeKnora');
|
||||
}, 500);
|
||||
} else {
|
||||
$('wk-test-msg').textContent = '验证未通过,请检查服务地址和 API Key';
|
||||
$('wk-test-msg').className = 'wk-test-result err';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// === 更新用户信息显示 ===
|
||||
function updateUserDisplay(type, name, avatarUrl, badge) {
|
||||
if (avatarUrl) {
|
||||
$('main-avatar-img').src = avatarUrl;
|
||||
$('main-avatar-img').style.display = 'block';
|
||||
$('main-letter').style.display = 'none';
|
||||
} else {
|
||||
$('main-avatar-img').style.display = 'none';
|
||||
$('main-letter').style.display = 'flex';
|
||||
$('main-letter').textContent = (name || 'U').charAt(0).toUpperCase();
|
||||
$('main-letter').style.background = '#07C160';
|
||||
}
|
||||
$('main-name').textContent = name;
|
||||
$('main-badge').textContent = badge;
|
||||
$('main-badge').className = 'main-badge ' + (type === 'ka' ? 'badge-ka' : 'badge-wk');
|
||||
}
|
||||
|
||||
// === 进入主界面 ===
|
||||
function enterMain(type, name, avatarUrl, badge) {
|
||||
currentUser = { type: type, name: name, avatar: avatarUrl, badge: badge };
|
||||
// 统一使用字母头像:圆形绿色底 + 字母 S
|
||||
$('main-avatar-img').style.display = 'none';
|
||||
$('main-letter').style.display = 'flex';
|
||||
$('main-letter').textContent = 'S';
|
||||
$('main-letter').style.background = '#07C160';
|
||||
// 设置名称和标签
|
||||
$('main-name').textContent = name;
|
||||
$('main-badge').textContent = badge;
|
||||
$('main-badge').className = 'main-badge ' + (type === 'ka' ? 'badge-ka' : 'badge-wk');
|
||||
updateUserDisplay(type, name, avatarUrl, badge);
|
||||
// 从 API 获取真实用户信息
|
||||
sendMsg({ type: 'GET_USER_INFO' }).then(function (resp) {
|
||||
if (resp && resp.success && resp.data) {
|
||||
var user = resp.data.user || resp.data;
|
||||
var realName = user.username || user.name || name;
|
||||
var realAvatar = user.avatar || avatarUrl;
|
||||
currentUser.name = realName;
|
||||
currentUser.avatar = realAvatar;
|
||||
// 同步更新 auth 存储
|
||||
sendMsg({ type: 'SET_AUTH', payload: { type: type, name: realName, avatar: realAvatar } });
|
||||
updateUserDisplay(type, realName, realAvatar, badge);
|
||||
}
|
||||
});
|
||||
// 设置面板数据
|
||||
$('set-type').textContent = badge;
|
||||
$('set-name').textContent = name;
|
||||
@@ -264,6 +306,166 @@
|
||||
}
|
||||
goPage('pg-main');
|
||||
loadLatestClip();
|
||||
loadAgentsForDropdown();
|
||||
}
|
||||
|
||||
// === 从 API 加载知识库列表到下拉菜单 ===
|
||||
function loadKnowledgeBasesForDropdown() {
|
||||
sendMsg({ type: 'LIST_KNOWLEDGE_BASES' }).then(function (resp) {
|
||||
if (resp && resp.success && resp.data) {
|
||||
var kbList = Array.isArray(resp.data) ? resp.data : (resp.data.items || []);
|
||||
popupAllKbs = kbList;
|
||||
filterAndRenderPopupKbs();
|
||||
// 检查剪藏知识库选择
|
||||
checkClipKbSelection(kbList);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var popupAllKbs = [];
|
||||
var popupAgents = [];
|
||||
|
||||
// === 根据当前选中智能体配置过滤知识库 ===
|
||||
function filterAndRenderPopupKbs() {
|
||||
var filtered = popupAllKbs;
|
||||
var agent = null;
|
||||
for (var i = 0; i < popupAgents.length; i++) {
|
||||
if (popupAgents[i].id === popupAgentId) { agent = popupAgents[i]; break; }
|
||||
}
|
||||
if (agent && agent.config) {
|
||||
var mode = agent.config.kb_selection_mode || 'all';
|
||||
if (mode === 'none') {
|
||||
filtered = [];
|
||||
} else if (mode === 'selected' && agent.config.knowledge_bases) {
|
||||
var allowedIds = agent.config.knowledge_bases;
|
||||
filtered = popupAllKbs.filter(function (kb) {
|
||||
return allowedIds.indexOf(kb.id) !== -1;
|
||||
});
|
||||
}
|
||||
}
|
||||
renderKbDropdownItems(filtered);
|
||||
}
|
||||
|
||||
// === 从 API 加载智能体列表到模式下拉 ===
|
||||
function loadAgentsForDropdown() {
|
||||
sendMsg({ type: 'LIST_AGENTS' }).then(function (resp) {
|
||||
if (resp && resp.success && resp.data) {
|
||||
popupAgents = Array.isArray(resp.data) ? resp.data : (resp.data.data || []);
|
||||
if (popupAgents.length > 0) {
|
||||
// 恢复持久化的模式选择
|
||||
chrome.storage.local.get('ka_selected_agent', function (stored) {
|
||||
if (!popupAgentId && stored && stored.ka_selected_agent && stored.ka_selected_agent.agentId) {
|
||||
var saved = stored.ka_selected_agent;
|
||||
for (var i = 0; i < popupAgents.length; i++) {
|
||||
if (popupAgents[i].id === saved.agentId) {
|
||||
popupAgentId = saved.agentId;
|
||||
popupAgentEnabled = !!saved.agentEnabled;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!popupAgentId) {
|
||||
popupAgentId = popupAgents[0].id;
|
||||
var isQA = popupAgents[0].id === 'builtin-quick-answer' || (popupAgents[0].config && popupAgents[0].config.agent_mode === 'quick-answer');
|
||||
popupAgentEnabled = !isQA;
|
||||
}
|
||||
// 设置当前选中智能体的图片上传能力
|
||||
for (var j = 0; j < popupAgents.length; j++) {
|
||||
if (popupAgents[j].id === popupAgentId) {
|
||||
popupAgentImageUpload = !!(popupAgents[j].config && popupAgents[j].config.image_upload_enabled);
|
||||
break;
|
||||
}
|
||||
}
|
||||
popupUpdateImageUI();
|
||||
renderAgentModeItems(popupAgents);
|
||||
// 加载全部知识库(前端根据智能体配置过滤)
|
||||
loadKnowledgeBasesForDropdown();
|
||||
});
|
||||
} else {
|
||||
loadKnowledgeBasesForDropdown();
|
||||
}
|
||||
} else {
|
||||
loadKnowledgeBasesForDropdown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderAgentModeItems(agentList) {
|
||||
var modeMenu = $('popup-mode-menu');
|
||||
if (!modeMenu) return;
|
||||
// 移除旧的模式选项
|
||||
var oldItems = modeMenu.querySelectorAll('.kb-mode-item');
|
||||
oldItems.forEach(function (item) { item.remove(); });
|
||||
|
||||
agentList.forEach(function (agent, idx) {
|
||||
var item = document.createElement('div');
|
||||
var isQA = agent.id === 'builtin-quick-answer' || (agent.config && agent.config.agent_mode === 'quick-answer');
|
||||
var isSelected = popupAgentId ? (agent.id === popupAgentId) : (idx === 0);
|
||||
item.className = 'kb-mode-item' + (isSelected ? ' selected' : '');
|
||||
item.setAttribute('data-agent-id', agent.id);
|
||||
item.innerHTML = '<span class="kb-radio"></span> ' + (function (s) {
|
||||
var d = document.createElement('div'); d.textContent = s; return d.innerHTML;
|
||||
})(agent.name);
|
||||
if (isSelected) {
|
||||
$('mode-name').textContent = agent.name;
|
||||
}
|
||||
item.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
popupAgentId = agent.id;
|
||||
popupAgentEnabled = !isQA;
|
||||
modeMenu.querySelectorAll('.kb-mode-item').forEach(function (i) { i.classList.remove('selected'); });
|
||||
item.classList.add('selected');
|
||||
$('mode-name').textContent = agent.name;
|
||||
modeMenu.classList.remove('show');
|
||||
// 持久化模式选择,同步给 sidepanel / weknora
|
||||
chrome.storage.local.set({ ka_selected_agent: { agentId: agent.id, agentEnabled: !isQA } });
|
||||
// 更新图片上传按钮可见性
|
||||
popupAgentImageUpload = !!(agent.config && agent.config.image_upload_enabled);
|
||||
popupUpdateImageUI();
|
||||
if (!popupAgentImageUpload && popupPendingImages.length > 0) popupClearImages();
|
||||
// 切换智能体后根据配置过滤知识库
|
||||
filterAndRenderPopupKbs();
|
||||
});
|
||||
modeMenu.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function renderKbDropdownItems(kbList) {
|
||||
var kbMenu = $('kb-menu');
|
||||
if (!kbMenu) return;
|
||||
// 移除旧的知识库选项(保留分隔线、模式等)
|
||||
var oldItems = kbMenu.querySelectorAll('.kb-dropdown-item');
|
||||
oldItems.forEach(function (item) { item.remove(); });
|
||||
// 在第一个分隔线之前插入新选项
|
||||
var firstDivider = kbMenu.querySelector('.kb-dropdown-divider');
|
||||
// 插入 "全部" 选项
|
||||
var allItem = createKbDropdownItem('all', '全部知识库', true);
|
||||
kbMenu.insertBefore(allItem, firstDivider);
|
||||
// 插入真实知识库
|
||||
kbList.forEach(function (kb) {
|
||||
var item = createKbDropdownItem(kb.id, kb.name, false);
|
||||
kbMenu.insertBefore(item, firstDivider);
|
||||
});
|
||||
}
|
||||
|
||||
function createKbDropdownItem(kbId, name, isSelected) {
|
||||
var div = document.createElement('div');
|
||||
div.className = 'kb-dropdown-item' + (isSelected ? ' selected' : '');
|
||||
div.setAttribute('data-kb', kbId);
|
||||
div.innerHTML = '<span class="kb-radio"></span> ' + (function (s) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = s;
|
||||
return d.innerHTML;
|
||||
})(name);
|
||||
div.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
selectedKb = kbId;
|
||||
$('kb-name').textContent = name.length > 4 ? name.substring(0, 4) : name;
|
||||
$('kb-menu').querySelectorAll('.kb-dropdown-item').forEach(function (i) { i.classList.remove('selected'); });
|
||||
div.classList.add('selected');
|
||||
$('kb-menu').classList.remove('show');
|
||||
});
|
||||
return div;
|
||||
}
|
||||
|
||||
// === 加载并显示最新一条内容 ===
|
||||
@@ -356,10 +558,114 @@
|
||||
var chatInput = $('chat-input');
|
||||
var sendBtn = $('btn-chat-send');
|
||||
var selectedKb = 'all';
|
||||
var selectedMode = 'fast';
|
||||
var popupAgentId = '';
|
||||
var popupAgentEnabled = false;
|
||||
var popupAgentImageUpload = false;
|
||||
|
||||
// 图片上传
|
||||
var popupPendingImages = [];
|
||||
var POPUP_MAX_IMAGES = 5;
|
||||
var POPUP_ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||
var POPUP_MAX_IMAGE_SIZE = 10 * 1024 * 1024;
|
||||
var popupImageInput = $('popup-image-input');
|
||||
var popupImageBtn = $('popup-image-btn');
|
||||
var popupImagePreviews = $('popup-image-previews');
|
||||
|
||||
function popupAddImages(files) {
|
||||
if (!popupAgentImageUpload) return;
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
if (popupPendingImages.length >= POPUP_MAX_IMAGES) { break; }
|
||||
var f = files[i];
|
||||
if (POPUP_ALLOWED_TYPES.indexOf(f.type) === -1 || f.size > POPUP_MAX_IMAGE_SIZE) continue;
|
||||
popupPendingImages.push({ file: f, preview: URL.createObjectURL(f) });
|
||||
}
|
||||
popupRenderPreviews();
|
||||
}
|
||||
|
||||
function popupRemoveImage(idx) {
|
||||
if (idx >= 0 && idx < popupPendingImages.length) {
|
||||
URL.revokeObjectURL(popupPendingImages[idx].preview);
|
||||
popupPendingImages.splice(idx, 1);
|
||||
}
|
||||
popupRenderPreviews();
|
||||
}
|
||||
|
||||
function popupClearImages() {
|
||||
popupPendingImages.forEach(function (img) { URL.revokeObjectURL(img.preview); });
|
||||
popupPendingImages = [];
|
||||
popupRenderPreviews();
|
||||
}
|
||||
|
||||
function popupRenderPreviews() {
|
||||
if (!popupImagePreviews) return;
|
||||
if (popupPendingImages.length === 0) {
|
||||
popupImagePreviews.style.display = 'none';
|
||||
popupImagePreviews.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
popupImagePreviews.style.display = 'flex';
|
||||
var html = '';
|
||||
for (var i = 0; i < popupPendingImages.length; i++) {
|
||||
html += '<div class="popup-img-item" data-idx="' + i + '">'
|
||||
+ '<img class="popup-img-thumb" src="' + popupPendingImages[i].preview + '">'
|
||||
+ '<span class="popup-img-remove">×</span></div>';
|
||||
}
|
||||
popupImagePreviews.innerHTML = html;
|
||||
popupImagePreviews.querySelectorAll('.popup-img-remove').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
popupRemoveImage(parseInt(btn.parentElement.getAttribute('data-idx')));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function popupUpdateImageUI() {
|
||||
if (popupImageBtn) popupImageBtn.style.display = popupAgentImageUpload ? '' : 'none';
|
||||
if (!popupAgentImageUpload && popupPendingImages.length > 0) popupClearImages();
|
||||
}
|
||||
|
||||
function popupFileToBase64(file) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function () { resolve(reader.result); };
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
if (popupImageInput) {
|
||||
popupImageInput.addEventListener('change', function () {
|
||||
if (popupImageInput.files) popupAddImages(Array.from(popupImageInput.files));
|
||||
popupImageInput.value = '';
|
||||
});
|
||||
}
|
||||
if (popupImageBtn) {
|
||||
popupImageBtn.addEventListener('click', function () {
|
||||
if (popupImageInput) popupImageInput.click();
|
||||
});
|
||||
}
|
||||
|
||||
chatInput.addEventListener('paste', function (e) {
|
||||
if (!popupAgentImageUpload) return;
|
||||
var items = e.clipboardData && e.clipboardData.items;
|
||||
if (!items) return;
|
||||
var imageFiles = [];
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
if (items[i].type.indexOf('image/') === 0) {
|
||||
var f = items[i].getAsFile();
|
||||
if (f) imageFiles.push(f);
|
||||
}
|
||||
}
|
||||
if (imageFiles.length > 0) {
|
||||
e.preventDefault();
|
||||
popupAddImages(imageFiles);
|
||||
}
|
||||
});
|
||||
|
||||
chatInput.addEventListener('input', function () {
|
||||
sendBtn.classList.toggle('active', chatInput.value.trim().length > 0);
|
||||
// 自动调整高度
|
||||
chatInput.style.height = 'auto';
|
||||
chatInput.style.height = Math.min(chatInput.scrollHeight, 100) + 'px';
|
||||
});
|
||||
|
||||
chatInput.addEventListener('keydown', function (e) {
|
||||
@@ -367,6 +673,7 @@
|
||||
e.preventDefault();
|
||||
sendChat();
|
||||
}
|
||||
// Shift+Enter 默认行为即换行,无需额外处理
|
||||
});
|
||||
|
||||
sendBtn.addEventListener('click', sendChat);
|
||||
@@ -374,32 +681,48 @@
|
||||
function sendChat() {
|
||||
var text = chatInput.value.trim();
|
||||
if (!text) return;
|
||||
var queryData = { query: text, user: currentUser, kb: selectedKb, mode: selectedMode, ts: Date.now() };
|
||||
var queryData = { query: text, user: currentUser, kb: selectedKb, ts: Date.now(), agentId: popupAgentId, agentEnabled: popupAgentEnabled };
|
||||
// 如果选中了具体知识库,传递知识库 ID
|
||||
if (selectedKb && selectedKb !== 'all') {
|
||||
queryData.knowledgeBaseIds = [selectedKb];
|
||||
}
|
||||
|
||||
if (chrome && chrome.storage) {
|
||||
chrome.storage.local.set({ ka_pending_query: queryData });
|
||||
function doPopupSend(data) {
|
||||
if (chrome && chrome.storage) {
|
||||
chrome.storage.local.set({ ka_pending_query: data });
|
||||
}
|
||||
if (chrome && chrome.runtime && chrome.runtime.sendMessage) {
|
||||
chrome.runtime.sendMessage({ type: 'CHAT_QUERY', payload: data }).catch(function () {});
|
||||
}
|
||||
if (chrome && chrome.tabs) {
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
|
||||
if (tabs && tabs[0]) {
|
||||
chrome.sidePanel.open({ tabId: tabs[0].id }).catch(function () {});
|
||||
}
|
||||
window.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
if (chrome && chrome.runtime && chrome.runtime.sendMessage) {
|
||||
chrome.runtime.sendMessage({ type: 'CHAT_QUERY', payload: queryData }).catch(function () {});
|
||||
}
|
||||
if (chrome && chrome.tabs) {
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
|
||||
if (tabs && tabs[0]) {
|
||||
chrome.sidePanel.open({ tabId: tabs[0].id }).catch(function () {});
|
||||
}
|
||||
window.close();
|
||||
|
||||
if (popupPendingImages.length > 0) {
|
||||
var promises = popupPendingImages.map(function (img) { return popupFileToBase64(img.file); });
|
||||
Promise.all(promises).then(function (dataURIs) {
|
||||
queryData.images = dataURIs.map(function (d) { return { data: d }; });
|
||||
popupClearImages();
|
||||
doPopupSend(queryData);
|
||||
});
|
||||
} else {
|
||||
doPopupSend(queryData);
|
||||
}
|
||||
}
|
||||
|
||||
// === 知识库下拉(合并模式选项) ===
|
||||
// === 知识库下拉 ===
|
||||
var kbMenu = $('kb-menu');
|
||||
var kbBtn = $('btn-kb-select');
|
||||
|
||||
function positionKbMenu() {
|
||||
var rect = kbBtn.getBoundingClientRect();
|
||||
var menuH = kbMenu.offsetHeight;
|
||||
// 优先向上弹出;空间不足则向下
|
||||
var top = rect.top - menuH - 6;
|
||||
if (top < 4) top = rect.bottom + 6;
|
||||
kbMenu.style.left = rect.left + 'px';
|
||||
@@ -408,10 +731,10 @@
|
||||
|
||||
kbBtn.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
popupModeMenu.classList.remove('show');
|
||||
var willShow = !kbMenu.classList.contains('show');
|
||||
kbMenu.classList.toggle('show');
|
||||
if (willShow) {
|
||||
// 先显示再计算高度
|
||||
requestAnimationFrame(positionKbMenu);
|
||||
}
|
||||
});
|
||||
@@ -427,20 +750,38 @@
|
||||
kbMenu.classList.remove('show');
|
||||
});
|
||||
});
|
||||
// 模式选择(合并在知识库菜单内)
|
||||
kbMenu.querySelectorAll('.kb-mode-item').forEach(function (item) {
|
||||
item.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
selectedMode = item.getAttribute('data-mode');
|
||||
kbMenu.querySelectorAll('.kb-mode-item').forEach(function (i) { i.classList.remove('selected'); });
|
||||
item.classList.add('selected');
|
||||
kbMenu.classList.remove('show');
|
||||
});
|
||||
|
||||
// === 模式下拉(独立) ===
|
||||
var popupModeMenu = $('popup-mode-menu');
|
||||
var modeBtn = $('btn-mode-select');
|
||||
|
||||
function positionModeMenu() {
|
||||
var rect = modeBtn.getBoundingClientRect();
|
||||
var menuH = popupModeMenu.offsetHeight;
|
||||
var top = rect.top - menuH - 6;
|
||||
if (top < 4) top = rect.bottom + 6;
|
||||
popupModeMenu.style.left = rect.left + 'px';
|
||||
popupModeMenu.style.top = top + 'px';
|
||||
}
|
||||
|
||||
modeBtn.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
kbMenu.classList.remove('show');
|
||||
var willShow = !popupModeMenu.classList.contains('show');
|
||||
popupModeMenu.classList.toggle('show');
|
||||
if (willShow) {
|
||||
requestAnimationFrame(positionModeMenu);
|
||||
}
|
||||
});
|
||||
|
||||
popupModeMenu.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
// 点击其他地方关闭下拉
|
||||
document.addEventListener('click', function () {
|
||||
kbMenu.classList.remove('show');
|
||||
popupModeMenu.classList.remove('show');
|
||||
if (moreMenu) moreMenu.classList.remove('show');
|
||||
});
|
||||
|
||||
@@ -586,61 +927,186 @@
|
||||
|
||||
function renderMarkdown(text) {
|
||||
if (!text) return '';
|
||||
var html = text;
|
||||
html = html.replace(/```(\w*)\n([\s\S]*?)```/g, function (_, lang, code) {
|
||||
return '<pre><code>' + escapeHtml(code.replace(/\n$/, '')) + '</code></pre>';
|
||||
|
||||
// 1. 提取代码块,替换为占位符
|
||||
var codeBlocks = [];
|
||||
var processed = text.replace(/```(\w*)\n([\s\S]*?)```/g, function (_, lang, code) {
|
||||
var idx = codeBlocks.length;
|
||||
codeBlocks.push('<pre class="md-pre"><code>' + escapeHtml(code.replace(/\n$/, '')) + '</code></pre>');
|
||||
return '\x00CODEBLOCK' + idx + '\x00';
|
||||
});
|
||||
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
|
||||
html = html.replace(/((?:\|.+\|\n)+)/g, function (tableBlock) {
|
||||
|
||||
// 2. 提取行内代码
|
||||
var inlineCodes = [];
|
||||
processed = processed.replace(/`([^`]+)`/g, function (_, code) {
|
||||
var idx = inlineCodes.length;
|
||||
inlineCodes.push('<code class="md-code">' + escapeHtml(code) + '</code>');
|
||||
return '\x00INLINE' + idx + '\x00';
|
||||
});
|
||||
|
||||
// 3. 表格处理 — 提取为占位符
|
||||
var tables = [];
|
||||
processed = processed.replace(/((?:\|.+\|\n)+)/g, function (tableBlock) {
|
||||
var rows = tableBlock.trim().split('\n');
|
||||
if (rows.length < 2) return tableBlock;
|
||||
var out = '<table>';
|
||||
var out = '<table class="md-table">';
|
||||
rows.forEach(function (row, i) {
|
||||
if (/^\|[\s\-:|]+\|$/.test(row)) return;
|
||||
var tag = i === 0 ? 'th' : 'td';
|
||||
var cells = row.split('|').filter(function (c, ci, arr) { return ci > 0 && ci < arr.length - 1; });
|
||||
out += '<tr>';
|
||||
cells.forEach(function (cell) { out += '<' + tag + '>' + cell.trim() + '</' + tag + '>'; });
|
||||
cells.forEach(function (cell) {
|
||||
out += '<' + tag + '>' + cell.trim() + '</' + tag + '>';
|
||||
});
|
||||
out += '</tr>';
|
||||
});
|
||||
out += '</table>';
|
||||
return out;
|
||||
var idx = tables.length;
|
||||
tables.push(out);
|
||||
return '\n\x00TABLE' + idx + '\x00\n';
|
||||
});
|
||||
var lines = html.split('\n');
|
||||
var result = [];
|
||||
var inList = false;
|
||||
var listType = '';
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
|
||||
// 4. 逐行解析为块元素
|
||||
var lines = processed.split('\n');
|
||||
var html = '';
|
||||
var i = 0;
|
||||
|
||||
while (i < lines.length) {
|
||||
var line = lines[i];
|
||||
if (line.indexOf('<pre>') !== -1 || line.indexOf('<table>') !== -1) {
|
||||
if (inList) { result.push('</' + listType + '>'); inList = false; }
|
||||
result.push(line); continue;
|
||||
|
||||
// 代码块占位符
|
||||
var cbMatch = line.match(/^\x00CODEBLOCK(\d+)\x00$/);
|
||||
if (cbMatch) {
|
||||
html += codeBlocks[parseInt(cbMatch[1])];
|
||||
i++; continue;
|
||||
}
|
||||
if (/^####\s+(.+)/.test(line)) { if (inList) { result.push('</' + listType + '>'); inList = false; } result.push('<h4>' + RegExp.$1 + '</h4>'); continue; }
|
||||
if (/^###\s+(.+)/.test(line)) { if (inList) { result.push('</' + listType + '>'); inList = false; } result.push('<h3>' + RegExp.$1 + '</h3>'); continue; }
|
||||
if (/^##\s+(.+)/.test(line)) { if (inList) { result.push('</' + listType + '>'); inList = false; } result.push('<h2>' + RegExp.$1 + '</h2>'); continue; }
|
||||
if (/^#\s+(.+)/.test(line)) { if (inList) { result.push('</' + listType + '>'); inList = false; } result.push('<h1>' + RegExp.$1 + '</h1>'); continue; }
|
||||
if (/^---+$/.test(line.trim())) { if (inList) { result.push('</' + listType + '>'); inList = false; } result.push('<hr>'); continue; }
|
||||
if (/^>\s*(.*)/.test(line)) { if (inList) { result.push('</' + listType + '>'); inList = false; } result.push('<blockquote>' + RegExp.$1 + '</blockquote>'); continue; }
|
||||
if (/^[\-\*]\s+(.+)/.test(line)) {
|
||||
if (!inList || listType !== 'ul') { if (inList) result.push('</' + listType + '>'); result.push('<ul>'); inList = true; listType = 'ul'; }
|
||||
result.push('<li>' + RegExp.$1 + '</li>'); continue;
|
||||
|
||||
// 表格占位符
|
||||
var tbMatch = line.match(/^\x00TABLE(\d+)\x00$/);
|
||||
if (tbMatch) {
|
||||
html += tables[parseInt(tbMatch[1])];
|
||||
i++; continue;
|
||||
}
|
||||
if (/^\d+\.\s+(.+)/.test(line)) {
|
||||
if (!inList || listType !== 'ol') { if (inList) result.push('</' + listType + '>'); result.push('<ol>'); inList = true; listType = 'ol'; }
|
||||
result.push('<li>' + RegExp.$1 + '</li>'); continue;
|
||||
|
||||
// 标题
|
||||
if (/^(#{1,6})\s+(.+)/.test(line)) {
|
||||
var level = RegExp.$1.length;
|
||||
html += '<h' + level + ' class="md-h">' + inlineFormat(RegExp.$2) + '</h' + level + '>';
|
||||
i++; continue;
|
||||
}
|
||||
if (inList) { result.push('</' + listType + '>'); inList = false; }
|
||||
if (line.trim() === '') continue;
|
||||
result.push('<p>' + line + '</p>');
|
||||
|
||||
// 引用块
|
||||
if (/^>\s*(.*)/.test(line)) {
|
||||
var bqLines = [];
|
||||
while (i < lines.length && /^>\s*(.*)/.test(lines[i])) {
|
||||
bqLines.push(RegExp.$1);
|
||||
i++;
|
||||
}
|
||||
html += '<blockquote class="md-bq">' + inlineFormat(bqLines.join('<br>')) + '</blockquote>';
|
||||
continue;
|
||||
}
|
||||
|
||||
// 无序列表
|
||||
if (/^(\s*)[*\-]\s+(.+)/.test(line)) {
|
||||
html += parseList(lines, i, 'ul');
|
||||
while (i < lines.length && /^(\s*)[*\-]\s+/.test(lines[i])) i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 有序列表
|
||||
if (/^(\s*)\d+\.\s+(.+)/.test(line)) {
|
||||
html += parseList(lines, i, 'ol');
|
||||
while (i < lines.length && /^(\s*)\d+\.\s+/.test(lines[i])) i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 水平线
|
||||
if (/^[-*_]{3,}\s*$/.test(line.trim())) {
|
||||
html += '<hr>';
|
||||
i++; continue;
|
||||
}
|
||||
|
||||
// 空行
|
||||
if (line.trim() === '') {
|
||||
i++; continue;
|
||||
}
|
||||
|
||||
// 普通段落
|
||||
var pLines = [];
|
||||
while (i < lines.length) {
|
||||
var pl = lines[i];
|
||||
if (pl.trim() === '' || /^#{1,6}\s/.test(pl) || /^>\s/.test(pl) ||
|
||||
/^(\s*)[*\-]\s+/.test(pl) || /^(\s*)\d+\.\s+/.test(pl) ||
|
||||
/^\x00CODEBLOCK/.test(pl) || /^\x00TABLE/.test(pl) ||
|
||||
/^[-*_]{3,}\s*$/.test(pl.trim())) break;
|
||||
pLines.push(pl);
|
||||
i++;
|
||||
}
|
||||
html += '<p class="md-p">' + inlineFormat(pLines.join('<br>')) + '</p>';
|
||||
}
|
||||
if (inList) result.push('</' + listType + '>');
|
||||
html = result.join('\n');
|
||||
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
||||
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
||||
html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1">');
|
||||
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
|
||||
|
||||
// 5. 恢复行内代码占位符
|
||||
html = html.replace(/\x00INLINE(\d+)\x00/g, function (_, idx) {
|
||||
return inlineCodes[parseInt(idx)];
|
||||
});
|
||||
|
||||
return html;
|
||||
|
||||
// --- 列表解析(支持嵌套)---
|
||||
function parseList(allLines, startIdx, defaultType) {
|
||||
var items = [];
|
||||
var j = startIdx;
|
||||
var baseIndent = -1;
|
||||
var listTag = defaultType;
|
||||
var re = listTag === 'ul' ? /^(\s*)[*\-]\s+(.+)/ : /^(\s*)\d+\.\s+(.+)/;
|
||||
|
||||
while (j < allLines.length) {
|
||||
var m = allLines[j].match(re);
|
||||
if (!m) break;
|
||||
var indent = m[1].length;
|
||||
if (baseIndent < 0) baseIndent = indent;
|
||||
if (indent > baseIndent) {
|
||||
var subStart = j;
|
||||
while (j < allLines.length) {
|
||||
var sm = allLines[j].match(re);
|
||||
if (!sm || sm[1].length < indent) break;
|
||||
j++;
|
||||
}
|
||||
var subHtml = parseList(allLines, subStart, listTag);
|
||||
if (items.length > 0) {
|
||||
items[items.length - 1] += subHtml;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (indent < baseIndent) break;
|
||||
items.push(inlineFormat(m[2]));
|
||||
j++;
|
||||
}
|
||||
|
||||
var out = '<' + listTag + ' class="md-list">';
|
||||
for (var k = 0; k < items.length; k++) {
|
||||
out += '<li>' + items[k] + '</li>';
|
||||
}
|
||||
out += '</' + listTag + '>';
|
||||
return out;
|
||||
}
|
||||
|
||||
// --- 行内格式化 ---
|
||||
function inlineFormat(s) {
|
||||
if (!s) return '';
|
||||
// 图片(必须在链接之前)
|
||||
s = s.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" style="max-width:100%;border-radius:6px;margin:4px 0;">');
|
||||
// 链接
|
||||
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
|
||||
s = s.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>');
|
||||
s = s.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
||||
s = s.replace(/(?<!\*)\*([^\s*][^*]*?)\*(?!\*)/g, '<em>$1</em>');
|
||||
s = s.replace(/~~(.+?)~~/g, '<del>$1</del>');
|
||||
s = s.replace(/\x00INLINE(\d+)\x00/g, function (_, idx) {
|
||||
return inlineCodes[parseInt(idx)];
|
||||
});
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
// === Markdown 速记 ===
|
||||
@@ -795,31 +1261,160 @@
|
||||
}
|
||||
});
|
||||
|
||||
// === 主页按钮 ===
|
||||
$('btn-home').addEventListener('click', function () {
|
||||
moreMenu.classList.remove('show');
|
||||
// 打开 WeKnora 管理页面,弹窗保留
|
||||
if (chrome && chrome.tabs) {
|
||||
chrome.tabs.create({ url: chrome.runtime.getURL('weknora.html') });
|
||||
$('btn-settings-close').addEventListener('click', function () {
|
||||
$('settings-overlay').classList.remove('show');
|
||||
});
|
||||
|
||||
// === 剪藏知识库选择 ===
|
||||
var clipKbOverlay = $('clip-kb-overlay');
|
||||
|
||||
// 检查是否需要引导用户选择剪藏知识库
|
||||
function checkClipKbSelection(kbList) {
|
||||
chrome.storage.local.get(['clipKbId', 'clipKbName'], function (data) {
|
||||
clipKbId = data.clipKbId || '';
|
||||
clipKbName = data.clipKbName || '';
|
||||
|
||||
// 验证保存的 KB 是否仍存在
|
||||
if (clipKbId) {
|
||||
var found = kbList.some(function (kb) { return kb.id === clipKbId; });
|
||||
if (!found) {
|
||||
clipKbId = '';
|
||||
clipKbName = '';
|
||||
chrome.storage.local.remove(['clipKbId', 'clipKbName']);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新设置面板显示
|
||||
updateClipKbDisplay();
|
||||
|
||||
// 首次使用 — 引导选择
|
||||
if (!clipKbId) {
|
||||
showClipKbSelector(kbList);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateClipKbDisplay() {
|
||||
var el = $('set-clip-kb');
|
||||
if (el) {
|
||||
el.textContent = clipKbName ? (clipKbName + ' ›') : '未选择 ›';
|
||||
}
|
||||
}
|
||||
|
||||
function showClipKbSelector(kbList) {
|
||||
var list = kbList || popupAllKbs;
|
||||
var listEl = $('clip-kb-list');
|
||||
var emptyEl = $('clip-kb-empty');
|
||||
|
||||
if (!list || list.length === 0) {
|
||||
listEl.style.display = 'none';
|
||||
emptyEl.style.display = 'block';
|
||||
} else {
|
||||
goPage('pg-login');
|
||||
listEl.style.display = 'block';
|
||||
emptyEl.style.display = 'none';
|
||||
var html = '';
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var kb = list[i];
|
||||
var selected = kb.id === clipKbId ? ' selected' : '';
|
||||
html += '<div class="clip-kb-card' + selected + '" data-kb-id="' + kb.id + '" data-kb-name="' + escapeHtml(kb.name) + '">'
|
||||
+ '<div class="clip-kb-card-name">' + escapeHtml(kb.name) + '</div>'
|
||||
+ '<div class="clip-kb-card-desc">' + escapeHtml(kb.description || '暂无描述') + '</div>'
|
||||
+ '<div class="clip-kb-card-meta">' + (kb.document_count || 0) + ' 文档 · ' + (kb.chunk_count || 0) + ' 分段</div>'
|
||||
+ '</div>';
|
||||
}
|
||||
listEl.innerHTML = html;
|
||||
|
||||
// 点击选择
|
||||
listEl.querySelectorAll('.clip-kb-card').forEach(function (card) {
|
||||
card.addEventListener('click', function () {
|
||||
clipKbId = card.getAttribute('data-kb-id');
|
||||
clipKbName = card.getAttribute('data-kb-name');
|
||||
chrome.storage.local.set({ clipKbId: clipKbId, clipKbName: clipKbName });
|
||||
updateClipKbDisplay();
|
||||
clipKbOverlay.classList.remove('show');
|
||||
toast('已选择知识库: ' + clipKbName, 'success');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
clipKbOverlay.classList.add('show');
|
||||
}
|
||||
|
||||
// 设置面板中点击"剪藏知识库"
|
||||
$('set-clip-kb-row').addEventListener('click', function () {
|
||||
$('settings-overlay').classList.remove('show');
|
||||
showClipKbSelector();
|
||||
});
|
||||
|
||||
// 关闭按钮
|
||||
$('clip-kb-close').addEventListener('click', function () {
|
||||
clipKbOverlay.classList.remove('show');
|
||||
});
|
||||
|
||||
// 背景点击关闭
|
||||
clipKbOverlay.addEventListener('click', function (e) {
|
||||
if (e.target === clipKbOverlay) {
|
||||
clipKbOverlay.classList.remove('show');
|
||||
}
|
||||
});
|
||||
|
||||
// === 查看收藏按钮 ===
|
||||
$('btn-clips').addEventListener('click', function () {
|
||||
// "前往创建"按钮 — 打开 WeKnora 主站
|
||||
$('clip-kb-goto').addEventListener('click', function () {
|
||||
clipKbOverlay.classList.remove('show');
|
||||
sendMsg({ type: 'GET_CONFIG' }).then(function (resp) {
|
||||
if (resp && resp.success && resp.data && resp.data.baseUrl) {
|
||||
var baseUrl = resp.data.baseUrl.replace(/\/api\/v1\/?$/, '');
|
||||
if (chrome && chrome.tabs) {
|
||||
chrome.tabs.create({ url: baseUrl });
|
||||
}
|
||||
} else {
|
||||
toast('请先配置服务地址', 'error');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// === 打开对话面板(sidebar)===
|
||||
$('btn-sidebar').addEventListener('click', function () {
|
||||
moreMenu.classList.remove('show');
|
||||
if (chrome && chrome.tabs) {
|
||||
chrome.tabs.create({ url: chrome.runtime.getURL('clips.html') });
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
|
||||
if (tabs && tabs[0]) {
|
||||
chrome.sidePanel.open({ tabId: tabs[0].id }).catch(function () {});
|
||||
}
|
||||
window.close();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// === 打开笔记/知识库页面 ===
|
||||
function openNotesPage() {
|
||||
if (!currentUser || !chrome || !chrome.tabs) return;
|
||||
if (currentUser.type === 'wk') {
|
||||
// 自部署 WeKnora — 用配置的 baseUrl
|
||||
sendMsg({ type: 'GET_CONFIG' }).then(function (resp) {
|
||||
if (resp && resp.success && resp.data && resp.data.baseUrl) {
|
||||
var url = resp.data.baseUrl.replace(/\/api\/v1\/?$/, '');
|
||||
chrome.tabs.create({ url: url });
|
||||
} else {
|
||||
toast('请先配置服务地址', 'error');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 知识助手官网用户
|
||||
chrome.tabs.create({ url: 'https://weknora.weixin.qq.com' });
|
||||
}
|
||||
}
|
||||
|
||||
// === 查看笔记按钮 ===
|
||||
$('btn-clips').addEventListener('click', function () {
|
||||
moreMenu.classList.remove('show');
|
||||
openNotesPage();
|
||||
});
|
||||
|
||||
// === 最新内容卡片 — 查看全部 ===
|
||||
$('btn-latest-more').addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
if (chrome && chrome.tabs) {
|
||||
chrome.tabs.create({ url: chrome.runtime.getURL('clips.html') });
|
||||
}
|
||||
openNotesPage();
|
||||
});
|
||||
|
||||
// 点击最新内容卡片本身也打开列表页;双击翻转进入速记编辑
|
||||
@@ -837,9 +1432,7 @@
|
||||
// 单击:延迟执行,等看有没有双击
|
||||
clickTimer = setTimeout(function () {
|
||||
clickTimer = null;
|
||||
if (chrome && chrome.tabs) {
|
||||
chrome.tabs.create({ url: chrome.runtime.getURL('clips.html') });
|
||||
}
|
||||
openNotesPage();
|
||||
}, 250);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -23,7 +23,10 @@ body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
.header-icon { width: 30px; height: 30px; background: var(--primary); border-radius: 10px; display: flex; align-items: center; justify-content: center; }
|
||||
.header-icon svg { width: 16px; height: 16px; color: #fff; }
|
||||
.header h1 { font-size: 15px; font-weight: 600; flex: 1; }
|
||||
.header h1 { font-size: 15px; font-weight: 600; flex: 1; min-width: 0; }
|
||||
.header-title-wrap { flex: 1; min-width: 0; display: flex; flex-direction: column; justify-content: center; }
|
||||
.header-session-title { font-size: 11px; font-weight: 400; color: var(--text3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: none; margin-top: 1px; }
|
||||
.header-session-title.show { display: block; }
|
||||
.header-btn { width: 32px; height: 32px; border: none; background: #f5f6f7; border-radius: 10px; cursor: pointer; display: flex; align-items: center; justify-content: center; color: var(--text3); transition: all 0.2s; }
|
||||
.header-btn:hover { background: #eee; color: var(--text); }
|
||||
.header-btn svg { width: 16px; height: 16px; }
|
||||
@@ -38,6 +41,8 @@ body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
word-wrap: break-word;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.msg-user {
|
||||
align-self: flex-end;
|
||||
@@ -100,27 +105,41 @@ body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
.quick-btn:hover { background: rgba(7,193,96,0.08); color: var(--primary); box-shadow: 0 2px 8px rgba(7,193,96,0.12); }
|
||||
|
||||
/* ===== Input Area — 单行胶囊输入栏 ===== */
|
||||
/* ===== Input Area ===== */
|
||||
.input-area {
|
||||
padding: 10px 14px 14px;
|
||||
flex-shrink: 0;
|
||||
background: transparent;
|
||||
}
|
||||
.sp-chat-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
/* 整体包裹卡片 */
|
||||
.sp-chat-bar-wrapper {
|
||||
background: var(--white);
|
||||
border-radius: 50px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 0 6px rgba(0,0,0,0.04), 0 4px 20px rgba(0,0,0,0.10);
|
||||
padding: 5px 5px 5px 6px;
|
||||
gap: 0;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
transition: box-shadow 0.2s;
|
||||
}
|
||||
.sp-chat-bar:focus-within {
|
||||
.sp-chat-bar-wrapper:focus-within {
|
||||
box-shadow: 0 4px 24px rgba(0,0,0,0.10), 0 0 0 1.5px rgba(7,193,96,0.18);
|
||||
}
|
||||
|
||||
/* 顶部工具栏:知识库 + 模式 */
|
||||
.sp-chat-bar-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 6px 8px 0;
|
||||
}
|
||||
|
||||
.sp-chat-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 5px 5px 8px;
|
||||
gap: 4px;
|
||||
position: relative;
|
||||
}
|
||||
/* 知识库选择按钮 */
|
||||
.sp-chat-bar-kb {
|
||||
display: flex;
|
||||
@@ -142,6 +161,25 @@ body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
.sp-chat-bar-kb svg { width: 13px; height: 13px; flex-shrink: 0; }
|
||||
.sp-chat-bar-arrow { width: 10px !important; height: 10px !important; opacity: 0.5; }
|
||||
|
||||
/* 模式选择按钮 */
|
||||
.sp-chat-bar-mode {
|
||||
display: flex; align-items: center; gap: 3px;
|
||||
border: none; background: #f3f4f5; border-radius: 50px;
|
||||
padding: 6px 8px 6px 10px; font-size: 11px; font-weight: 500;
|
||||
color: var(--text2); cursor: pointer; white-space: nowrap;
|
||||
transition: all 0.15s; flex-shrink: 0;
|
||||
}
|
||||
.sp-chat-bar-mode:hover { background: #eaebed; color: var(--text); }
|
||||
.sp-chat-bar-mode svg { width: 13px; height: 13px; flex-shrink: 0; }
|
||||
|
||||
/* 模式下拉菜单 */
|
||||
.sp-mode-dropdown-menu {
|
||||
position: fixed; background: var(--white); border-radius: 14px;
|
||||
box-shadow: 0 0 6px rgba(0,0,0,0.04), 0 8px 32px rgba(0,0,0,0.14);
|
||||
padding: 6px 0; min-width: 160px; display: none; z-index: 100;
|
||||
}
|
||||
.sp-mode-dropdown-menu.show { display: block; }
|
||||
|
||||
/* 分隔竖线 */
|
||||
.sp-chat-bar-divider { width: 1px; height: 16px; background: #e5e5e5; margin: 0 8px; flex-shrink: 0; }
|
||||
|
||||
@@ -154,9 +192,13 @@ body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
font-family: inherit;
|
||||
outline: none;
|
||||
color: var(--text);
|
||||
padding: 0;
|
||||
padding: 6px 4px;
|
||||
min-width: 0;
|
||||
height: 30px;
|
||||
max-height: 120px;
|
||||
resize: none;
|
||||
overflow-y: auto;
|
||||
line-height: 18px;
|
||||
}
|
||||
.sp-chat-bar-input::placeholder { color: #c0c0c0; }
|
||||
|
||||
@@ -179,6 +221,33 @@ body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
.sp-chat-bar-send.active { background: var(--primary); color: #fff; box-shadow: 0 2px 8px rgba(7,193,96,0.25); }
|
||||
.sp-chat-bar-send.active:hover { background: var(--primary-dark); }
|
||||
|
||||
/* 图片上传按钮 */
|
||||
.sp-chat-bar-img {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
border: none; background: none; color: #999; cursor: pointer;
|
||||
width: 28px; height: 28px; padding: 0; flex-shrink: 0; border-radius: 6px; transition: all 0.15s;
|
||||
}
|
||||
.sp-chat-bar-img:hover { color: var(--primary); background: rgba(7,193,96,0.06); }
|
||||
.sp-chat-bar-img svg { width: 16px; height: 16px; }
|
||||
|
||||
/* 图片预览条 */
|
||||
.sp-image-previews {
|
||||
display: flex; gap: 6px; padding: 6px 12px 0; overflow-x: auto; flex-shrink: 0;
|
||||
}
|
||||
.sp-img-item { position: relative; flex-shrink: 0; }
|
||||
.sp-img-thumb { width: 44px; height: 44px; border-radius: 6px; object-fit: cover; border: 1px solid #e8e8e8; }
|
||||
.sp-img-remove {
|
||||
position: absolute; top: -4px; right: -4px;
|
||||
width: 16px; height: 16px; border-radius: 50%; background: rgba(0,0,0,0.5); color: #fff;
|
||||
font-size: 12px; line-height: 16px; text-align: center; cursor: pointer; border: none; padding: 0;
|
||||
}
|
||||
.sp-img-remove:hover { background: #e44; }
|
||||
|
||||
/* 用户消息中的图片 */
|
||||
.msg-user-images { display: flex; gap: 6px; margin-top: 6px; flex-wrap: wrap; }
|
||||
.msg-user-img { width: 60px; height: 60px; border-radius: 8px; object-fit: cover; border: 1px solid rgba(255,255,255,0.3); cursor: pointer; }
|
||||
.msg-user-img:hover { opacity: 0.85; }
|
||||
|
||||
/* ===== 合并下拉菜单 (fixed定位) ===== */
|
||||
.sp-kb-dropdown-menu {
|
||||
position: fixed;
|
||||
@@ -293,6 +362,162 @@ body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.clip-action-btn:hover { background: rgba(7,193,96,0.08); color: var(--primary); }
|
||||
|
||||
/* === Markdown 渲染样式 === */
|
||||
.md-pre { background: #1e1e2e; color: #cdd6f4; border-radius: 8px; padding: 10px 12px; overflow-x: auto; font-size: 12px; margin: 6px 0; line-height: 1.5; }
|
||||
.md-pre code { font-family: "SF Mono", "Fira Code", "Consolas", monospace; white-space: pre; }
|
||||
.md-code { background: #f3f4f6; padding: 1px 5px; border-radius: 4px; font-size: 12px; font-family: "SF Mono", "Fira Code", "Consolas", monospace; }
|
||||
.md-h { margin: 8px 0 4px; line-height: 1.4; }
|
||||
h1.md-h { font-size: 18px; font-weight: 700; }
|
||||
h2.md-h { font-size: 16px; font-weight: 700; }
|
||||
h3.md-h { font-size: 14px; font-weight: 600; }
|
||||
h4.md-h, h5.md-h, h6.md-h { font-size: 13px; font-weight: 600; }
|
||||
.md-bq { border-left: 3px solid var(--primary); padding: 4px 12px; margin: 6px 0; background: #f8faf8; color: #666; border-radius: 0 4px 4px 0; font-size: 13px; }
|
||||
.md-list { margin: 4px 0; padding-left: 20px; font-size: 13px; line-height: 1.6; }
|
||||
.md-list li { margin: 2px 0; }
|
||||
.md-list .md-list { margin: 2px 0 2px 0; }
|
||||
.md-p { margin: 4px 0; line-height: 1.6; font-size: 13px; }
|
||||
.md-p a, .md-list a, .md-bq a { color: var(--primary); text-decoration: none; }
|
||||
.md-p a:hover, .md-list a:hover { text-decoration: underline; }
|
||||
.md-table { border-collapse: collapse; margin: 6px 0; font-size: 12px; }
|
||||
.md-table th { border: 1px solid #e5e5e5; padding: 4px 8px; background: #f7f8fa; font-weight: 600; text-align: left; white-space: nowrap; }
|
||||
.md-table td { border: 1px solid #e5e5e5; padding: 4px 8px; }
|
||||
.md-table tr:hover td { background: rgba(7,193,96,0.02); }
|
||||
.md-table-wrap { overflow-x: auto; margin: 6px 0; }
|
||||
.md-img { max-width: 100%; max-height: 200px; border-radius: 6px; margin: 6px 0; display: block; object-fit: contain; cursor: pointer; }
|
||||
.md-img:hover { opacity: 0.85; }
|
||||
|
||||
/* 图片灯箱 */
|
||||
.sp-lightbox {
|
||||
position: fixed; inset: 0; background: rgba(0,0,0,0.8); z-index: 9999;
|
||||
display: flex; align-items: center; justify-content: center; cursor: zoom-out;
|
||||
}
|
||||
.sp-lightbox img {
|
||||
max-width: 95%; max-height: 95%; object-fit: contain; border-radius: 8px;
|
||||
box-shadow: 0 4px 32px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
/* === Agent 流式显示(参考 AgentStreamDisplay.vue)=== */
|
||||
.agent-stream { display: flex; flex-direction: column; gap: 0; width: 92%; align-self: flex-start; margin-bottom: 6px; }
|
||||
|
||||
/* 折叠的中间步骤 — 树根 */
|
||||
.agent-tree-root {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 6px 14px; font-size: 12px; font-weight: 500; cursor: pointer;
|
||||
border-radius: 8px; background: var(--white); border: 0.5px solid #e0e0e0;
|
||||
box-shadow: 0 2px 4px rgba(7,192,95,0.08); color: var(--text);
|
||||
transition: all 0.25s cubic-bezier(0.4,0,0.2,1); margin-bottom: 10px; user-select: none;
|
||||
}
|
||||
.agent-tree-root:hover { background: rgba(7,192,95,0.04); }
|
||||
.agent-tree-root-title { display: flex; align-items: center; gap: 6px; }
|
||||
.agent-tree-root-title svg { width: 16px; height: 16px; color: var(--primary); flex-shrink: 0; }
|
||||
.agent-tree-root-toggle { font-size: 12px; color: var(--primary); transition: transform 0.2s; }
|
||||
.agent-tree-root.expanded .agent-tree-root-toggle { transform: rotate(180deg); }
|
||||
|
||||
/* 树子节点 */
|
||||
.agent-tree-children { display: none; padding-left: 12px; margin-top: -4px; margin-bottom: 10px; position: relative; }
|
||||
.agent-tree-root.expanded + .agent-tree-children { display: block; }
|
||||
.agent-tree-child { position: relative; padding-left: 20px; margin-bottom: 6px; }
|
||||
.agent-tree-child::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; border-left: 1px dashed #e0e0e0; }
|
||||
.agent-tree-child:last-child::before { bottom: auto; height: 15px; }
|
||||
.agent-tree-branch { position: absolute; left: 0; top: 15px; width: 16px; border-top: 1px dashed #e0e0e0; }
|
||||
|
||||
/* 通用动作卡片(思考/工具调用) */
|
||||
.agent-card {
|
||||
background: var(--white); border-radius: 5px; border: 1px solid #e8e8e8;
|
||||
overflow: hidden; transition: all 0.2s; box-shadow: 0 1px 2px rgba(0,0,0,0.02);
|
||||
animation: agentFadeIn 0.25s ease-out; margin-bottom: 8px; position: relative; width: 100%;
|
||||
}
|
||||
.agent-card:hover { border-color: var(--primary); box-shadow: 0 1px 4px rgba(7,192,95,0.08); }
|
||||
.agent-card.pending { border-color: rgba(7,192,95,0.15); overflow: hidden; }
|
||||
.agent-card.pending::after {
|
||||
content: ''; position: absolute; inset: 0;
|
||||
background: linear-gradient(120deg, transparent 0%, rgba(7,192,95,0.06) 40%, rgba(7,192,95,0.08) 55%, transparent 85%);
|
||||
transform: translateX(-100%); animation: agentShimmer 2.8s ease-in-out infinite; pointer-events: none;
|
||||
}
|
||||
.agent-card.error { border-left: 2px solid #e44; }
|
||||
.agent-card-header {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 5px 10px; cursor: pointer; user-select: none; transition: background-color 0.15s;
|
||||
}
|
||||
.agent-card-header:hover { background: rgba(7,192,95,0.03); }
|
||||
.agent-card-header.no-expand { cursor: default; }
|
||||
.agent-card-header.no-expand:hover { background: transparent; }
|
||||
.agent-card-title { display: flex; align-items: center; gap: 6px; flex: 1; min-width: 0; }
|
||||
.agent-card-title svg { width: 14px; height: 14px; color: var(--primary); flex-shrink: 0; }
|
||||
.agent-card-name { font-size: 12px; font-weight: 500; white-space: nowrap; color: var(--text); overflow: hidden; text-overflow: ellipsis; }
|
||||
.agent-card-summary { color: var(--text3); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; margin-left: 4px; }
|
||||
.agent-card-toggle { font-size: 12px; color: var(--text3); transition: transform 0.2s; padding: 0 2px; flex-shrink: 0; }
|
||||
.agent-card.expanded .agent-card-toggle { transform: rotate(180deg); }
|
||||
.agent-card-detail {
|
||||
display: none; padding: 6px 12px; border-top: 1px solid #f0f0f0;
|
||||
font-size: 12px; color: var(--text2); line-height: 1.6; max-height: 200px;
|
||||
overflow-y: auto; background: #fafbfc; white-space: pre-wrap; word-break: break-word;
|
||||
}
|
||||
.agent-card.expanded .agent-card-detail { display: block; }
|
||||
.agent-card-info {
|
||||
padding: 5px 10px; border-top: 1px solid #f0f0f0;
|
||||
font-size: 11px; font-weight: 500; color: var(--text); line-height: 1.5;
|
||||
}
|
||||
.agent-card-info strong { color: var(--primary); font-weight: 600; }
|
||||
|
||||
/* 计划任务变更 */
|
||||
.agent-plan-change {
|
||||
padding: 8px 12px; background: linear-gradient(135deg, rgba(7,192,95,0.05), rgba(7,192,95,0.02));
|
||||
border-radius: 6px; border: 1px solid rgba(7,192,95,0.2);
|
||||
font-size: 12px; color: var(--text); margin-bottom: 8px; animation: agentFadeIn 0.25s ease-out;
|
||||
}
|
||||
.agent-plan-change strong { color: var(--primary); font-weight: 600; margin-right: 3px; }
|
||||
|
||||
/* 回答区域 */
|
||||
.agent-answer { animation: agentFadeIn 0.25s ease-out; }
|
||||
.agent-answer-content {
|
||||
font-size: 14px; color: var(--text); line-height: 1.6; background: var(--white);
|
||||
border-radius: 16px; border-bottom-left-radius: 6px; padding: 12px 16px;
|
||||
box-shadow: 0 0 6px rgba(0,0,0,0.04), 0 4px 16px rgba(0,0,0,0.08);
|
||||
}
|
||||
.agent-answer-toolbar {
|
||||
display: flex; gap: 6px; margin-top: 6px; padding-left: 2px;
|
||||
}
|
||||
.agent-answer-toolbar button {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
width: 28px; height: 28px; border: 1px solid #e8e8e8; border-radius: 50%;
|
||||
background: var(--white); cursor: pointer; color: var(--text3); transition: all 0.2s;
|
||||
}
|
||||
.agent-answer-toolbar button:hover { border-color: var(--primary); color: var(--primary); background: rgba(7,192,95,0.04); }
|
||||
.agent-answer-toolbar button svg { width: 14px; height: 14px; }
|
||||
|
||||
/* 知识引用面板 */
|
||||
.agent-refs {
|
||||
background: var(--white); border: 1px solid #e8e8e8; border-radius: 10px;
|
||||
margin-top: 8px; overflow: hidden; font-size: 12px;
|
||||
}
|
||||
.agent-refs-header {
|
||||
display: flex; align-items: center; gap: 6px; padding: 8px 12px;
|
||||
cursor: pointer; color: var(--text2); font-weight: 500; user-select: none;
|
||||
}
|
||||
.agent-refs-header:hover { background: rgba(7,193,96,0.04); }
|
||||
.agent-refs-header svg:first-child { color: var(--primary); flex-shrink: 0; }
|
||||
.agent-refs-arrow { margin-left: auto; transition: transform 0.2s; color: var(--text3); }
|
||||
.agent-refs.expanded .agent-refs-arrow { transform: rotate(180deg); }
|
||||
.agent-refs-list {
|
||||
display: none; border-top: 1px solid #f0f0f0; padding: 6px 12px 8px;
|
||||
max-height: 200px; overflow-y: auto;
|
||||
}
|
||||
.agent-refs.expanded .agent-refs-list { display: block; }
|
||||
.agent-ref-group { margin-bottom: 6px; }
|
||||
.agent-ref-group-title { display: flex; align-items: center; gap: 4px; font-weight: 500; color: var(--text); padding: 2px 0; font-size: 12px; }
|
||||
.agent-ref-group-title svg { color: var(--primary); flex-shrink: 0; }
|
||||
.agent-ref-count { color: var(--text3); font-weight: 400; font-size: 11px; }
|
||||
.agent-ref-chunk { color: var(--text3); padding: 2px 0 2px 18px; line-height: 1.5; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: default; font-size: 11px; }
|
||||
.agent-ref-chunk:hover { color: var(--primary); background: rgba(7,193,96,0.04); border-radius: 4px; }
|
||||
.agent-ref-idx { color: var(--primary); font-weight: 500; font-size: 10px; }
|
||||
|
||||
/* 动画 */
|
||||
@keyframes agentFadeIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
|
||||
@keyframes agentShimmer { 0% { transform: translateX(-90%); } 50% { transform: translateX(-5%); } 100% { transform: translateX(90%); } }
|
||||
@keyframes agentSpin { to { transform: rotate(360deg); } }
|
||||
.agent-spinner { width: 12px; height: 12px; border: 2px solid #e0e0e0; border-top-color: var(--primary); border-radius: 50%; animation: agentSpin 0.8s linear infinite; display: inline-block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -301,7 +526,10 @@ body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
<div class="header-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||||
</div>
|
||||
<h1>快速笔记 5.0</h1>
|
||||
<div class="header-title-wrap">
|
||||
<h1>快速笔记 5.0</h1>
|
||||
<div class="header-session-title" id="sp-session-title"></div>
|
||||
</div>
|
||||
<button class="header-btn" id="btn-clear" title="清空对话">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
||||
</button>
|
||||
@@ -328,18 +556,31 @@ body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
</div>
|
||||
|
||||
<div class="input-area">
|
||||
<div class="sp-chat-bar">
|
||||
<button class="sp-chat-bar-kb" id="sp-btn-kb">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>
|
||||
<span id="sp-kb-name">全部</span>
|
||||
<svg class="sp-chat-bar-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</button>
|
||||
<div class="sp-chat-bar-divider"></div>
|
||||
<input class="sp-chat-bar-input" id="sp-input" type="text" placeholder="向知识库提问…" autocomplete="off">
|
||||
<button class="sp-chat-bar-send" id="sp-send">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8-8-8z"/></svg>
|
||||
</button>
|
||||
<div class="sp-image-previews" id="sp-image-previews" style="display:none;"></div>
|
||||
<div class="sp-chat-bar-wrapper">
|
||||
<div class="sp-chat-bar-toolbar">
|
||||
<button class="sp-chat-bar-kb" id="sp-btn-kb">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>
|
||||
<span id="sp-kb-name">全部</span>
|
||||
<svg class="sp-chat-bar-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</button>
|
||||
<button class="sp-chat-bar-mode" id="sp-btn-mode">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
||||
<span id="sp-mode-name">快速回答</span>
|
||||
<svg class="sp-chat-bar-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="sp-chat-bar">
|
||||
<button class="sp-chat-bar-img" id="sp-image-btn" style="display:none;" title="上传图片">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
|
||||
</button>
|
||||
<textarea class="sp-chat-bar-input" id="sp-input" placeholder="向知识库提问…" autocomplete="off" rows="1"></textarea>
|
||||
<button class="sp-chat-bar-send" id="sp-send">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8-8-8z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<input type="file" id="sp-image-input" accept="image/jpeg,image/png,image/gif,image/webp" multiple style="display:none;">
|
||||
<!-- 合并下拉菜单 -->
|
||||
<div class="sp-kb-dropdown-menu" id="sp-kb-menu">
|
||||
<div class="sp-dropdown-item selected" data-kb="all">
|
||||
@@ -367,8 +608,9 @@ body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
|
||||
技术规范
|
||||
</div>
|
||||
<div class="sp-dropdown-divider"></div>
|
||||
<div class="sp-dropdown-section">模式</div>
|
||||
</div>
|
||||
<!-- 模式下拉菜单(独立) -->
|
||||
<div class="sp-mode-dropdown-menu" id="sp-mode-menu">
|
||||
<div class="sp-mode-item selected" data-mode="fast">
|
||||
<span class="sp-radio"></span>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
||||
@@ -384,11 +626,6 @@ body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg>
|
||||
精确
|
||||
</div>
|
||||
<div class="sp-dropdown-divider"></div>
|
||||
<div class="sp-attach-item" id="sp-btn-attach">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
|
||||
上传图片
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -106,6 +106,123 @@ body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
.chat-tool-btn svg { width: 12px; height: 12px; }
|
||||
.chat-tool-btn.mode-active { color: var(--primary); font-weight: 500; }
|
||||
.chat-tool-divider { width: 1px; height: 12px; background: #eee; margin: 0 2px; }
|
||||
|
||||
/* 图片上传按钮 */
|
||||
.wk-image-upload-btn {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
border: none; background: none; color: #999; cursor: pointer;
|
||||
width: 26px; height: 26px; padding: 0; border-radius: 6px; transition: all 0.15s;
|
||||
}
|
||||
.wk-image-upload-btn:hover { color: var(--primary); background: rgba(7,193,96,0.06); }
|
||||
.wk-image-upload-btn svg { width: 14px; height: 14px; }
|
||||
|
||||
/* 图片预览条 */
|
||||
.wk-image-previews {
|
||||
display: flex; gap: 6px; padding: 6px 12px; overflow-x: auto; flex-shrink: 0;
|
||||
}
|
||||
.wk-img-item { position: relative; flex-shrink: 0; }
|
||||
.wk-img-thumb { width: 44px; height: 44px; border-radius: 6px; object-fit: cover; border: 1px solid #e8e8e8; }
|
||||
.wk-img-remove {
|
||||
position: absolute; top: -4px; right: -4px;
|
||||
width: 16px; height: 16px; border-radius: 50%; background: rgba(0,0,0,0.5); color: #fff;
|
||||
font-size: 12px; line-height: 16px; text-align: center; cursor: pointer; border: none; padding: 0;
|
||||
}
|
||||
.wk-img-remove:hover { background: #e44; }
|
||||
|
||||
/* === Markdown 渲染样式 === */
|
||||
.md-pre { background: #1e1e2e; color: #cdd6f4; border-radius: 8px; padding: 10px 12px; overflow-x: auto; font-size: 12px; margin: 6px 0; line-height: 1.5; }
|
||||
.md-pre code { font-family: "SF Mono", "Fira Code", "Consolas", monospace; white-space: pre; }
|
||||
.md-code { background: #f3f4f6; padding: 1px 5px; border-radius: 4px; font-size: 12px; font-family: "SF Mono", "Fira Code", "Consolas", monospace; }
|
||||
.md-h { margin: 8px 0 4px; line-height: 1.4; }
|
||||
h1.md-h { font-size: 18px; font-weight: 700; }
|
||||
h2.md-h { font-size: 16px; font-weight: 700; }
|
||||
h3.md-h { font-size: 14px; font-weight: 600; }
|
||||
h4.md-h, h5.md-h, h6.md-h { font-size: 13px; font-weight: 600; }
|
||||
.md-bq { border-left: 3px solid var(--primary); padding: 4px 12px; margin: 6px 0; background: #f8faf8; color: #666; border-radius: 0 4px 4px 0; font-size: 13px; }
|
||||
.md-list { margin: 4px 0; padding-left: 20px; font-size: 13px; line-height: 1.6; }
|
||||
.md-list li { margin: 2px 0; }
|
||||
.md-list .md-list { margin: 2px 0 2px 0; }
|
||||
.md-p { margin: 4px 0; line-height: 1.6; font-size: 13px; }
|
||||
.md-p a, .md-list a, .md-bq a { color: var(--primary); text-decoration: none; }
|
||||
.md-p a:hover, .md-list a:hover { text-decoration: underline; }
|
||||
.md-table { border-collapse: collapse; margin: 6px 0; font-size: 12px; width: 100%; }
|
||||
.md-table th { border: 1px solid #e5e5e5; padding: 4px 8px; background: #f7f8fa; font-weight: 600; text-align: left; }
|
||||
.md-table td { border: 1px solid #e5e5e5; padding: 4px 8px; }
|
||||
.md-table tr:hover td { background: rgba(7,193,96,0.02); }
|
||||
.md-img { max-width: 100%; max-height: 200px; border-radius: 6px; margin: 6px 0; display: block; object-fit: contain; cursor: pointer; }
|
||||
|
||||
/* === Agent 流式显示 === */
|
||||
.agent-stream { display: flex; flex-direction: column; gap: 0; max-width: 90%; margin-bottom: 6px; }
|
||||
.agent-tree-root {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 6px 12px; font-size: 12px; font-weight: 500; cursor: pointer;
|
||||
border-radius: 8px; background: #fff; border: 0.5px solid #e0e0e0;
|
||||
box-shadow: 0 2px 4px rgba(7,192,95,0.08); color: var(--text);
|
||||
transition: all 0.25s; margin-bottom: 8px; user-select: none;
|
||||
}
|
||||
.agent-tree-root:hover { background: rgba(7,192,95,0.04); }
|
||||
.agent-tree-root-title { display: flex; align-items: center; gap: 6px; }
|
||||
.agent-tree-root-title svg { width: 16px; height: 16px; color: var(--primary); flex-shrink: 0; }
|
||||
.agent-tree-root-toggle { font-size: 12px; color: var(--primary); transition: transform 0.2s; }
|
||||
.agent-tree-root.expanded .agent-tree-root-toggle { transform: rotate(180deg); }
|
||||
.agent-tree-children { display: none; padding-left: 12px; margin-top: -4px; margin-bottom: 8px; }
|
||||
.agent-tree-root.expanded + .agent-tree-children { display: block; }
|
||||
.agent-tree-child { position: relative; padding-left: 20px; margin-bottom: 6px; }
|
||||
.agent-tree-child::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; border-left: 1px dashed #e0e0e0; }
|
||||
.agent-tree-child:last-child::before { bottom: auto; height: 15px; }
|
||||
.agent-tree-branch { position: absolute; left: 0; top: 15px; width: 16px; border-top: 1px dashed #e0e0e0; }
|
||||
.agent-card {
|
||||
background: #fff; border-radius: 5px; border: 1px solid #e8e8e8;
|
||||
overflow: hidden; transition: all 0.2s; box-shadow: 0 1px 2px rgba(0,0,0,0.02);
|
||||
animation: agentFadeIn 0.25s ease-out; margin-bottom: 8px; position: relative;
|
||||
}
|
||||
.agent-card:hover { border-color: var(--primary); box-shadow: 0 1px 4px rgba(7,192,95,0.08); }
|
||||
.agent-card.pending { border-color: rgba(7,192,95,0.15); }
|
||||
.agent-card.pending::after {
|
||||
content: ''; position: absolute; inset: 0;
|
||||
background: linear-gradient(120deg, transparent 0%, rgba(7,192,95,0.06) 40%, rgba(7,192,95,0.08) 55%, transparent 85%);
|
||||
transform: translateX(-100%); animation: agentShimmer 2.8s ease-in-out infinite; pointer-events: none;
|
||||
}
|
||||
.agent-card.error { border-left: 2px solid #e44; }
|
||||
.agent-card-header { display: flex; justify-content: space-between; align-items: center; padding: 5px 10px; cursor: pointer; user-select: none; transition: background-color 0.15s; }
|
||||
.agent-card-header:hover { background: rgba(7,192,95,0.03); }
|
||||
.agent-card-header.no-expand { cursor: default; }
|
||||
.agent-card-header.no-expand:hover { background: transparent; }
|
||||
.agent-card-title { display: flex; align-items: center; gap: 6px; flex: 1; min-width: 0; }
|
||||
.agent-card-title svg { width: 14px; height: 14px; color: var(--primary); flex-shrink: 0; }
|
||||
.agent-card-name { font-size: 12px; font-weight: 500; white-space: nowrap; color: var(--text); overflow: hidden; text-overflow: ellipsis; }
|
||||
.agent-card-summary { color: #999; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; margin-left: 4px; }
|
||||
.agent-card-toggle { font-size: 12px; color: #999; transition: transform 0.2s; padding: 0 2px; flex-shrink: 0; }
|
||||
.agent-card.expanded .agent-card-toggle { transform: rotate(180deg); }
|
||||
.agent-card-detail { display: none; padding: 6px 12px; border-top: 1px solid #f0f0f0; font-size: 12px; color: #555; line-height: 1.6; max-height: 200px; overflow-y: auto; background: #fafbfc; white-space: pre-wrap; word-break: break-word; }
|
||||
.agent-card.expanded .agent-card-detail { display: block; }
|
||||
.agent-card-info { padding: 5px 10px; border-top: 1px solid #f0f0f0; font-size: 11px; font-weight: 500; color: var(--text); }
|
||||
.agent-card-info strong { color: var(--primary); font-weight: 600; }
|
||||
.agent-answer { animation: agentFadeIn 0.25s ease-out; }
|
||||
.agent-answer-content { font-size: 13px; color: #333; line-height: 1.6; background: #f7f8fa; padding: 10px 14px; border-radius: 12px; border-bottom-left-radius: 4px; }
|
||||
.agent-answer-toolbar { display: flex; gap: 6px; margin-top: 6px; }
|
||||
.agent-answer-toolbar button { display: flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: 1px solid #e8e8e8; border-radius: 50%; background: #fff; cursor: pointer; color: #999; transition: all 0.2s; }
|
||||
.agent-answer-toolbar button:hover { border-color: var(--primary); color: var(--primary); }
|
||||
.agent-answer-toolbar button svg { width: 13px; height: 13px; }
|
||||
.agent-refs { background: #fff; border: 1px solid #e8e8e8; border-radius: 10px; margin-top: 8px; overflow: hidden; font-size: 12px; }
|
||||
.agent-refs-header { display: flex; align-items: center; gap: 6px; padding: 8px 12px; cursor: pointer; color: #555; font-weight: 500; user-select: none; }
|
||||
.agent-refs-header:hover { background: rgba(7,193,96,0.04); }
|
||||
.agent-refs-header svg:first-child { color: var(--primary); flex-shrink: 0; }
|
||||
.agent-refs-arrow { margin-left: auto; transition: transform 0.2s; color: #999; }
|
||||
.agent-refs.expanded .agent-refs-arrow { transform: rotate(180deg); }
|
||||
.agent-refs-list { display: none; border-top: 1px solid #f0f0f0; padding: 6px 12px 8px; max-height: 200px; overflow-y: auto; }
|
||||
.agent-refs.expanded .agent-refs-list { display: block; }
|
||||
.agent-ref-group { margin-bottom: 6px; }
|
||||
.agent-ref-group-title { display: flex; align-items: center; gap: 4px; font-weight: 500; color: var(--text); padding: 2px 0; font-size: 12px; }
|
||||
.agent-ref-group-title svg { color: var(--primary); flex-shrink: 0; }
|
||||
.agent-ref-count { color: #999; font-weight: 400; font-size: 11px; }
|
||||
.agent-ref-chunk { color: #999; padding: 2px 0 2px 18px; line-height: 1.5; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: default; font-size: 11px; }
|
||||
.agent-ref-chunk:hover { color: var(--primary); background: rgba(7,193,96,0.04); border-radius: 4px; }
|
||||
.agent-ref-idx { color: var(--primary); font-weight: 500; font-size: 10px; }
|
||||
@keyframes agentFadeIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
|
||||
@keyframes agentShimmer { 0% { transform: translateX(-90%); } 50% { transform: translateX(-5%); } 100% { transform: translateX(90%); } }
|
||||
@keyframes agentSpin { to { transform: rotate(360deg); } }
|
||||
.agent-spinner { width: 12px; height: 12px; border: 2px solid #e0e0e0; border-top-color: var(--primary); border-radius: 50%; animation: agentSpin 0.8s linear infinite; display: inline-block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -146,10 +263,10 @@ body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-user">
|
||||
<div class="sidebar-avatar">S</div>
|
||||
<div class="sidebar-avatar" id="wk-user-letter">U</div>
|
||||
<div class="sidebar-user-info">
|
||||
<div class="sidebar-user-name">saras</div>
|
||||
<div class="sidebar-user-email">saraszhang@tencent....</div>
|
||||
<div class="sidebar-user-name" id="wk-user-name">加载中...</div>
|
||||
<div class="sidebar-user-email" id="wk-user-email"></div>
|
||||
</div>
|
||||
<svg class="sidebar-user-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</div>
|
||||
@@ -236,9 +353,13 @@ body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
<p>选择知识库后,输入问题开始智能问答</p>
|
||||
</div>
|
||||
<div class="chat-panel-input">
|
||||
<div class="wk-image-previews" id="wk-image-previews" style="display:none;"></div>
|
||||
<div class="chat-input-card">
|
||||
<textarea placeholder="输入问题..." rows="1"></textarea>
|
||||
<div class="chat-input-toolbar">
|
||||
<button class="wk-image-upload-btn" id="wk-image-btn" style="display:none;" title="上传图片">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
|
||||
</button>
|
||||
<button class="chat-tool-btn">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||||
普通
|
||||
@@ -258,6 +379,7 @@ body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="file" id="wk-image-input" accept="image/jpeg,image/png,image/gif,image/webp" multiple style="display:none;">
|
||||
</div>
|
||||
|
||||
<script src="weknora.js"></script>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user