ui changes and bugfixes

This commit is contained in:
Madiator2011 2024-11-12 23:14:41 +01:00 committed by GitHub
parent 0124ebe43b
commit ae6b5014ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 1309 additions and 1134 deletions

View file

@ -121,8 +121,9 @@ def index():
'status': status, 'status': status,
'installed': dirs_ok, 'installed': dirs_ok,
'install_status': install_status, 'install_status': install_status,
'is_bcomfy': app_name == 'bcomfy' # Add this line 'is_bcomfy': app_name == 'bcomfy'
} }
filebrowser_status = get_filebrowser_status() filebrowser_status = get_filebrowser_status()
return render_template('index.html', return render_template('index.html',
apps=app_configs, apps=app_configs,
@ -130,11 +131,7 @@ def index():
pod_id=RUNPOD_POD_ID, pod_id=RUNPOD_POD_ID,
RUNPOD_PUBLIC_IP=os.environ.get('RUNPOD_PUBLIC_IP'), RUNPOD_PUBLIC_IP=os.environ.get('RUNPOD_PUBLIC_IP'),
RUNPOD_TCP_PORT_22=os.environ.get('RUNPOD_TCP_PORT_22'), RUNPOD_TCP_PORT_22=os.environ.get('RUNPOD_TCP_PORT_22'),
# lutzapps - CHANGE #2 - allow localhost Url for unsecure "http" and "ws" WebSockets protocol,
# according to LOCAL_DEBUG ENV var (used 3x in "index.html" changes)
enable_unsecure_localhost=os.environ.get('LOCAL_DEBUG'), enable_unsecure_localhost=os.environ.get('LOCAL_DEBUG'),
settings=settings, settings=settings,
current_auth_method=current_auth_method, current_auth_method=current_auth_method,
ssh_password=ssh_password, ssh_password=ssh_password,
@ -520,16 +517,28 @@ def get_model_types_route():
@app.route('/download_model', methods=['POST']) @app.route('/download_model', methods=['POST'])
def download_model_route(): def download_model_route():
url = request.json.get('url') try:
model_name = request.json.get('model_name') data = request.json
model_type = request.json.get('model_type') url = data.get('url')
civitai_token = request.json.get('civitai_token') or load_civitai_token() model_name = data.get('model_name')
hf_token = request.json.get('hf_token') or load_huggingface_token() # lutzapps - added HF_TOKEN ENV var support model_type = data.get('model_type')
version_id = request.json.get('version_id') civitai_token = data.get('civitai_token')
file_index = request.json.get('file_index') hf_token = data.get('hf_token')
version_id = data.get('version_id')
file_index = data.get('file_index')
# If no token provided in request, try to read from file
if not civitai_token:
try:
if os.path.exists('/workspace/.civitai_token'):
with open('/workspace/.civitai_token', 'r') as f:
token_data = json.load(f)
civitai_token = token_data.get('token')
except Exception as e:
app.logger.error(f"Error reading token file: {str(e)}")
is_civitai, _, _, _ = check_civitai_url(url) is_civitai, _, _, _ = check_civitai_url(url)
is_huggingface, _, _, _, _ = check_huggingface_url(url) # TODO: double call is_huggingface, _, _, _, _ = check_huggingface_url(url)
if not (is_civitai or is_huggingface): if not (is_civitai or is_huggingface):
return jsonify({'status': 'error', 'message': 'Unsupported URL. Please use Civitai or Hugging Face URLs.'}), 400 return jsonify({'status': 'error', 'message': 'Unsupported URL. Please use Civitai or Hugging Face URLs.'}), 400
@ -550,6 +559,11 @@ def download_model_route():
app.logger.error(error_message) app.logger.error(error_message)
return jsonify({'status': 'error', 'message': error_message}), 500 return jsonify({'status': 'error', 'message': error_message}), 500
except Exception as e:
error_message = f"Error processing request: {str(e)}\n{traceback.format_exc()}"
app.logger.error(error_message)
return jsonify({'status': 'error', 'message': error_message}), 400
@app.route('/get_model_folders') @app.route('/get_model_folders')
def get_model_folders(): def get_model_folders():
folders = {} folders = {}

View file

@ -45,15 +45,15 @@
font-size: 1.4em; font-size: 1.4em;
} }
.button-group { .button-group {
display: grid; display: flex;
grid-template-columns: repeat(2, 1fr); flex-direction: row;
gap: 10px; gap: 8px;
margin-bottom: 10px; margin-bottom: 10px;
} }
button { button {
border: none; border: none;
color: white; color: white;
padding: 12px 15px; padding: 8px 15px;
text-align: center; text-align: center;
text-decoration: none; text-decoration: none;
display: inline-flex; display: inline-flex;
@ -63,51 +63,36 @@
cursor: pointer; cursor: pointer;
border-radius: 5px; border-radius: 5px;
transition: background-color 0.3s, transform 0.1s; transition: background-color 0.3s, transform 0.1s;
width: 100%;
}
button:hover:not(:disabled) {
transform: translateY(-2px);
} }
button:disabled { button:disabled {
background-color: #cccccc; opacity: 0.6;
cursor: not-allowed; cursor: not-allowed;
} }
button i { button i {
margin-right: 5px; margin-right: 8px;
}
.start-button {
background-color: #4CAF50;
}
.start-button:hover:not(:disabled) {
background-color: #45a049;
}
.stop-button {
background-color: #f44336;
}
.stop-button:hover:not(:disabled) {
background-color: #da190b;
}
.log-button {
background-color: #008CBA;
}
.log-button:hover:not(:disabled) {
background-color: #007aa3;
}
.open-button {
background-color: #FF9800;
}
.open-button:hover:not(:disabled) {
background-color: #e68a00;
} }
.start-button { background-color: #4CAF50; }
.stop-button { background-color: #f44336; }
.log-button { background-color: #008CBA; }
.open-button { background-color: #FF9800; }
.force-kill-button { background-color: #d9534f; }
.install-button { .install-button {
width: 100%;
margin-top: 10px;
background-color: #9C27B0; background-color: #9C27B0;
grid-column: span 2;
} }
.install-button:hover { .install-button:hover {
background-color: #7B1FA2; background-color: #7B1FA2;
} }
.force-kill-button { .log-button:hover:not(:disabled) {
background-color: #d9534f; background-color: #007aa3;
}
.open-button:hover:not(:disabled) {
background-color: #e68a00;
}
.install-button:hover {
background-color: #7B1FA2;
} }
.force-kill-button:hover:not(:disabled) { .force-kill-button:hover:not(:disabled) {
background-color: #c9302c; background-color: #c9302c;
@ -456,7 +441,8 @@
} }
.fix-custom-nodes-button { .fix-custom-nodes-button {
background-color: #9C27B0; width: 100%; /* Keep fix custom nodes button full width */
margin-top: 10px;
} }
.fix-custom-nodes-button:hover { .fix-custom-nodes-button:hover {
@ -1061,13 +1047,6 @@
flex-direction: column; flex-direction: column;
} }
#logs-container {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
}
#logs { #logs {
flex: 1; flex: 1;
@ -1963,10 +1942,8 @@
overflow-y: auto; overflow-y: auto;
} }
.model-downloader-section, .model-folders-container {
.right-column { max-height: 600px;
height: auto;
max-height: calc(50vh - 100px);
} }
} }
@ -2336,6 +2313,63 @@
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
} }
/* Example URLs styling */
.example-urls {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin-top: 20px;
padding: 15px;
background-color: #333;
border-radius: 10px;
}
.example-url {
background-color: #2a2a2a;
padding: 15px;
border-radius: 8px;
transition: transform 0.2s, box-shadow 0.2s;
}
.example-url:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.example-label {
display: block;
font-weight: bold;
color: #4CAF50;
margin-bottom: 8px;
font-size: 0.9em;
text-transform: uppercase;
}
.example-link {
display: block;
color: #fff;
text-decoration: none;
word-break: break-all;
font-family: monospace;
font-size: 0.9em;
padding: 8px;
background-color: #1a1a1a;
border-radius: 4px;
border: 1px solid #444;
}
.example-link:hover {
background-color: #2c2c2c;
border-color: #4CAF50;
}
/* Responsive adjustments */
@media (max-width: 1200px) {
.example-urls {
grid-template-columns: 1fr;
}
}
</style> </style>
</head> </head>
<body> <body>
@ -2369,7 +2403,6 @@
{% for app_key, app_info in apps.items() %} {% for app_key, app_info in apps.items() %}
<div class="app"> <div class="app">
<h2>{{ app_info.name }}</h2> <h2>{{ app_info.name }}</h2>
{% if app_status[app_key]['dirs_ok'] %}
<div class="button-group"> <div class="button-group">
<button onclick="startApp('{{ app_key }}')" id="start-{{ app_key }}" class="start-button" {% if app_status[app_key]['status'] == 'running' %}disabled{% endif %}> <button onclick="startApp('{{ app_key }}')" id="start-{{ app_key }}" class="start-button" {% if app_status[app_key]['status'] == 'running' %}disabled{% endif %}>
<i class="fas fa-play"></i> Start <i class="fas fa-play"></i> Start
@ -2384,47 +2417,20 @@
<i class="fas fa-external-link-alt"></i> Open App <i class="fas fa-external-link-alt"></i> Open App
</button> </button>
<button onclick="forceKillApp('{{ app_key }}')" id="force-kill-{{ app_key }}" class="force-kill-button"> <button onclick="forceKillApp('{{ app_key }}')" id="force-kill-{{ app_key }}" class="force-kill-button">
<i class="fas fa-skull-crossbones"></i> Force Kill <i class="fas fa-times-circle"></i> Force Kill
</button> </button>
{% if app_status[app_key]['dirs_ok'] and app_status[app_key]['is_bcomfy'] %} </div>
{% if app_key == 'bcomfy' and app_status[app_key]['installed'] %}
<button onclick="fixCustomNodes('{{ app_key }}')" id="fix-custom-nodes-{{ app_key }}" class="fix-custom-nodes-button"> <button onclick="fixCustomNodes('{{ app_key }}')" id="fix-custom-nodes-{{ app_key }}" class="fix-custom-nodes-button">
<i class="fas fa-wrench"></i> Fix Custom Nodes <i class="fas fa-wrench"></i> Fix Custom Nodes
</button> </button>
{% endif %} {% endif %}
</div>
<span id="status-{{ app_key }}" class="status status-{{ app_status[app_key]['status'] }}">
{{ app_status[app_key]['status'] | capitalize }}
</span>
{% else %}
<p class="error-message">{{ app_status[app_key]['message'] }}</p>
{% if not app_status[app_key]['installed'] %} {% if not app_status[app_key]['installed'] %}
<div class="install-container"> <button onclick="installApp('{{ app_key }}')" id="install-{{ app_key }}" class="install-button">
<button onclick="installApp('{{ app_key }}')" id="install-{{ app_key }}" class="install-button" {% if app_status[app_key]['install_status']['status'] == 'in_progress' %}disabled{% endif %}> <i class="fas fa-download"></i> Install
<i class="fas fa-download"></i> {% if app_status[app_key]['install_status']['status'] == 'in_progress' %}Installing...{% else %}Install {{ app_info.name }}{% endif %}
</button> </button>
<div id="install-progress-{{ app_key }}" class="install-progress" {% if app_status[app_key]['install_status']['status'] == 'in_progress' %}style="display: block;"{% endif %}>
<div class="progress-container">
<div class="progress-label">Download Progress:</div>
<div class="progress-bar">
<div class="progress-bar-fill download-progress" style="width: 0%">0%</div>
</div>
</div>
<div class="progress-container">
<div class="progress-label">Unpack Progress:</div>
<div class="progress-bar">
<div class="progress-bar-fill unpack-progress" style="width: 0%">0%</div>
</div>
</div>
<div class="download-info">
<span class="download-speed"></span>
<span class="download-eta"></span>
</div><!-- 4 lutzapps - remove a typo "4" -->
<div class="install-stage"></div>
</div>
</div>
<div id="install-logs-{{ app_key }}" class="install-logs"></div>
{% endif %}
{% endif %} {% endif %}
<div class="status">Status: {{ app_status[app_key]['status'] }}</div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
@ -2537,27 +2543,27 @@
<div id="example-urls" class="inner-tab-content active"> <div id="example-urls" class="inner-tab-content active">
<div class="example-urls"> <div class="example-urls">
<div class="example-url"> <div class="example-url">
<span class="example-label">Stable Diffusion:</span> <span class="example-label">Stable Diffusion Model:</span>
<a href="#" class="example-link" id="example-sd" onclick="useInModelDownloader('ckpt', this.textContent); return false;"></a> <a href="#" class="example-link" id="example-sd" onclick="useInModelDownloader('ckpt', this.textContent); return false;"></a>
</div> </div>
<div class="example-url"> <div class="example-url">
<span class="example-label">LoRA:</span> <span class="example-label">LoRA Model:</span>
<a href="#" class="example-link" id="example-lora" onclick="useInModelDownloader('loras', this.textContent); return false;"></a> <a href="#" class="example-link" id="example-lora" onclick="useInModelDownloader('loras', this.textContent); return false;"></a>
</div> </div>
<div class="example-url"> <div class="example-url">
<span class="example-label">VAE:</span> <span class="example-label">VAE Model:</span>
<a href="#" class="example-link" id="example-vae" onclick="useInModelDownloader('vae', this.textContent); return false;"></a> <a href="#" class="example-link" id="example-vae" onclick="useInModelDownloader('vae', this.textContent); return false;"></a>
</div> </div>
<div class="example-url"> <div class="example-url">
<span class="example-label">Upscaler:</span> <span class="example-label">Upscaler Model:</span>
<a href="#" class="example-link" id="example-upscaler" onclick="useInModelDownloader('upscale_models', this.textContent); return false;"></a> <a href="#" class="example-link" id="example-upscaler" onclick="useInModelDownloader('upscale_models', this.textContent); return false;"></a>
</div> </div>
<div class="example-url"> <div class="example-url">
<span class="example-label">Flux Dev:</span> <span class="example-label">Flux Dev Model:</span>
<a href="#" class="example-link" id="example-flux-dev" onclick="useInModelDownloader('unet', this.textContent); return false;"></a> <a href="#" class="example-link" id="example-flux-dev" onclick="useInModelDownloader('unet', this.textContent); return false;"></a>
</div> </div>
<div class="example-url"> <div class="example-url">
<span class="example-label">Flux Schnell:</span> <span class="example-label">Flux Schnell Model:</span>
<a href="#" class="example-link" id="example-flux-schnell" onclick="useInModelDownloader('unet', this.textContent); return false;"></a> <a href="#" class="example-link" id="example-flux-schnell" onclick="useInModelDownloader('unet', this.textContent); return false;"></a>
</div> </div>
</div> </div>
@ -2589,6 +2595,20 @@
</audio> </audio>
</div> </div>
<!-- Add this near the top of the body, before other scripts -->
<script>
// Make app_configs available to JavaScript
const app_configs = {
{% for app_key, app_info in apps.items() %}
"{{ app_key }}": {
"name": "{{ app_info.name }}",
"port": {{ app_info.port }}
},
{% endfor %}
};
</script>
<!-- Rest of your JavaScript code -->
<script> <script>
const appStatuses = {}; const appStatuses = {};
let currentLogApp = null; let currentLogApp = null;
@ -2806,25 +2826,96 @@
} }
async function startApp(appKey) { async function startApp(appKey) {
try {
const response = await fetch(`/start/${appKey}`); const response = await fetch(`/start/${appKey}`);
const data = await response.json(); const data = await response.json();
if (data.status === 'started') { if (data.status === 'started' || data.status === 'already_running') {
updateAppStatus(appKey, 'running'); updateAppStatus({ [appKey]: 'running' });
viewLogs(appKey, appConfigs[appKey].name); // Get the app name from app_configs and show logs
} else if (data.status === 'error') { const appName = app_configs[appKey].name;
alert(data.message); // Switch to logs view
currentLogApp = appKey;
currentLogAppName = appName;
document.getElementById('currentAppName').textContent = `Logs - ${appName}`;
document.getElementById('downloadLogsBtn').style.display = 'flex';
document.getElementById('logs').textContent = ''; // Clear existing logs
updateLogs(); // Start updating logs
// Make sure logs section is visible
const logsSection = document.querySelector('.logs-section');
if (logsSection) {
logsSection.style.display = 'flex';
}
}
} catch (error) {
console.error('Error:', error);
}
}
function updateAppStatus(data) {
for (const [appKey, status] of Object.entries(data)) {
// Update status text
const statusDiv = document.querySelector(`#apps .app:has(#start-${appKey}) .status`);
if (statusDiv) {
statusDiv.textContent = `Status: ${status}`;
}
// Update button states
const startButton = document.getElementById(`start-${appKey}`);
const stopButton = document.getElementById(`stop-${appKey}`);
const openButton = document.getElementById(`open-${appKey}`);
if (startButton) {
startButton.disabled = (status === 'running');
}
if (stopButton) {
stopButton.disabled = (status === 'stopped');
}
if (openButton) {
openButton.disabled = (status === 'stopped');
}
}
}
function viewLogs(appKey, appName) {
currentLogApp = appKey;
currentLogAppName = appName;
document.getElementById('currentAppName').textContent = `Logs - ${appName}`;
document.getElementById('downloadLogsBtn').style.display = 'flex';
updateLogs();
// Make sure logs section is visible
const logsSection = document.querySelector('.logs-section');
if (logsSection) {
logsSection.style.display = 'flex';
} }
} }
async function stopApp(appKey) { async function stopApp(appKey) {
try {
const response = await fetch(`/stop/${appKey}`); const response = await fetch(`/stop/${appKey}`);
const data = await response.json(); const data = await response.json();
if (data.status === 'stopped') { if (data.status === 'stopped' || data.status === 'already_stopped') {
updateAppStatus(appKey, 'stopped'); // Update button states
const startButton = document.getElementById(`start-${appKey}`);
const stopButton = document.getElementById(`stop-${appKey}`);
const openButton = document.getElementById(`open-${appKey}`);
if (startButton) startButton.disabled = false; // Enable start button
if (stopButton) stopButton.disabled = true; // Disable stop button
if (openButton) openButton.disabled = true; // Disable open button
// Update status text
const statusDiv = document.querySelector(`#apps .app:has(#start-${appKey}) .status`);
if (statusDiv) {
statusDiv.textContent = 'Status: stopped';
}
} else if (data.status === 'error') { } else if (data.status === 'error') {
alert(data.message); alert(data.message);
} }
await updateStatus(); } catch (error) {
console.error('Error:', error);
}
} }
async function openApp(appKey, port) { async function openApp(appKey, port) {
@ -2878,14 +2969,6 @@
} }
} }
function viewLogs(appKey, appName) {
currentLogApp = appKey;
currentLogAppName = appName;
document.getElementById('currentAppName').textContent = `Logs: ${appName}`;
updateLogs();
document.getElementById('downloadLogsBtn').style.display = 'flex';
}
function updateLogs() { function updateLogs() {
if (currentLogApp) { if (currentLogApp) {
fetch(`/logs/${currentLogApp}`) fetch(`/logs/${currentLogApp}`)
@ -3150,6 +3233,7 @@
} }
} }
function showVersionChoiceDialog(data, url, modelName, modelType, civitaiToken, hfToken) { function showVersionChoiceDialog(data, url, modelName, modelType, civitaiToken, hfToken) {
const dialogContent = ` const dialogContent = `
<h3>Multiple versions available. Please choose one:</h3> <h3>Multiple versions available. Please choose one:</h3>
@ -3236,7 +3320,13 @@
} }
async function saveCivitaiToken() { async function saveCivitaiToken() {
const token = document.getElementById('civitaiTokenSave').value; // Get token from the main Civitai token input field
const token = document.getElementById('civitaiToken').value;
if (!token) {
alert('Please enter a Civitai API token');
return;
}
try { try {
const response = await fetch('/save_civitai_token', { const response = await fetch('/save_civitai_token', {
method: 'POST', method: 'POST',
@ -3247,15 +3337,16 @@
}); });
const result = await response.json(); const result = await response.json();
if (result.status === 'success') { if (result.status === 'success') {
document.getElementById('civitaiTokenStatus').textContent = 'Token saved successfully.'; // Update both token fields to show it's saved
document.getElementById('civitaiToken').value = '********'; document.getElementById('civitaiToken').value = '********';
document.getElementById('civitaiTokenSave').value = '********'; document.getElementById('civitaiTokenSave').value = '********';
alert('Civitai token saved successfully.');
} else { } else {
document.getElementById('civitaiTokenStatus').textContent = `Error: ${result.message}`; alert(`Error: ${result.message}`);
} }
} catch (error) { } catch (error) {
console.error('Error:', error); console.error('Error:', error);
document.getElementById('civitaiTokenStatus').textContent = `Error: ${error.message}`; alert(`Error: ${error.message}`);
} }
} }
@ -3285,15 +3376,12 @@
} }
} }
function formatSize(sizeInBytes) { function formatSize(bytes) {
const units = ['B', 'KB', 'MB', 'GB', 'TB']; if (!bytes || isNaN(bytes)) return '0 B';
let size = sizeInBytes; const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
let unitIndex = 0; const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), sizes.length - 1);
while (size >= 1024 && unitIndex < units.length - 1) { const size = bytes / Math.pow(1024, i);
size /= 1024; return `${size.toFixed(2)} ${sizes[i]}`;
unitIndex++;
}
return `${size.toFixed(2)} ${units[unitIndex]}`;
} }
function formatTime(seconds) { function formatTime(seconds) {

View file

@ -14,7 +14,7 @@ import time
import datetime import datetime
import shutil import shutil
from utils.app_configs import (app_configs, DEBUG_SETTINGS, pretty_dict, init_app_configs, init_debug_settings, write_debug_setting, ensure_kohya_local_venv_is_symlinked) from utils.app_configs import (app_configs, DEBUG_SETTINGS, pretty_dict, init_app_configs, init_debug_settings, write_debug_setting, ensure_kohya_local_venv_is_symlinked)
from utils.model_utils import (get_sha256_hash_from_file) from utils.model_utils import (get_sha256_hash_from_file, download_civitai_model)
INSTALL_STATUS_FILE = '/tmp/install_status.json' INSTALL_STATUS_FILE = '/tmp/install_status.json'
@ -701,6 +701,11 @@ def download_and_unpack_venv_v2(app_name:str, app_configs:dict, send_websocket_m
send_websocket_message('install_progress', {'app_name': app_name, 'percentage': 100, 'stage': 'Unpacking Complete'}) send_websocket_message('install_progress', {'app_name': app_name, 'percentage': 100, 'stage': 'Unpacking Complete'})
send_websocket_message('install_log', {'app_name': app_name, 'log': 'Unpacking complete. Proceeding to clone repository...'}) send_websocket_message('install_log', {'app_name': app_name, 'log': 'Unpacking complete. Proceeding to clone repository...'})
# Get the app config from app_configs
app_config = app_configs.get(app_name)
if not app_config:
return False, f"App '{app_name}' not found in configurations."
# Clone the repository # Clone the repository
success, message = clone_application(app_config, send_websocket_message) success, message = clone_application(app_config, send_websocket_message)
if not success: if not success:
@ -1074,3 +1079,71 @@ def install_app(app_name:str, app_configs:dict, send_websocket_message) -> tuple
return success, message return success, message
else: else:
return False, f"Unknown app: {app_name}" return False, f"Unknown app: {app_name}"
def download_civitai_model(url, model_name, model_type, civitai_token=None, version_id=None, file_index=None):
try:
model_id = extract_model_id(url)
if not model_id:
raise ValueError("Could not extract model ID from URL")
# Use provided token or try to read from file
if not civitai_token:
try:
if os.path.exists('/workspace/.civitai_token'):
with open('/workspace/.civitai_token', 'r') as f:
token_data = json.load(f)
civitai_token = token_data.get('token')
except Exception as e:
print(f"Error reading token file: {str(e)}")
headers = {"Authorization": f"Bearer {civitai_token}"} if civitai_token else {}
# Get model info
model_info_url = f"https://civitai.com/api/v1/models/{model_id}"
response = requests.get(model_info_url, headers=headers)
if response.status_code != 200:
raise Exception(f"Failed to get model info: {response.text}")
model_info = response.json()
versions = model_info.get('modelVersions', [])
if not versions:
raise Exception("No versions found for this model")
# Handle version selection - return early if selection needed
if version_id is None and len(versions) > 1:
return {
'type': 'version',
'versions': versions,
'civitai_token': civitai_token
}
# Get the selected version
selected_version = versions[0] if version_id is None else next(
(v for v in versions if str(v['id']) == str(version_id)), None)
if not selected_version:
raise Exception("Specified version not found")
# Handle file selection - return early if selection needed
files = selected_version.get('files', [])
if not files:
raise Exception("No files found for this version")
if file_index is None and len(files) > 1:
return {
'type': 'file',
'files': files,
'version_id': selected_version['id'],
'civitai_token': civitai_token
}
# Select the file
selected_file = files[file_index if file_index is not None else 0]
if not selected_file:
raise Exception("No file found")
return selected_file
except Exception as e:
raise Exception(f"Exception downloading from CivitAI: {str(e)}")