mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-24 22:52:12 -03:00
feat: add Vue widget demo node and development support
- Add LoraManagerDemoNode to node mappings for Vue widget demonstration - Update .gitignore to exclude Vue widget development artifacts (node_modules, .vite, dist) - Implement automatic Vue widget build check in development mode with fallback handling - Maintain pytest compatibility with proper import error handling
This commit is contained in:
176
vue-widgets/src/components/DemoWidget.vue
Normal file
176
vue-widgets/src/components/DemoWidget.vue
Normal file
@@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<div class="demo-widget-container">
|
||||
<h3 class="demo-title">LoRA Manager Demo Widget</h3>
|
||||
|
||||
<div class="demo-content">
|
||||
<div class="input-group">
|
||||
<label for="demo-input">Model Name:</label>
|
||||
<InputText
|
||||
id="demo-input"
|
||||
v-model="modelName"
|
||||
placeholder="Enter model name..."
|
||||
class="demo-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<label for="strength-input">Strength:</label>
|
||||
<InputNumber
|
||||
id="strength-input"
|
||||
v-model="strength"
|
||||
:min="0"
|
||||
:max="2"
|
||||
:step="0.1"
|
||||
showButtons
|
||||
class="demo-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<Button
|
||||
label="Apply"
|
||||
icon="pi pi-check"
|
||||
@click="handleApply"
|
||||
severity="success"
|
||||
/>
|
||||
<Button
|
||||
label="Reset"
|
||||
icon="pi pi-refresh"
|
||||
@click="handleReset"
|
||||
severity="secondary"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Card v-if="appliedValue" class="result-card">
|
||||
<template #title>Current Configuration</template>
|
||||
<template #content>
|
||||
<p><strong>Model:</strong> {{ appliedValue.modelName || 'None' }}</p>
|
||||
<p><strong>Strength:</strong> {{ appliedValue.strength }}</p>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import Button from 'primevue/button'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import InputNumber from 'primevue/inputnumber'
|
||||
import Card from 'primevue/card'
|
||||
|
||||
interface ComponentWidget {
|
||||
serializeValue?: (node: unknown, index: number) => Promise<unknown>
|
||||
value?: unknown
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
widget: ComponentWidget
|
||||
node: { id: number }
|
||||
}>()
|
||||
|
||||
const modelName = ref('')
|
||||
const strength = ref(1.0)
|
||||
const appliedValue = ref<{ modelName: string; strength: number } | null>(null)
|
||||
|
||||
function handleApply() {
|
||||
appliedValue.value = {
|
||||
modelName: modelName.value,
|
||||
strength: strength.value
|
||||
}
|
||||
console.log('Applied configuration:', appliedValue.value)
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
modelName.value = ''
|
||||
strength.value = 1.0
|
||||
appliedValue.value = null
|
||||
console.log('Reset configuration')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// Serialize the widget value when the workflow is saved or executed
|
||||
props.widget.serializeValue = async () => {
|
||||
const value = appliedValue.value || { modelName: '', strength: 1.0 }
|
||||
console.log('Serializing widget value:', value)
|
||||
return value
|
||||
}
|
||||
|
||||
// Restore widget value if it exists
|
||||
if (props.widget.value) {
|
||||
const savedValue = props.widget.value as { modelName: string; strength: number }
|
||||
modelName.value = savedValue.modelName || ''
|
||||
strength.value = savedValue.strength || 1.0
|
||||
appliedValue.value = savedValue
|
||||
console.log('Restored widget value:', savedValue)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.demo-widget-container {
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
background: var(--comfy-menu-bg);
|
||||
border-radius: 4px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.demo-title {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--fg-color);
|
||||
}
|
||||
|
||||
.demo-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--fg-color);
|
||||
}
|
||||
|
||||
.demo-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.result-card {
|
||||
margin-top: 8px;
|
||||
background: var(--comfy-input-bg);
|
||||
}
|
||||
|
||||
.result-card :deep(.p-card-title) {
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.result-card :deep(.p-card-content) {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.result-card p {
|
||||
margin: 4px 0;
|
||||
font-size: 13px;
|
||||
color: var(--fg-color);
|
||||
}
|
||||
</style>
|
||||
73
vue-widgets/src/main.ts
Normal file
73
vue-widgets/src/main.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { createApp, type App as VueApp } from 'vue'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import DemoWidget from '@/components/DemoWidget.vue'
|
||||
|
||||
// @ts-ignore - ComfyUI external module
|
||||
import { app } from '../../../scripts/app.js'
|
||||
|
||||
const vueApps = new Map<number, VueApp>()
|
||||
|
||||
// @ts-ignore
|
||||
function createVueWidget(node) {
|
||||
const container = document.createElement('div')
|
||||
container.id = `lora-manager-demo-widget-${node.id}`
|
||||
container.style.width = '100%'
|
||||
container.style.height = '100%'
|
||||
container.style.minHeight = '300px'
|
||||
container.style.display = 'flex'
|
||||
container.style.flexDirection = 'column'
|
||||
container.style.overflow = 'hidden'
|
||||
|
||||
const widget = node.addDOMWidget(
|
||||
'lora_demo_widget',
|
||||
'lora-manager-demo',
|
||||
container,
|
||||
{
|
||||
getMinHeight: () => 320,
|
||||
hideOnZoom: false,
|
||||
serialize: true
|
||||
}
|
||||
)
|
||||
|
||||
const vueApp = createApp(DemoWidget, {
|
||||
widget,
|
||||
node
|
||||
})
|
||||
|
||||
vueApp.use(PrimeVue)
|
||||
|
||||
vueApp.mount(container)
|
||||
vueApps.set(node.id, vueApp)
|
||||
|
||||
widget.onRemove = () => {
|
||||
const vueApp = vueApps.get(node.id)
|
||||
if (vueApp) {
|
||||
vueApp.unmount()
|
||||
vueApps.delete(node.id)
|
||||
}
|
||||
}
|
||||
|
||||
return { widget }
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'comfyui.loramanager.demo',
|
||||
|
||||
getCustomWidgets() {
|
||||
return {
|
||||
// @ts-ignore
|
||||
LORA_DEMO_WIDGET(node) {
|
||||
return createVueWidget(node)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// @ts-ignore
|
||||
nodeCreated(node) {
|
||||
if (node.constructor?.comfyClass !== 'LoraManagerDemoNode') return
|
||||
|
||||
const [oldWidth, oldHeight] = node.size
|
||||
|
||||
node.setSize([Math.max(oldWidth, 350), Math.max(oldHeight, 400)])
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user