Fixed the issue of External CDN dependencies in embedded HTML, replaced Google Fonts with the system's native font stack, and replaced FontAwesome loaded via CDN with built-in files.
This commit is contained in:
parent
3d1e0dc71c
commit
a50bb97ab1
7 changed files with 105 additions and 55 deletions
|
|
@ -27,6 +27,21 @@ var chatHTML []byte
|
|||
//go:embed logo.jpg
|
||||
var logoImage []byte
|
||||
|
||||
//go:embed static/css/fontawesome.min.css
|
||||
var fontawesomeCSS []byte
|
||||
|
||||
//go:embed static/webfonts/fa-brands-400.woff2
|
||||
var faBrandsFont []byte
|
||||
|
||||
//go:embed static/webfonts/fa-regular-400.woff2
|
||||
var faRegularFont []byte
|
||||
|
||||
//go:embed static/webfonts/fa-solid-900.woff2
|
||||
var faSolidFont []byte
|
||||
|
||||
//go:embed static/webfonts/fa-v4compatibility.woff2
|
||||
var faV4CompatFont []byte
|
||||
|
||||
// clientConn wraps a WebSocket connection with a write mutex for safe concurrent writes
|
||||
type clientConn struct {
|
||||
conn *websocket.Conn
|
||||
|
|
@ -290,6 +305,11 @@ func (c *Channel) Start(ctx context.Context) error {
|
|||
mux.HandleFunc("/ws", c.handleWebSocket)
|
||||
mux.HandleFunc("/", c.handleIndex)
|
||||
mux.HandleFunc("/assets/logo.jpg", c.handleLogo)
|
||||
mux.HandleFunc("/static/css/fontawesome.min.css", c.handleFontawesomeCSS)
|
||||
mux.HandleFunc("/static/webfonts/fa-brands-400.woff2", c.handleFaBrandsFont)
|
||||
mux.HandleFunc("/static/webfonts/fa-regular-400.woff2", c.handleFaRegularFont)
|
||||
mux.HandleFunc("/static/webfonts/fa-solid-900.woff2", c.handleFaSolidFont)
|
||||
mux.HandleFunc("/static/webfonts/fa-v4compatibility.woff2", c.handleFaV4CompatFont)
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", c.config.Host, c.config.Port)
|
||||
c.server = &http.Server{
|
||||
|
|
@ -626,3 +646,38 @@ func (c *Channel) handleIndex(w http.ResponseWriter, r *http.Request) {
|
|||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Write(chatHTML)
|
||||
}
|
||||
|
||||
// handleFontawesomeCSS serves the FontAwesome CSS file
|
||||
func (c *Channel) handleFontawesomeCSS(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/css")
|
||||
w.Header().Set("Cache-Control", "public, max-age=86400") // Cache for 1 day
|
||||
w.Write(fontawesomeCSS)
|
||||
}
|
||||
|
||||
// handleFaBrandsFont serves the FontAwesome brands font file
|
||||
func (c *Channel) handleFaBrandsFont(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "font/woff2")
|
||||
w.Header().Set("Cache-Control", "public, max-age=86400") // Cache for 1 day
|
||||
w.Write(faBrandsFont)
|
||||
}
|
||||
|
||||
// handleFaRegularFont serves the FontAwesome regular font file
|
||||
func (c *Channel) handleFaRegularFont(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "font/woff2")
|
||||
w.Header().Set("Cache-Control", "public, max-age=86400") // Cache for 1 day
|
||||
w.Write(faRegularFont)
|
||||
}
|
||||
|
||||
// handleFaSolidFont serves the FontAwesome solid font file
|
||||
func (c *Channel) handleFaSolidFont(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "font/woff2")
|
||||
w.Header().Set("Cache-Control", "public, max-age=86400") // Cache for 1 day
|
||||
w.Write(faSolidFont)
|
||||
}
|
||||
|
||||
// handleFaV4CompatFont serves the FontAwesome v4 compatibility font file
|
||||
func (c *Channel) handleFaV4CompatFont(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "font/woff2")
|
||||
w.Header().Set("Cache-Control", "public, max-age=86400") // Cache for 1 day
|
||||
w.Write(faV4CompatFont)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,7 @@
|
|||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<title>PicoClaw · Intelligent Conversation</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
<link rel="stylesheet" href="/static/css/fontawesome.min.css">
|
||||
<style>
|
||||
:root {
|
||||
--bg-primary: #0f0f0f;
|
||||
|
|
@ -51,7 +48,7 @@
|
|||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
height: 100vh;
|
||||
|
|
@ -517,7 +514,7 @@
|
|||
border: none;
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-family: inherit;
|
||||
outline: none;
|
||||
resize: none;
|
||||
line-height: 1.5;
|
||||
|
|
@ -541,7 +538,7 @@
|
|||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
white-space: nowrap;
|
||||
|
|
@ -879,46 +876,41 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked@11.1.1/marked.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.8/dist/purify.min.js"></script>
|
||||
<script>
|
||||
// 配置 marked
|
||||
marked.setOptions({
|
||||
breaks: true,
|
||||
gfm: true,
|
||||
headerIds: true,
|
||||
mangle: false
|
||||
});
|
||||
|
||||
// 配置 DOMPurify 以提供更强的 XSS 防护
|
||||
const DOMPURIFY_CONFIG = {
|
||||
ALLOWED_TAGS: [
|
||||
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
||||
'p', 'br', 'hr',
|
||||
'strong', 'em', 'b', 'i', 'u', 'del', 's', 'code', 'pre',
|
||||
'ul', 'ol', 'li',
|
||||
'blockquote',
|
||||
'a', 'img',
|
||||
'table', 'thead', 'tbody', 'tr', 'th', 'td',
|
||||
'span', 'div'
|
||||
],
|
||||
ALLOWED_ATTR: [
|
||||
'href', 'title', 'alt', 'src',
|
||||
'class', 'id'
|
||||
],
|
||||
ALLOW_DATA_ATTR: false,
|
||||
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|data):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,
|
||||
FORBID_TAGS: ['script', 'style', 'iframe', 'object', 'embed', 'form', 'input', 'button'],
|
||||
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onfocus', 'onblur'],
|
||||
KEEP_CONTENT: true,
|
||||
RETURN_DOM: false,
|
||||
RETURN_DOM_FRAGMENT: false,
|
||||
RETURN_TRUSTED_TYPE: false
|
||||
};
|
||||
|
||||
// 创建安全的 sanitize 函数
|
||||
function sanitizeHtml(html) {
|
||||
return DOMPurify.sanitize(html, DOMPURIFY_CONFIG);
|
||||
// Simple markdown-like formatting without external dependencies
|
||||
function parseMarkdown(text) {
|
||||
// Escape HTML to prevent XSS
|
||||
let html = text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
|
||||
// Code blocks (```)
|
||||
html = html.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>');
|
||||
|
||||
// Bold (**text**)
|
||||
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
||||
|
||||
// Italic (*text*)
|
||||
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
||||
|
||||
// Inline code (`code`)
|
||||
html = html.replace(/`(.+?)`/g, '<code>$1</code>');
|
||||
|
||||
// Links [text](url)
|
||||
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
|
||||
|
||||
// Headers
|
||||
html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
|
||||
html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
|
||||
html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');
|
||||
|
||||
// Line breaks
|
||||
html = html.replace(/\n/g, '<br>');
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
let ws;
|
||||
|
|
@ -1344,9 +1336,7 @@
|
|||
// 根据发送者类型决定是否渲染 Markdown
|
||||
if (sender === 'assistant' || sender === 'system') {
|
||||
// AI 回复和系统消息渲染 Markdown
|
||||
const rawHtml = marked.parse(content);
|
||||
const cleanHtml = sanitizeHtml(rawHtml);
|
||||
contentDiv.innerHTML = cleanHtml;
|
||||
contentDiv.innerHTML = parseMarkdown(content);
|
||||
} else {
|
||||
// 用户消息保持纯文本
|
||||
contentDiv.textContent = content;
|
||||
|
|
@ -1367,9 +1357,7 @@
|
|||
const contentDiv = reconnectMessageElement.querySelector('.message-content');
|
||||
if (contentDiv) {
|
||||
// Render Markdown for system messages
|
||||
const rawHtml = marked.parse(newContent);
|
||||
const cleanHtml = sanitizeHtml(rawHtml);
|
||||
contentDiv.innerHTML = cleanHtml;
|
||||
contentDiv.innerHTML = parseMarkdown(newContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1390,11 +1378,9 @@
|
|||
contentDiv.style.cursor = 'pointer';
|
||||
contentDiv.style.userSelect = 'none';
|
||||
|
||||
const rawHtml = marked.parse(
|
||||
contentDiv.innerHTML = parseMarkdown(
|
||||
t.clickToRetry + '\n\n**[' + t.retryButton + ']**'
|
||||
);
|
||||
const cleanHtml = sanitizeHtml(rawHtml);
|
||||
contentDiv.innerHTML = cleanHtml;
|
||||
|
||||
messageDiv.appendChild(contentDiv);
|
||||
messagesDiv.insertBefore(messageDiv, typingIndicator);
|
||||
|
|
|
|||
9
pkg/channels/websocket/static/css/fontawesome.min.css
vendored
Normal file
9
pkg/channels/websocket/static/css/fontawesome.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
pkg/channels/websocket/static/webfonts/fa-brands-400.woff2
Normal file
BIN
pkg/channels/websocket/static/webfonts/fa-brands-400.woff2
Normal file
Binary file not shown.
BIN
pkg/channels/websocket/static/webfonts/fa-regular-400.woff2
Normal file
BIN
pkg/channels/websocket/static/webfonts/fa-regular-400.woff2
Normal file
Binary file not shown.
BIN
pkg/channels/websocket/static/webfonts/fa-solid-900.woff2
Normal file
BIN
pkg/channels/websocket/static/webfonts/fa-solid-900.woff2
Normal file
Binary file not shown.
BIN
pkg/channels/websocket/static/webfonts/fa-v4compatibility.woff2
Normal file
BIN
pkg/channels/websocket/static/webfonts/fa-v4compatibility.woff2
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue