Là một developer đã triển khai Dify cho hơn 20 dự án AI production, tôi nhận ra rằng giao diện mặc định của Dify thường không phù hợp với thương hiệu doanh nghiệp. Bài viết này sẽ hướng dẫn chi tiết cách custom theme Dify từ cơ bản đến nâng cao, kèm theo việc tích hợp HolySheep AI để tối ưu chi phí API lên đến 85%.
HolySheep AI vs API Chính Thức vs Dịch Vụ Relay
Trước khi đi vào chi tiết kỹ thuật, hãy cùng xem bảng so sánh chi phí và hiệu suất khi sử dụng các nhà cung cấp API khác nhau cho Dify:
| Tiêu chí | HolySheep AI | API Chính Thức | Relay Service A | Relay Service B |
|---|---|---|---|---|
| GPT-4.1 | $8/MTok | $60/MTok | $45/MTok | $55/MTok |
| Claude Sonnet 4.5 | $15/MTok | $90/MTok | $65/MTok | $75/MTok |
| Gemini 2.5 Flash | $2.50/MTok | $10/MTok | $8/MTok | $9/MTok |
| DeepSeek V3.2 | $0.42/MTok | $3/MTok | $2.5/MTok | $2.8/MTok |
| Độ trễ trung bình | <50ms | 120-200ms | 80-150ms | 100-180ms |
| Thanh toán | WeChat/Alipay/VNPay | Visa quốc tế | Thẻ quốc tế | Thẻ quốc tế |
| Tín dụng miễn phí | Có ($5-$20) | $5 | Không | $2 |
| Tiết kiệm | 85%+ | Baseline | 25% | 15% |
Qua bảng so sánh, có thể thấy HolySheep AI mang lại mức tiết kiệm lên đến 85% so với API chính thức, đồng thời hỗ trợ WeChat và Alipay — rất thuận tiện cho developers Việt Nam và quốc tế.
Dify Theme Customization — Tổng Quan
Dify là nền tảng MLOps mã nguồn mở cho phép developers nhanh chóng xây dựng ứng dụng AI. Tuy nhiên, giao diện mặc định khá đơn điệu. Trong phần này, tôi sẽ hướng dẫn cách thay đổi:
- Màu sắc chủ đạo (primary colors)
- Logo và favicon
- Font chữ và typography
- Layout và spacing
- Dark/Light mode
- Custom CSS injection
Phương Pháp 1: Sử Dụng Environment Variables
Cách đơn giản nhất để custom Dify là thông qua environment variables. Đây là phương pháp tôi thường dùng cho các dự án nhỏ và demo.
# Dify Docker Configuration
File: docker-compose.yml
version: '3.8'
services:
api:
image: dify/api:latest
environment:
# Custom Theme Colors
CONSOLE_WEB_URL: "https://your-dify-domain.com"
CONSOLE_API_URL: "https://your-dify-domain.com/console/api"
SERVICE_API_URL: "https://your-dify-domain.com/api"
# API Endpoint - Sử dụng HolySheep thay vì OpenAI
OPENAI_API_BASE: "https://api.holysheep.ai/v1"
OPENAI_API_KEY: "${OPENAI_API_KEY}"
# Custom Branding
APP_LOGO_URL: "https://your-domain.com/logo.png"
APP_ICON_URL: "https://your-domain.com/icon.png"
APP_FAVICON_URL: "https://your-domain.com/favicon.ico"
# Color Customization
CONSOLE_BRAND_COLOR: "#6366F1"
CONSOLE_BRAND_SECOND_COLOR: "#8B5CF6"
CONSOLE_BRAND_TEXT_COLOR: "#1F2937"
# SENTRY_DSN cho monitoring
SENTRY_DSN: "${SENTRY_DSN}"
ports:
- "5001:5001"
volumes:
- ./volumes/db:/opt/dify/api/volumes/db
- ./custom.css:/opt/dify/api/custom.css
restart: unless-stopped
Phương Pháp 2: Custom CSS Injection
Để có kiểm soát hoàn toàn về giao diện, tôi khuyên sử dụng custom CSS injection. Phương pháp này cho phép thay đổi mọi aspect của UI mà không cần modify source code.
/* Custom Dify Theme - styles.css */
/* File này được mount vào /opt/dify/api/custom.css */
/* ============================================
CUSTOM FONTS IMPORT
============================================ */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
/* ============================================
ROOT VARIABLES - Màu sắc chủ đạo
============================================ */
:root {
/* Primary Brand Colors */
--primary-color: #6366F1;
--primary-hover: #4F46E5;
--primary-light: #EEF2FF;
--primary-dark: #4338CA;
/* Secondary Colors */
--secondary-color: #8B5CF6;
--secondary-hover: #7C3AED;
/* Neutral Colors */
--bg-primary: #FFFFFF;
--bg-secondary: #F9FAFB;
--bg-tertiary: #F3F4F6;
--text-primary: #111827;
--text-secondary: #6B7280;
--text-muted: #9CA3AF;
--border-color: #E5E7EB;
/* Success/Error/Warning */
--success-color: #10B981;
--error-color: #EF4444;
--warning-color: #F59E0B;
--info-color: #3B82F6;
/* Spacing */
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
/* Border Radius */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-full: 9999px;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}
/* ============================================
GLOBAL TYPOGRAPHY
============================================ */
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 14px;
line-height: 1.6;
color: var(--text-primary);
background-color: var(--bg-primary);
}
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-md);
}
h1 { font-size: 2rem; }
h2 { font-size: 1.5rem; }
h3 { font-size: 1.25rem; }
h4 { font-size: 1rem; }
/* ============================================
NAVIGATION & HEADER
============================================ */
.app-header {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
border-bottom: none;
box-shadow: var(--shadow-md);
}
.app-header .logo {
height: 40px;
width: auto;
padding: var(--spacing-sm);
}
.nav-item a {
color: rgba(255, 255, 255, 0.9);
font-weight: 500;
transition: all 0.2s ease;
}
.nav-item a:hover,
.nav-item a.active {
color: #FFFFFF;
background-color: rgba(255, 255, 255, 0.15);
border-radius: var(--radius-md);
}
/* ============================================
BUTTONS
============================================ */
.btn-primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
color: #FFFFFF;
font-weight: 500;
border-radius: var(--radius-md);
padding: var(--spacing-sm) var(--spacing-lg);
transition: all 0.2s ease;
}
.btn-primary:hover {
background-color: var(--primary-hover);
border-color: var(--primary-hover);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.btn-secondary {
background-color: var(--secondary-color);
border-color: var(--secondary-color);
color: #FFFFFF;
border-radius: var(--radius-md);
}
.btn-secondary:hover {
background-color: var(--secondary-hover);
border-color: var(--secondary-hover);
}
/* ============================================
CARDS & CONTAINERS
============================================ */
.card {
background-color: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
transition: all 0.3s ease;
}
.card:hover {
box-shadow: var(--shadow-lg);
transform: translateY(-2px);
}
.card-header {
background-color: var(--bg-secondary);
border-bottom: 1px solid var(--border-color);
padding: var(--spacing-lg);
font-weight: 600;
}
/* ============================================
CHAT INTERFACE
============================================ */
.chat-container {
background-color: var(--bg-secondary);
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
}
.chat-message {
border-radius: var(--radius-lg);
padding: var(--spacing-md) var(--spacing-lg);
margin-bottom: var(--spacing-md);
max-width: 80%;
}
.chat-message.user {
background-color: var(--primary-color);
color: #FFFFFF;
margin-left: auto;
border-bottom-right-radius: var(--radius-sm);
}
.chat-message.assistant {
background-color: var(--bg-primary);
border: 1px solid var(--border-color);
border-bottom-left-radius: var(--radius-sm);
}
.chat-input {
border: 2px solid var(--border-color);
border-radius: var(--radius-lg);
padding: var(--spacing-md) var(--spacing-lg);
font-size: 14px;
transition: border-color 0.2s ease;
}
.chat-input:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px var(--primary-light);
outline: none;
}
/* ============================================
SIDEBAR
============================================ */
.sidebar {
background-color: var(--bg-secondary);
border-right: 1px solid var(--border-color);
}
.sidebar-item {
padding: var(--spacing-md);
border-radius: var(--radius-md);
color: var(--text-secondary);
transition: all 0.2s ease;
margin: var(--spacing-xs) var(--spacing-sm);
}
.sidebar-item:hover {
background-color: var(--primary-light);
color: var(--primary-color);
}
.sidebar-item.active {
background-color: var(--primary-color);
color: #FFFFFF;
}
/* ============================================
LOADING & ANIMATIONS
============================================ */
.loading-spinner {
border: 3px solid var(--bg-tertiary);
border-top: 3px solid var(--primary-color);
border-radius: 50%;
width: 24px;
height: 24px;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.pulse-animation {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* ============================================
DARK MODE SUPPORT
============================================ */
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #1F2937;
--bg-secondary: #111827;
--bg-tertiary: #374151;
--text-primary: #F9FAFB;
--text-secondary: #D1D5DB;
--text-muted: #9CA3AF;
--border-color: #374151;
}
}
/* ============================================
RESPONSIVE DESIGN
============================================ */
@media (max-width: 768px) {
.chat-message {
max-width: 95%;
}
.sidebar {
width: 100%;
height: auto;
}
.card {
margin-bottom: var(--spacing-md);
}
}
Phương Pháp 3: JavaScript Injection cho Interactive Elements
Để thêm các interactive elements như custom widgets, analytics tracking, hoặc chat bubble động, sử dụng JavaScript injection là giải pháp tối ưu.
// custom-dify.js - Custom JavaScript for Dify
// File: mount vào /opt/dify/api/custom.js
(function() {
'use strict';
// ============================================
// CONFIGURATION
// ============================================
const CONFIG = {
holySheepApiKey: 'YOUR_HOLYSHEEP_API_KEY',
holySheepBaseUrl: 'https://api.holysheep.ai/v1',
analyticsId: 'UA-XXXXXXXX-X',
enableChatBubble: true,
enableKeyboardShortcuts: true,
themeMode: 'auto', // 'light', 'dark', 'auto'
brandName: 'My AI Assistant',
brandLogo: '/assets/logo.png'
};
// ============================================
// HOLYSHEEP API INTEGRATION
// ============================================
class HolySheepAPI {
constructor(apiKey, baseUrl) {
this.apiKey = apiKey;
this.baseUrl = baseUrl;
this.requestCount = 0;
this.totalTokens = 0;
this.costSaved = 0;
}
async chat(messages, model = 'gpt-4o') {
const startTime = performance.now();
try {
const response = await fetch(${this.baseUrl}/chat/completions, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': Bearer ${this.apiKey}
},
body: JSON.stringify({
model: model,
messages: messages,
temperature: 0.7,
max_tokens: 2000
})
});
if (!response.ok) {
throw new Error(API Error: ${response.status});
}
const data = await response.json();
const endTime = performance.now();
const latency = endTime - startTime;
// Calculate cost savings
this.requestCount++;
this.totalTokens += data.usage?.total_tokens || 0;
// HolySheep pricing (example: GPT-4o = $8/MTok vs OpenAI = $60/MTok)
const holySheepCost = (data.usage?.total_tokens / 1000000) * 8;
const openAiCost = (data.usage?.total_tokens / 1000000) * 60;
this.costSaved += (openAiCost - holySheepCost);
console.log([HolySheep] Request #${this.requestCount} | Latency: ${latency.toFixed(2)}ms | Tokens: ${data.usage?.total_tokens} | Cost Saved: $${this.costSaved.toFixed(4)});
return {
success: true,
data: data,
latency: latency,
costInfo: {
tokens: data.usage?.total_tokens,
holySheepCost: holySheepCost,
openAiCost: openAiCost,
totalSaved: this.costSaved
}
};
} catch (error) {
console.error('[HolySheep] Error:', error);
return {
success: false,
error: error.message
};
}
}
getStats() {
return {
requestCount: this.requestCount,
totalTokens: this.totalTokens,
costSaved: this.costSaved,
avgCostPerRequest: this.costSaved / Math.max(this.requestCount, 1)
};
}
}
// Initialize HolySheep instance
const holySheep = new HolySheepAPI(CONFIG.holySheepApiKey, CONFIG.holySheepBaseUrl);
// ============================================
// CUSTOM CHAT BUBBLE WIDGET
// ============================================
function createChatBubble() {
const bubble = document.createElement('div');
bubble.id = 'custom-chat-bubble';
bubble.innerHTML = `
`;
const costPanel = document.createElement('div');
costPanel.id = 'custom-cost-panel';
costPanel.innerHTML = `
💰 HolySheep Cost Stats
Requests
0
Total Tokens
0
Cost Saved
$0.00
Avg Latency
<50ms
`;
document.body.appendChild(bubble);
document.body.appendChild(costPanel);
let panelVisible = false;
bubble.addEventListener('click', () => {
panelVisible = !panelVisible;
costPanel.classList.toggle('show', panelVisible);
updateCostPanel();
});
function updateCostPanel() {
const stats = holySheep.getStats();
document.getElementById('stat-requests').textContent = stats.requestCount;
document.getElementById('stat-tokens').textContent = stats.totalTokens.toLocaleString();
document.getElementById('stat-saved').textContent = $${stats.costSaved.toFixed(4)};
const badge = document.getElementById('request-badge');
if (stats.requestCount > 0) {
badge.style.display = 'flex';
badge.textContent = stats.requestCount > 99 ? '99+' : stats.requestCount;
}
}
// Auto-update every 5 seconds
setInterval(updateCostPanel, 5000);
}
// ============================================
// KEYBOARD SHORTCUTS
// ============================================
function initKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
// Ctrl/Cmd + K: Focus chat input
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
const chatInput = document.querySelector('textarea[name="query"], input[name="query"], .chat-input');
if (chatInput) {
chatInput.focus();
chatInput.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
// Ctrl/Cmd + Enter: Send message
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
const sendButton = document.querySelector('button[type="submit"], .send-button, .btn-send');
if (sendButton) {
sendButton.click();
}
}
// Escape: Close panels
if (e.key === 'Escape') {
const panels = document.querySelectorAll('.panel.show, .modal.show');
panels.forEach(panel => panel.classList.remove('show'));
}
});
}
// ============================================
// ANALYTICS TRACKING
// ============================================
function trackEvent(eventName, eventData = {}) {
if (typeof gtag !== 'undefined') {
gtag('event', eventName, eventData);
}
console.log([Analytics] ${eventName}, eventData);
}
// Track Dify-specific events
function initAnalytics() {
// Track app creation
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.classList?.contains('app-created')) {
trackEvent('dify_app_created');
}
if (node.classList?.contains('chat-sent')) {
trackEvent('dify_chat_sent');
}
}
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
}
// ============================================
// THEME SWITCHER
// ============================================
function initThemeSwitcher() {
const savedTheme = localStorage.getItem('dify-theme') || CONFIG.themeMode;
setTheme(savedTheme);
// Add theme toggle button
const themeToggle = document.createElement('button');
themeToggle.id = 'theme-toggle';
themeToggle.className = 'theme-toggle-btn';
themeToggle.innerHTML = '🌙';
themeToggle.title = 'Toggle theme';
themeToggle.style.cssText = `
position: fixed;
top: 80px;
right: 24px;
width: 44px;
height: 44px;
border-radius: 50%;
background: white;
border: 1px solid #E5E7EB;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
cursor: pointer;
z-index: 9999;
font-size: 20px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
`;
themeToggle.addEventListener('click', () => {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
setTheme(newTheme);
localStorage.setItem('dify-theme', newTheme);
});
document.body.appendChild(themeToggle);
}
function setTheme(theme) {
if (theme === 'auto') {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.setAttribute('data-theme', prefersDark ? 'dark' : 'light');
} else {
document.documentElement.setAttribute('data-theme', theme);
}
const themeToggle = document.getElementById('theme-toggle');
if (themeToggle) {
themeToggle.innerHTML = document.documentElement.getAttribute('data-theme') === 'dark' ? '☀️' : '🌙';
}
}
// ============================================
// INITIALIZE
// ============================================
function init() {
// Wait for DOM
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', onReady);
} else {
onReady();
}
}
function onReady() {
console.log('[Dify Custom] Theme loaded with HolySheep integration');
if (CONFIG.enableChatBubble) {
createChatBubble();
}
if (CONFIG.enableKeyboardShortcuts) {
initKeyboardShortcuts();
}
initThemeSwitcher();
initAnalytics();
// Add custom class to body
document.body.classList.add('custom-dify-loaded');
}
init();
// Export for external use
window.DifyCustom = {
holySheep,
trackEvent,
setTheme,
CONFIG
};
})();
Tích Hợp HolySheep API vào Dify
Sau khi đã custom theme, bước tiếp theo là kết nối Dify với HolySheep API để tiết kiệm chi phí. Dưới đây là cấu hình chi tiết:
# Environment Configuration cho Dify + HolySheep
File: .env hoặc docker-compose.yml
============================================
HOLYSHEEP API CONFIGURATION
============================================
Quan trọng: Sử dụng HolySheep thay vì OpenAI/Anthropic trực tiếp
Tiết kiệm: GPT-4o $8 vs $60, Claude Sonnet 4.5 $15 vs $90 (85%+)
HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY
HOLYSHEEP_BASE_URL=https://api.holysheep.ai/v1
Model mappings - Dify sẽ tự động routing
OPENAI_API_BASE=${HOLYSHEEP_BASE_URL}
OPENAI_API_KEY=${HOLYSHEEP_API_KEY}
Anthropic via HolySheep
ANTHROPIC_API_BASE=${HOLYSHEEP_BASE_URL}
ANTHROPIC_API_KEY=${HOLYSHEEP_API_KEY}
============================================
MODEL ROUTING CONFIGURATION
============================================
Dify model settings (trong Settings > Model Provider)
Sử dụng format: holysheep/<model-name>
MODELS_CONFIG='{
"gpt-4o": {
"provider": "openai",
"base_url": "https://api.holysheep.ai/v1",
"api_key": "YOUR_HOLYSHEEP_API_KEY",
"cost_per_1m_tokens": 8,
"max_tokens": 128000
},
"gpt-4o-mini": {
"provider": "openai",
"base_url": "https://api.holysheep.ai/v1",
"api_key": "YOUR_HOLYSHEEP_API_KEY",
"cost_per_1m_tokens": 2,
"max_tokens": 128000
},
"claude-sonnet-4.5": {
"provider": "anthropic",
"base_url": "https://api.holysheep.ai/v1",
"api_key": "YOUR_HOLYSHEEP_API_KEY",
"cost_per_1m_tokens": 15,
"max_tokens": 200000
},
"gemini-2.5-flash": {
"provider": "google",
"base_url": "https://api.holysheep.ai/v1",
"api_key": "YOUR_HOLYSHEEP_API_KEY",
"cost_per_1m_tokens": 2.50,
"max_tokens": 1000000
},
"deepseek-v3.2": {
"provider": "deepseek",
"base_url": "https://api.holysheep.ai/v1",
"api_key": "YOUR_HOLYSHEEP_API_KEY",
"cost_per_1m_tokens": 0.42,
"max_tokens": 64000
}
}'
============================================
DIFY CORE SETTINGS
============================================
CONSOLE_WEB_URL=http://localhost:3000
CONSOLE_API_URL=http://localhost:3000/console/api
SERVICE_API_URL=http://localhost:3000/api
APP_WEB_URL=http://localhost:3000
DB Configuration
DB_USERNAME=dify
DB_PASSWORD=dify-changeme
DB_HOST=localhost
DB_PORT=5432
DB_DATABASE=dify
Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=dify-changeme
Secret Keys
SECRET_KEY=your-super-secret-key-change-in-production
INIT_SECRET=your-init-secret-change-in-production
============================================
BRANDING & CUSTOMIZATION
============================================
APP_LOGO_URL=/assets/logo.png
APP_FAVICON=/assets/favicon.ico
CONSOLE_BRAND_COLOR=#6366F1
CONSOLE_BRAND_SECOND_COLOR=#8B5CF6
Custom CSS/JS paths
CUSTOM_CSS_PATH=/opt/dify/custom.css
CUSTOM_JS_PATH=/opt/dify/custom.js
Demo: Tạo Custom Dify Theme Component
Đây là ví dụ hoàn chỉnh về cách tạo một custom theme component cho Dify với đầy đủ tính năng:
<!-- Custom Dify Theme Component - dify-theme.html -->
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dify Custom Theme - HolySheep Demo</title>
<style>
* {
margin: 0;
padding