Image cache node

This commit is contained in:
space-nuko
2023-05-05 20:22:12 -05:00
parent 7ddda80cf6
commit ad35826c7b
17 changed files with 358 additions and 33 deletions

View File

@@ -45,6 +45,7 @@
"@litegraph-ts/nodes-basic": "workspace:*", "@litegraph-ts/nodes-basic": "workspace:*",
"@litegraph-ts/nodes-events": "workspace:*", "@litegraph-ts/nodes-events": "workspace:*",
"@litegraph-ts/nodes-math": "workspace:*", "@litegraph-ts/nodes-math": "workspace:*",
"@litegraph-ts/nodes-strings": "workspace:*",
"@litegraph-ts/tsconfig": "workspace:*", "@litegraph-ts/tsconfig": "workspace:*",
"@sveltejs/vite-plugin-svelte": "^2.1.1", "@sveltejs/vite-plugin-svelte": "^2.1.1",
"@tsconfig/svelte": "^4.0.1", "@tsconfig/svelte": "^4.0.1",

19
pnpm-lock.yaml generated
View File

@@ -43,6 +43,9 @@ importers:
'@litegraph-ts/nodes-math': '@litegraph-ts/nodes-math':
specifier: workspace:* specifier: workspace:*
version: link:litegraph/packages/nodes-math version: link:litegraph/packages/nodes-math
'@litegraph-ts/nodes-strings':
specifier: workspace:*
version: link:litegraph/packages/nodes-strings
'@litegraph-ts/tsconfig': '@litegraph-ts/tsconfig':
specifier: workspace:* specifier: workspace:*
version: link:litegraph/packages/tsconfig version: link:litegraph/packages/tsconfig
@@ -805,6 +808,22 @@ importers:
specifier: ^4.2.1 specifier: ^4.2.1
version: 4.3.1 version: 4.3.1
litegraph/packages/nodes-strings:
dependencies:
'@litegraph-ts/core':
specifier: workspace:*
version: link:../core
devDependencies:
'@litegraph-ts/tsconfig':
specifier: workspace:*
version: link:../tsconfig
typescript:
specifier: ^5.0.3
version: 5.0.3
vite:
specifier: ^4.2.1
version: 4.3.1
litegraph/packages/tsconfig: {} litegraph/packages/tsconfig: {}
packages: packages:

View File

@@ -231,9 +231,9 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
return res; return res;
} }
override onNodeSelected(node: LGraphNode) { override onSelectionChange(nodes: Record<number, LGraphNode>) {
const ls = get(layoutState) const ls = get(layoutState)
ls.currentSelectionNodes = [node] ls.currentSelectionNodes = Object.values(nodes)
ls.currentSelection = [] ls.currentSelection = []
layoutState.set(ls) layoutState.set(ls)
} }

View File

@@ -10,6 +10,8 @@ import type TypedEmitter from "typed-emitter";
import "@litegraph-ts/nodes-basic" import "@litegraph-ts/nodes-basic"
import "@litegraph-ts/nodes-events" import "@litegraph-ts/nodes-events"
import "@litegraph-ts/nodes-math" import "@litegraph-ts/nodes-math"
import "@litegraph-ts/nodes-strings"
import "$lib/nodes/index"
import * as nodes from "$lib/nodes/index" import * as nodes from "$lib/nodes/index"
import ComfyGraphCanvas, { type SerializedGraphCanvasState } from "$lib/ComfyGraphCanvas"; import ComfyGraphCanvas, { type SerializedGraphCanvasState } from "$lib/ComfyGraphCanvas";
@@ -25,7 +27,7 @@ import ComfyGraph from "$lib/ComfyGraph";
import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode"; import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
import { get } from "svelte/store"; import { get } from "svelte/store";
import uiState from "$lib/stores/uiState"; import uiState from "$lib/stores/uiState";
import { promptToGraphVis, toGraphVis } from "$lib/utils"; import { promptToGraphVis } from "$lib/utils";
export const COMFYBOX_SERIAL_VERSION = 1; export const COMFYBOX_SERIAL_VERSION = 1;
@@ -578,9 +580,15 @@ export default class ComfyApp {
try { try {
while (this.queueItems.length) { while (this.queueItems.length) {
({ num, batchCount } = this.queueItems.pop()); ({ num, batchCount } = this.queueItems.pop());
console.log(`Queue get! ${num} ${batchCount}`); console.debug(`Queue get! ${num} ${batchCount} ${tag}`);
for (let i = 0; i < batchCount; i++) { for (let i = 0; i < batchCount; i++) {
for (const node of this.lGraph._nodes_in_order) {
if ("beforeQueued" in node) {
(node as ComfyGraphNode).beforeQueued();
}
}
const p = await this.graphToPrompt(tag); const p = await this.graphToPrompt(tag);
try { try {

View File

@@ -41,6 +41,13 @@
targetType = "" targetType = ""
} }
function validNodeProperty(spec: AttributesSpec, node: LGraphNode): boolean {
if (spec.validNodeTypes) {
return spec.validNodeTypes.indexOf(node.type) !== -1;
}
return spec.name in node.properties
}
function updateAttribute(entry: AttributesSpec, value: any) { function updateAttribute(entry: AttributesSpec, value: any) {
if (target) { if (target) {
const name = entry.name const name = entry.name
@@ -157,7 +164,7 @@
{/if} {/if}
</div> </div>
{:else if node} {:else if node}
{#if spec.location === "nodeProps" && spec.name in node.properties} {#if spec.location === "nodeProps" && validNodeProperty(spec, node)}
<div class="props-entry"> <div class="props-entry">
{#if spec.type === "string"} {#if spec.type === "string"}
<TextBox <TextBox

View File

@@ -5,17 +5,18 @@ import type { SerializedPrompt } from "$lib/components/ComfyApp";
import { toast } from '@zerodevx/svelte-toast' import { toast } from '@zerodevx/svelte-toast'
import type { GalleryOutput } from "./ComfyWidgetNodes"; import type { GalleryOutput } from "./ComfyWidgetNodes";
export interface ComfyAfterQueuedEventProperties extends Record<any, any> { export interface ComfyQueueEventsProperties extends Record<any, any> {
prompt: SerializedPrompt prompt: SerializedPrompt | null
} }
export class ComfyAfterQueuedEvent extends ComfyGraphNode { export class ComfyQueueEvents extends ComfyGraphNode {
override properties: ComfyAfterQueuedEventProperties = { override properties: ComfyQueueEventsProperties = {
prompt: null prompt: null
} }
static slotLayout: SlotLayout = { static slotLayout: SlotLayout = {
outputs: [ outputs: [
{ name: "beforeQueued", type: BuiltInSlotType.EVENT },
{ name: "afterQueued", type: BuiltInSlotType.EVENT }, { name: "afterQueued", type: BuiltInSlotType.EVENT },
{ name: "prompt", type: "*" } { name: "prompt", type: "*" }
], ],
@@ -23,17 +24,22 @@ export class ComfyAfterQueuedEvent extends ComfyGraphNode {
override onPropertyChanged(property: string, value: any, prevValue?: any) { override onPropertyChanged(property: string, value: any, prevValue?: any) {
if (property === "value") { if (property === "value") {
this.setOutputData(0, this.properties.prompt) this.setOutputData(2, this.properties.prompt)
} }
} }
override onExecute() { override onExecute() {
this.setOutputData(0, this.properties.prompt) this.setOutputData(2, this.properties.prompt)
}
override beforeQueued() {
this.setProperty("value", null)
this.triggerSlot(0, "bang")
} }
override afterQueued(p: SerializedPrompt) { override afterQueued(p: SerializedPrompt) {
this.setProperty("value", p) this.setProperty("value", p)
this.triggerSlot(0, "bang") this.triggerSlot(1, "bang")
} }
override onSerialize(o: SerializedLGraphNode) { override onSerialize(o: SerializedLGraphNode) {
@@ -43,19 +49,21 @@ export class ComfyAfterQueuedEvent extends ComfyGraphNode {
} }
LiteGraph.registerNodeType({ LiteGraph.registerNodeType({
class: ComfyAfterQueuedEvent, class: ComfyQueueEvents,
title: "Comfy.AfterQueuedEvent", title: "Comfy.QueueEvents",
desc: "Triggers a 'bang' event when a prompt is queued.", desc: "Triggers a 'bang' event when a prompt is queued.",
type: "actions/after_queued" type: "actions/queue_events"
}) })
export interface ComfyOnExecutedEventProperties extends Record<any, any> { export interface ComfyOnExecutedEventProperties extends Record<any, any> {
images: GalleryOutput | null images: GalleryOutput | null,
filename: string | null
} }
export class ComfyOnExecutedEvent extends ComfyGraphNode { export class ComfyOnExecutedEvent extends ComfyGraphNode {
override properties: ComfyOnExecutedEventProperties = { override properties: ComfyOnExecutedEventProperties = {
images: null images: null,
filename: null
} }
static slotLayout: SlotLayout = { static slotLayout: SlotLayout = {
@@ -63,7 +71,7 @@ export class ComfyOnExecutedEvent extends ComfyGraphNode {
{ name: "images", type: "IMAGE" } { name: "images", type: "IMAGE" }
], ],
outputs: [ outputs: [
{ name: "images", type: "IMAGE" }, { name: "images", type: "OUTPUT" },
{ name: "onExecuted", type: BuiltInSlotType.EVENT }, { name: "onExecuted", type: BuiltInSlotType.EVENT },
], ],
} }
@@ -76,6 +84,7 @@ export class ComfyOnExecutedEvent extends ComfyGraphNode {
override receiveOutput(output: any) { override receiveOutput(output: any) {
if (output && "images" in output) { if (output && "images" in output) {
this.setProperty("images", output as GalleryOutput) this.setProperty("images", output as GalleryOutput)
this.setOutputData(0, this.properties.images)
this.triggerSlot(1, "bang") this.triggerSlot(1, "bang")
} }
} }
@@ -202,3 +211,43 @@ LiteGraph.registerNodeType({
desc: "Displays a message.", desc: "Displays a message.",
type: "actions/notify" type: "actions/notify"
}) })
export interface ComfyExecuteSubgraphActionProperties extends Record<any, any> {
tag: string | null,
}
export class ComfyExecuteSubgraphAction extends ComfyGraphNode {
override properties: ComfyExecuteSubgraphActionProperties = {
tag: null
}
static slotLayout: SlotLayout = {
inputs: [
{ name: "execute", type: BuiltInSlotType.ACTION },
{ name: "tag", type: "string" }
],
}
override onExecute() {
const tag = this.getInputData(1)
if (tag)
this.setProperty("tag", tag)
}
override onAction(action: any, param: any) {
const tag = this.getInputData(1) || this.properties.tag;
const app = (window as any)?.app;
if (!app)
return;
app.queuePrompt(0, 1, tag);
}
}
LiteGraph.registerNodeType({
class: ComfyExecuteSubgraphAction,
title: "Comfy.ExecuteSubgraphAction",
desc: "Runs a part of the graph based on a tag",
type: "actions/execute_subgraph"
})

View File

@@ -20,6 +20,7 @@ export type DefaultWidgetLayout = {
export default class ComfyGraphNode extends LGraphNode { export default class ComfyGraphNode extends LGraphNode {
isBackendNode?: boolean; isBackendNode?: boolean;
beforeQueued?(): void;
afterQueued?(prompt: SerializedPrompt): void; afterQueued?(prompt: SerializedPrompt): void;
onExecuted?(output: any): void; onExecuted?(output: any): void;
@@ -61,7 +62,7 @@ export default class ComfyGraphNode extends LGraphNode {
for (let index = 0; index < this.inputs.length; index++) { for (let index = 0; index < this.inputs.length; index++) {
const input = this.inputs[index] const input = this.inputs[index]
const serInput = o.inputs[index] const serInput = o.inputs[index]
if ("widgetNodeType" in serInput) { if (serInput && "widgetNodeType" in serInput) {
const comfyInput = input as IComfyInputSlot const comfyInput = input as IComfyInputSlot
const ty: string = serInput.widgetNodeType as any const ty: string = serInput.widgetNodeType as any
const widgetNode = Object.values(LiteGraph.registered_node_types) const widgetNode = Object.values(LiteGraph.registered_node_types)

View File

@@ -0,0 +1,211 @@
import { BuiltInSlotType, LiteGraph, type ITextWidget, type SlotLayout, clamp } from "@litegraph-ts/core";
import ComfyGraphNode from "./ComfyGraphNode";
import type { GalleryOutput } from "./ComfyWidgetNodes";
export interface ComfyImageCacheNodeProperties extends Record<any, any> {
images: GalleryOutput | null,
index: number,
filenames: Record<number, { filename: string | null, status: ImageCacheState }>,
genNumber: number
}
type ImageCacheState = "none" | "uploading" | "failed" | "cached"
/*
* A node that can act as both an input and output image node by uploading
* the output file into ComfyUI's input folder.
*/
export default class ComfyImageCacheNode extends ComfyGraphNode {
override properties: ComfyImageCacheNodeProperties = {
images: null,
index: 0,
filenames: {},
genNumber: 0
}
static slotLayout: SlotLayout = {
inputs: [
{ name: "images", type: "OUTPUT" },
{ name: "index", type: "number" },
{ name: "store", type: BuiltInSlotType.ACTION },
{ name: "clear", type: BuiltInSlotType.ACTION }
],
outputs: [
{ name: "filename", type: "string" },
{ name: "state", type: "string" },
]
}
private _uploadPromise: Promise<void> | null = null;
private _state: ImageCacheState = "none"
stateWidget: ITextWidget;
filenameWidget: ITextWidget;
constructor(name?: string) {
super(name)
this.stateWidget = this.addWidget<ITextWidget>(
"text",
"State",
"none"
);
this.stateWidget.disabled = true;
this.filenameWidget = this.addWidget<ITextWidget>(
"text",
"File",
""
);
this.filenameWidget.disabled = true;
}
override onPropertyChanged(property: string, value: any, prevValue?: any) {
if (property === "images") {
if (value != null)
this.properties.index = clamp(this.properties.index, 0, value.length)
else
this.properties.index = 0
}
if (this.properties.filenames && this.properties.images) {
const fileCount = this.properties.images.images.length;
const cachedCount = Object.keys(this.properties.filenames).length
console.warn(cachedCount, this.properties.filenames)
this.filenameWidget.value = `${fileCount} files, ${cachedCount} cached`
}
}
override onExecute() {
const index = this.getInputData(1)
if (typeof index === "number")
this.setIndex(index)
const existing = this.properties.filenames[this.properties.index]
let state = "none"
if (existing)
state = existing.status
this.stateWidget.value = state
let filename = null
if (this.properties.index in this.properties.filenames)
filename = this.properties.filenames[this.properties.index].filename
this.setOutputData(0, filename)
this.setOutputData(1, state)
}
private setIndex(newIndex: number, force: boolean = false) {
console.debug("[ComfyImageCacheNode] setIndex", newIndex, force)
if (newIndex === this.properties.index && !force)
return;
if (!this.properties.images || newIndex < 0 || newIndex >= this.properties.images.images.length) {
console.debug("[ComfyImageCacheNode] invalid indexes", newIndex, this.properties.images)
return
}
this.setProperty("index", newIndex)
const data = this.properties.images.images[newIndex]
if (data == null) {
return;
}
this.properties.filenames ||= {}
const existing = this.properties.filenames[newIndex]
if (existing != null && existing.status === "cached") {
return
}
const lastGenNumber = this.properties.genNumber
// ComfyUI's LoadImage node only operates on files in its input
// folder. Usually we're dealing with an image in either the output
// folder (SaveImage) or the temp folder (PreviewImage). So we have
// to copy the image into ComfyUI's input folder first by using
// their upload API.
if (data.subfolder === "input") {
// Already in the correct folder for use by LoadImage
this.properties.filenames[newIndex] = { filename: data.filename, status: "cached" }
this.onPropertyChanged("filenames", this.properties.filenames)
}
else {
this.properties.filenames[newIndex] = { filename: null, status: "uploading" }
this.onPropertyChanged("filenames", this.properties.filenames)
const url = "http://localhost:8188" // TODO make configurable
const params = new URLSearchParams(data)
const promise = fetch(url + "/view?" + params)
.then((r) => r.blob())
.then((blob) => {
console.debug("Fetchin", url, params)
const formData = new FormData();
formData.append("image", blob, data.filename);
return fetch(
new Request(url + "/upload/image", {
body: formData,
method: 'POST'
})
)
})
.then((r) => r.json())
.then((json) => {
console.debug("Gottem", json)
if (lastGenNumber === this.properties.genNumber) {
this.properties.filenames[newIndex] = { filename: data.filename, status: "cached" }
this.onPropertyChanged("filenames", this.properties.filenames)
}
else {
console.warn("[ComfyImageCacheNode] New generation since index switched!")
}
this._uploadPromise = null;
})
.catch((e) => {
console.error("Error uploading:", e)
if (lastGenNumber === this.properties.genNumber) {
this.properties.filenames[newIndex] = { filename: null, status: "failed" }
this.onPropertyChanged("filenames", this.properties.filenames)
}
else {
console.warn("[ComfyImageCacheNode] New generation since index switched!")
}
})
if (this._uploadPromise)
this._uploadPromise.then(() => promise)
else
this._uploadPromise = promise
}
}
override onAction(action: any) {
if (action === "clear") {
this.setProperty("images", null)
this.setProperty("filenames", {})
this.setProperty("index", 0)
return
}
const link = this.getInputLink(0)
if (link.data && "images" in link.data) {
this.setProperty("genNumber", this.properties.genNumber + 1)
this.setProperty("images", link.data as GalleryOutput)
this.setProperty("filenames", {})
console.debug("[ComfyImageCacheNode] Received output!", link.data)
this.setIndex(0, true)
}
}
}
LiteGraph.registerNodeType({
class: ComfyImageCacheNode,
title: "Comfy.ImageCache",
desc: "Allows reusing a previously output image by uploading it into ComfyUI's input folder.",
type: "image/cache"
})

View File

@@ -119,7 +119,6 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
if (this.inputs.length >= this.inputIndex) { if (this.inputs.length >= this.inputIndex) {
const data = this.getInputData(this.inputIndex) const data = this.getInputData(this.inputIndex)
if (data) { // TODO can "null" be a legitimate value here? if (data) { // TODO can "null" be a legitimate value here?
console.log(data)
this.setValue(data) this.setValue(data)
const input = this.getInputLink(this.inputIndex) const input = this.getInputLink(this.inputIndex)
input.data = null; input.data = null;
@@ -407,7 +406,7 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
static slotLayout: SlotLayout = { static slotLayout: SlotLayout = {
inputs: [ inputs: [
{ name: "images", type: "IMAGE" }, { name: "images", type: "OUTPUT" },
{ name: "store", type: BuiltInSlotType.ACTION } { name: "store", type: BuiltInSlotType.ACTION }
] ]
} }

View File

@@ -1,5 +1,6 @@
export { default as ComfyReroute } from "./ComfyReroute" export { default as ComfyReroute } from "./ComfyReroute"
export { ComfyWidgetNode, ComfySliderNode, ComfyComboNode, ComfyTextNode } from "./ComfyWidgetNodes" export { ComfyWidgetNode, ComfySliderNode, ComfyComboNode, ComfyTextNode } from "./ComfyWidgetNodes"
export { ComfyAfterQueuedEvent, ComfyCopyAction, ComfySwapAction, ComfyNotifyAction, ComfyOnExecutedEvent } from "./ComfyActionNodes" export { ComfyQueueEvents, ComfyCopyAction, ComfySwapAction, ComfyNotifyAction, ComfyOnExecutedEvent, ComfyExecuteSubgraphAction } from "./ComfyActionNodes"
export { default as ComfyValueControl } from "./ComfyValueControl" export { default as ComfyValueControl } from "./ComfyValueControl"
export { default as ComfySelector } from "./ComfySelector" export { default as ComfySelector } from "./ComfySelector"
export { default as ComfyImageCacheNode } from "./ComfyImageCacheNode"

View File

@@ -34,6 +34,7 @@ export type Attributes = {
classes: string, classes: string,
blockVariant?: "block" | "hidden", blockVariant?: "block" | "hidden",
hidden?: boolean, hidden?: boolean,
disabled?: boolean,
flexGrow?: number flexGrow?: number
} }
@@ -45,6 +46,7 @@ export type AttributesSpec = {
values?: string[], values?: string[],
hidden?: boolean, hidden?: boolean,
validNodeTypes?: string[],
serialize?: (arg: any) => string, serialize?: (arg: any) => string,
deserialize?: (arg: string) => any, deserialize?: (arg: string) => any,
@@ -73,6 +75,12 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
location: "widget", location: "widget",
editable: true editable: true
}, },
{
name: "disabled",
type: "boolean",
location: "widget",
editable: true
},
{ {
name: "direction", name: "direction",
type: "enum", type: "enum",
@@ -104,6 +112,7 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
{ {
categoryName: "behavior", categoryName: "behavior",
specs: [ specs: [
// Node variables
{ {
name: "tags", name: "tags",
type: "string", type: "string",
@@ -116,24 +125,40 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
return arg.split(",").map(s => s.trim()) return arg.split(",").map(s => s.trim())
} }
}, },
// Range
{ {
name: "min", name: "min",
type: "number", type: "number",
location: "nodeProps", location: "nodeProps",
editable: true, editable: true,
validNodeTypes: ["ui/slider"],
}, },
{ {
name: "max", name: "max",
type: "number", type: "number",
location: "nodeProps", location: "nodeProps",
editable: true editable: true,
validNodeTypes: ["ui/slider"],
}, },
{ {
name: "step", name: "step",
type: "number", type: "number",
location: "nodeProps", location: "nodeProps",
editable: true, editable: true,
validNodeTypes: ["ui/slider"],
}, },
// Button
{
name: "message",
type: "string",
location: "nodeProps",
editable: true,
validNodeTypes: ["ui/button"],
},
// Workflow
{ {
name: "defaultWorkflow", name: "defaultWorkflow",
type: "string", type: "string",
@@ -531,8 +556,6 @@ function deserialize(data: SerializedLayoutState, graph: LGraph) {
attrsChanged: writable(false) attrsChanged: writable(false)
}; };
dragItem.attrs.flexGrow = 100;
const dragEntry: DragItemEntry = { const dragEntry: DragItemEntry = {
dragItem, dragItem,
children: [], children: [],

View File

@@ -30,7 +30,11 @@
<div class="wrapper gradio-button"> <div class="wrapper gradio-button">
{#if node !== null} {#if node !== null}
<Button on:click={onClick} variant="primary" {style}> <Button
disabled={widget.attrs.disabled}
on:click={onClick}
variant="primary"
{style}>
{widget.attrs.title} {widget.attrs.title}
</Button> </Button>
{/if} {/if}

View File

@@ -73,7 +73,7 @@
<Select <Select
bind:value={option} bind:value={option}
items={node.properties.values} items={node.properties.values}
disabled={node.properties.values.length === 0} disabled={widget.attrs.disabled || node.properties.values.length === 0}
clearable={false} clearable={false}
showChevron={true} showChevron={true}
on:change on:change

View File

@@ -56,6 +56,7 @@
{#if node !== null && option !== null} {#if node !== null && option !== null}
<Range <Range
bind:value={option} bind:value={option}
disabled={widget.attrs.disabled}
minimum={node.properties.min} minimum={node.properties.min}
maximum={node.properties.max} maximum={node.properties.max}
step={node.properties.step} step={node.properties.step}

View File

@@ -32,6 +32,7 @@
<TextBox <TextBox
bind:value={$nodeValue} bind:value={$nodeValue}
label={widget.attrs.title} label={widget.attrs.title}
disabled={widget.attrs.disabled}
lines={node.properties.multiline ? 5 : 1} lines={node.properties.multiline ? 5 : 1}
max_lines={node.properties.multiline ? 5 : 1} max_lines={node.properties.multiline ? 5 : 1}
show_label={true} show_label={true}

View File

@@ -7,11 +7,11 @@ import FullReload from 'vite-plugin-full-reload';
export default defineConfig({ export default defineConfig({
clearScreen: false, clearScreen: false,
plugins: [ plugins: [
FullReload(["src/**/*.{js,ts,svelte}"]),
svelte(), svelte(),
// FullReload(["src/**/*.{js,ts,svelte}"])
], ],
resolve: { resolve: {
alias:{ alias: {
'$lib': resolve(__dirname, './src/lib'), '$lib': resolve(__dirname, './src/lib'),
}, },
}, },
@@ -28,9 +28,9 @@ export default defineConfig({
server: { server: {
port: 3000, port: 3000,
// hmr: { // hmr: {
// clientPort: 443, // clientPort: 443,
// }, // },
// fs: { // fs: {
// allow: [ // allow: [
// "src", // "src",