Add files via upload
This commit is contained in:
@@ -196,6 +196,75 @@ async def get_preview_image(request):
|
||||
except Exception as e:
|
||||
return web.Response(status=404)
|
||||
|
||||
@PromptServer.instance.routes.get("/power_artist_loader/csv/read")
|
||||
async def read_csv(request):
|
||||
"""读取CSV文件内容API"""
|
||||
try:
|
||||
csv_path = os.path.join(os.path.dirname(__file__), "artists.csv")
|
||||
artists = []
|
||||
|
||||
if os.path.exists(csv_path):
|
||||
async with aiofiles.open(csv_path, 'r', encoding='utf-8') as f:
|
||||
content = await f.read()
|
||||
for line in content.split('\n'):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
parts = line.split(';')
|
||||
if len(parts) >= 2:
|
||||
artists.append({
|
||||
'name': parts[0].strip(),
|
||||
'keywords': parts[1].strip(),
|
||||
'image': parts[2].strip() if len(parts) > 2 else ''
|
||||
})
|
||||
|
||||
return web.json_response({
|
||||
'success': True,
|
||||
'artists': artists
|
||||
})
|
||||
except Exception as e:
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}, status=500)
|
||||
|
||||
@PromptServer.instance.routes.post("/power_artist_loader/csv/save")
|
||||
async def save_csv(request):
|
||||
"""保存CSV文件API"""
|
||||
try:
|
||||
data = await request.json()
|
||||
artists = data.get('artists', [])
|
||||
|
||||
csv_path = os.path.join(os.path.dirname(__file__), "artists.csv")
|
||||
|
||||
# 构建CSV内容
|
||||
lines = []
|
||||
for artist in artists:
|
||||
name = artist.get('name', '').strip()
|
||||
keywords = artist.get('keywords', '').strip()
|
||||
image = artist.get('image', '').strip()
|
||||
|
||||
if name: # 只保存有名字的行
|
||||
lines.append(f"{name};{keywords};{image}")
|
||||
|
||||
# 写入文件
|
||||
async with aiofiles.open(csv_path, 'w', encoding='utf-8') as f:
|
||||
await f.write('\n'.join(lines))
|
||||
|
||||
# 重新加载数据到内存
|
||||
power_artist_loader_instance.load_artists_data()
|
||||
|
||||
return web.json_response({
|
||||
'success': True,
|
||||
'message': f'Saved {len(lines)} artists'
|
||||
})
|
||||
except Exception as e:
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}, status=500)
|
||||
|
||||
# 节点类映射
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"PowerArtistLoader": PowerArtistLoader
|
||||
|
||||
Generated
+1878
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "comfyui-power-artist-loader-manager",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"watch": "vite build --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@comfyorg/comfyui-frontend-types": "^1.20.2",
|
||||
"@types/react": "^18.2.66",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"typescript": "^5.4.2",
|
||||
"vite": "^5.2.10"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
|
||||
interface Artist {
|
||||
name: string;
|
||||
keywords: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
interface ArtistManagerProps {
|
||||
api: any;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
const ArtistManager: React.FC<ArtistManagerProps> = ({ api, onClose }) => {
|
||||
const [artists, setArtists] = useState<Artist[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [message, setMessage] = useState('');
|
||||
|
||||
// 加载CSV数据
|
||||
const loadArtists = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await api.fetchApi('/power_artist_loader/csv/read');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
setArtists(data.artists);
|
||||
setMessage('');
|
||||
} else {
|
||||
setMessage('Failed to load artists: ' + data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
setMessage('Error loading artists: ' + error);
|
||||
console.error('Load error:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 保存CSV数据
|
||||
const saveArtists = async () => {
|
||||
try {
|
||||
setSaving(true);
|
||||
const response = await api.fetchApi('/power_artist_loader/csv/save', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ artists })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
setMessage('✓ Saved successfully!');
|
||||
setTimeout(() => setMessage(''), 3000);
|
||||
} else {
|
||||
setMessage('Failed to save: ' + data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
setMessage('Error saving: ' + error);
|
||||
console.error('Save error:', error);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 添加新画师
|
||||
const addArtist = () => {
|
||||
setArtists([...artists, { name: '', keywords: '', image: '' }]);
|
||||
};
|
||||
|
||||
// 删除画师
|
||||
const deleteArtist = (index: number) => {
|
||||
setArtists(artists.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
// 更新画师信息
|
||||
const updateArtist = (index: number, field: keyof Artist, value: string) => {
|
||||
const newArtists = [...artists];
|
||||
newArtists[index][field] = value;
|
||||
setArtists(newArtists);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadArtists();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <div className="artist-manager-loading">Loading artists...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="artist-manager">
|
||||
<div className="artist-manager-header">
|
||||
<h2>🎨 Artist Library Manager</h2>
|
||||
<div className="artist-manager-actions">
|
||||
<button onClick={addArtist} className="btn btn-add">
|
||||
+ Add Artist
|
||||
</button>
|
||||
<button onClick={saveArtists} disabled={saving} className="btn btn-save">
|
||||
{saving ? 'Saving...' : '💾 Save'}
|
||||
</button>
|
||||
<button onClick={loadArtists} className="btn btn-refresh">
|
||||
🔄 Refresh
|
||||
</button>
|
||||
{onClose && (
|
||||
<button onClick={onClose} className="btn btn-close">
|
||||
✕ Close
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{message && (
|
||||
<div className={`artist-manager-message ${message.includes('✓') ? 'success' : 'error'}`}>
|
||||
{message}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="artist-table-container">
|
||||
<table className="artist-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="col-name">Artist Name</th>
|
||||
<th className="col-keywords">Keywords</th>
|
||||
<th className="col-actions">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{artists.map((artist, index) => (
|
||||
<tr key={index}>
|
||||
<td className="col-name">
|
||||
<div className="artist-name-cell">
|
||||
<span className="artist-index">{index + 1}</span>
|
||||
<input
|
||||
type="text"
|
||||
value={artist.name}
|
||||
onChange={(e) => updateArtist(index, 'name', e.target.value)}
|
||||
placeholder="Artist name"
|
||||
className="input-field"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
value={artist.image}
|
||||
onChange={(e) => updateArtist(index, 'image', e.target.value)}
|
||||
placeholder="image.jpg"
|
||||
className="input-field input-image"
|
||||
/>
|
||||
</td>
|
||||
<td className="col-keywords">
|
||||
<textarea
|
||||
value={artist.keywords}
|
||||
onChange={(e) => updateArtist(index, 'keywords', e.target.value)}
|
||||
placeholder="Keywords"
|
||||
className="textarea-field"
|
||||
/>
|
||||
</td>
|
||||
<td className="col-actions">
|
||||
<button
|
||||
onClick={() => deleteArtist(index)}
|
||||
className="btn btn-delete"
|
||||
title="Delete"
|
||||
>
|
||||
🗑️
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="artist-manager-footer">
|
||||
<p className="artist-count">📊 Total: <strong>{artists.length}</strong> artists</p>
|
||||
<p className="help-text">
|
||||
Format: name;keywords;image.jpg | Save updates artists.csv
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ArtistManager;
|
||||
@@ -0,0 +1,82 @@
|
||||
import { app } from '/scripts/app.js';
|
||||
import { api } from '/scripts/api.js';
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import ArtistManager from './ArtistManager';
|
||||
import './styles.css';
|
||||
|
||||
// 等待 ComfyUI 应用初始化
|
||||
app.registerExtension({
|
||||
name: "PowerArtistLoader.Manager",
|
||||
|
||||
async setup() {
|
||||
try {
|
||||
// 添加右键菜单选项
|
||||
const getCanvasMenuOptions = app.canvas.getCanvasMenuOptions;
|
||||
app.canvas.getCanvasMenuOptions = function () {
|
||||
const options = getCanvasMenuOptions.apply(this, arguments);
|
||||
|
||||
options.push({
|
||||
content: "🎨 Artist Manager",
|
||||
callback: () => {
|
||||
openArtistManager();
|
||||
}
|
||||
});
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
console.log("Power Artist Manager menu registered");
|
||||
} catch (error) {
|
||||
console.error("Failed to register Artist Manager:", error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 打开管理器窗口
|
||||
function openArtistManager() {
|
||||
// 创建模态窗口
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'artist-manager-modal';
|
||||
modal.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
z-index: 10000;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.style.cssText = `
|
||||
width: 90%;
|
||||
height: 85%;
|
||||
max-width: 1200px;
|
||||
background: #1e1e1e;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
||||
`;
|
||||
|
||||
modal.appendChild(container);
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// 渲染React组件
|
||||
const root = createRoot(container);
|
||||
root.render(<ArtistManager api={api} onClose={() => {
|
||||
root.unmount();
|
||||
document.body.removeChild(modal);
|
||||
}} />);
|
||||
|
||||
// 点击背景关闭
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target === modal) {
|
||||
root.unmount();
|
||||
document.body.removeChild(modal);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
/* 主容器 */
|
||||
.artist-manager {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 16px;
|
||||
background: #1e1e1e;
|
||||
color: #e0e0e0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 头部区域 */
|
||||
.artist-manager-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 2px solid #333;
|
||||
}
|
||||
|
||||
.artist-manager-header h2 {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.artist-manager-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 按钮通用样式 */
|
||||
.btn {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.btn-add {
|
||||
background: #4caf50;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-add:hover:not(:disabled) {
|
||||
background: #45a049;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
background: #2196f3;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-save:hover:not(:disabled) {
|
||||
background: #1976d2;
|
||||
}
|
||||
|
||||
.btn-refresh {
|
||||
background: #ff9800;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-refresh:hover:not(:disabled) {
|
||||
background: #f57c00;
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
background: #757575;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-close:hover:not(:disabled) {
|
||||
background: #616161;
|
||||
}
|
||||
|
||||
.btn-delete {
|
||||
background: #f44336;
|
||||
color: #fff;
|
||||
padding: 6px 12px;
|
||||
font-size: 16px;
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
.btn-delete:hover:not(:disabled) {
|
||||
background: #d32f2f;
|
||||
}
|
||||
|
||||
/* 消息提示 */
|
||||
.artist-manager-message {
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
animation: slideDown 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.artist-manager-message.success {
|
||||
background: #4caf50;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.artist-manager-message.error {
|
||||
background: #f44336;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.artist-manager-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
font-size: 18px;
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
/* 表格容器 */
|
||||
.artist-table-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
border: 1px solid #333;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 12px;
|
||||
background: #1a1a1a;
|
||||
}
|
||||
|
||||
/* 表格 */
|
||||
.artist-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 14px;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.artist-table thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: #2d2d2d;
|
||||
z-index: 10;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.artist-table th {
|
||||
padding: 12px 4px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
color: #4caf50;
|
||||
border-bottom: 2px solid #4caf50;
|
||||
}
|
||||
|
||||
.artist-table td {
|
||||
padding: 8px 4px;
|
||||
border-bottom: 1px solid #333;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.artist-table tbody tr {
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
|
||||
.artist-table tbody tr:hover {
|
||||
background: #252525;
|
||||
}
|
||||
|
||||
/* 列宽定义 */
|
||||
.col-name {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.col-keywords {
|
||||
width: 65%;
|
||||
}
|
||||
|
||||
.col-actions {
|
||||
width: 10%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 艺术家名称单元格 */
|
||||
.artist-name-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.artist-index {
|
||||
display: inline-block;
|
||||
background: #333;
|
||||
color: #4caf50;
|
||||
padding: 2px 8px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
/* 输入框样式 */
|
||||
.input-field {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
background: #2d2d2d;
|
||||
border: 1px solid #444;
|
||||
border-radius: 3px;
|
||||
color: #e0e0e0;
|
||||
font-size: 13px;
|
||||
box-sizing: border-box;
|
||||
font-family: inherit;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.input-field:focus {
|
||||
outline: none;
|
||||
border-color: #4caf50;
|
||||
background: #333;
|
||||
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.1);
|
||||
}
|
||||
|
||||
.input-field::placeholder {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.input-image {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.input-image::placeholder {
|
||||
color: #555;
|
||||
}
|
||||
|
||||
/* 文本域样式 */
|
||||
.textarea-field {
|
||||
width: 100%;
|
||||
min-height: 80px;
|
||||
max-height: 200px;
|
||||
padding: 8px;
|
||||
background: #2d2d2d;
|
||||
border: 1px solid #444;
|
||||
border-radius: 3px;
|
||||
color: #e0e0e0;
|
||||
font-size: 13px;
|
||||
font-family: inherit;
|
||||
resize: vertical;
|
||||
box-sizing: border-box;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.5;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.textarea-field:focus {
|
||||
outline: none;
|
||||
border-color: #4caf50;
|
||||
background: #333;
|
||||
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.1);
|
||||
}
|
||||
|
||||
.textarea-field::placeholder {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 底部区域 */
|
||||
.artist-manager-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #333;
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.artist-count {
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.artist-count strong {
|
||||
color: #4caf50;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.help-text {
|
||||
font-style: italic;
|
||||
margin: 0;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
.artist-table-container::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.artist-table-container::-webkit-scrollbar-track {
|
||||
background: #1a1a1a;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.artist-table-container::-webkit-scrollbar-thumb {
|
||||
background: #4caf50;
|
||||
border-radius: 5px;
|
||||
border: 2px solid #1a1a1a;
|
||||
}
|
||||
|
||||
.artist-table-container::-webkit-scrollbar-thumb:hover {
|
||||
background: #45a049;
|
||||
}
|
||||
|
||||
/* Firefox 滚动条 */
|
||||
.artist-table-container {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #4caf50 #1a1a1a;
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.js"]
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import path from 'path';
|
||||
|
||||
const rewriteComfyImports = ({ isDev }) => {
|
||||
return {
|
||||
name: "rewrite-comfy-imports",
|
||||
resolveId(source) {
|
||||
if (!isDev) return;
|
||||
if (source === "/scripts/app.js") {
|
||||
return "http://127.0.0.1:8188/scripts/app.js";
|
||||
}
|
||||
if (source === "/scripts/api.js") {
|
||||
return "http://127.0.0.1:8188/scripts/api.js";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default defineConfig(({ mode }) => ({
|
||||
plugins: [
|
||||
react(),
|
||||
rewriteComfyImports({ isDev: mode === "development" })
|
||||
],
|
||||
build: {
|
||||
emptyOutDir: true,
|
||||
rollupOptions: {
|
||||
external: ['/scripts/app.js', '/scripts/api.js'],
|
||||
input: {
|
||||
main: path.resolve(__dirname, 'src/main.tsx'),
|
||||
},
|
||||
output: {
|
||||
dir: '../web/dist',
|
||||
entryFileNames: 'artist-manager.js',
|
||||
chunkFileNames: '[name]-[hash].js',
|
||||
assetFileNames: '[name][extname]',
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
Vendored
+59
File diff suppressed because one or more lines are too long
Vendored
+268
@@ -0,0 +1,268 @@
|
||||
.artist-manager {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 16px;
|
||||
background: #1e1e1e;
|
||||
color: #e0e0e0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.artist-manager-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 2px solid #333;
|
||||
}
|
||||
|
||||
.artist-manager-header h2 {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.artist-manager-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-add {
|
||||
background: #4caf50;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-add:hover {
|
||||
background: #45a049;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
background: #2196f3;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-save:hover {
|
||||
background: #1976d2;
|
||||
}
|
||||
|
||||
.btn-refresh {
|
||||
background: #ff9800;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-refresh:hover {
|
||||
background: #f57c00;
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
background: #757575;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-close:hover {
|
||||
background: #616161;
|
||||
}
|
||||
|
||||
.btn-delete {
|
||||
background: #f44336;
|
||||
color: #fff;
|
||||
padding: 4px 8px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.btn-delete:hover {
|
||||
background: #d32f2f;
|
||||
}
|
||||
|
||||
.artist-manager-message {
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.artist-manager-message.success {
|
||||
background: #4caf50;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.artist-manager-message.error {
|
||||
background: #f44336;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.artist-manager-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
font-size: 18px;
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.artist-table-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #333;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.artist-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 14px;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.artist-table thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: #2d2d2d;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.artist-table th {
|
||||
padding: 12px 8px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
color: #4caf50;
|
||||
border-bottom: 2px solid #4caf50;
|
||||
}
|
||||
|
||||
.artist-table td {
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #333;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.artist-table tbody tr:hover {
|
||||
background: #252525;
|
||||
}
|
||||
|
||||
.col-name {
|
||||
width: 25%;
|
||||
padding-left: 4px !important;
|
||||
padding-right: 4px !important;
|
||||
}
|
||||
|
||||
.col-keywords {
|
||||
width: 65%;
|
||||
padding-left: 4px !important;
|
||||
padding-right: 4px !important;
|
||||
}
|
||||
|
||||
.col-actions {
|
||||
width: 10%;
|
||||
text-align: center;
|
||||
padding-left: 4px !important;
|
||||
padding-right: 4px !important;
|
||||
}
|
||||
|
||||
.artist-name-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
width: 100%;
|
||||
padding: 6px 8px;
|
||||
background: #2d2d2d;
|
||||
border: 1px solid #444;
|
||||
border-radius: 3px;
|
||||
color: #e0e0e0;
|
||||
font-size: 13px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.input-field:focus {
|
||||
outline: none;
|
||||
border-color: #4caf50;
|
||||
background: #333;
|
||||
}
|
||||
|
||||
.input-image {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.textarea-field {
|
||||
width: 100%;
|
||||
min-height: 60px;
|
||||
padding: 6px 8px;
|
||||
background: #2d2d2d;
|
||||
border: 1px solid #444;
|
||||
border-radius: 3px;
|
||||
color: #e0e0e0;
|
||||
font-size: 13px;
|
||||
font-family: inherit;
|
||||
resize: vertical;
|
||||
box-sizing: border-box;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.textarea-field:focus {
|
||||
outline: none;
|
||||
border-color: #4caf50;
|
||||
background: #333;
|
||||
}
|
||||
|
||||
.artist-manager-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #333;
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.artist-count {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.help-text {
|
||||
font-style: italic;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.artist-table-container::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.artist-table-container::-webkit-scrollbar-track {
|
||||
background: #1e1e1e;
|
||||
}
|
||||
|
||||
.artist-table-container::-webkit-scrollbar-thumb {
|
||||
background: #4caf50;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.artist-table-container::-webkit-scrollbar-thumb:hover {
|
||||
background: #45a049;
|
||||
}
|
||||
Reference in New Issue
Block a user