mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-29 18:52:19 +00:00
Add about panel in settings dialog (#799)
* basic about page * Remove frontend version from setting dialog header * Style about page * basic system stats * Basic styling * Reword * Format memory amount
This commit is contained in:
40
src/components/common/DeviceInfo.vue
Normal file
40
src/components/common/DeviceInfo.vue
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<template v-for="col in deviceColumns" :key="col.field">
|
||||||
|
<div class="font-medium">{{ $t(col.header) }}</div>
|
||||||
|
<div>{{ formatValue(props.device[col.field], col.field) }}</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { DeviceStats } from '@/types/apiTypes'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
device: DeviceStats
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const deviceColumns = [
|
||||||
|
{ field: 'name', header: 'Name' },
|
||||||
|
{ field: 'type', header: 'Type' },
|
||||||
|
{ field: 'vram_total', header: 'VRAM Total' },
|
||||||
|
{ field: 'vram_free', header: 'VRAM Free' },
|
||||||
|
{ field: 'torch_vram_total', header: 'Torch VRAM Total' },
|
||||||
|
{ field: 'torch_vram_free', header: 'Torch VRAM Free' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const formatValue = (value: any, field: string) => {
|
||||||
|
if (
|
||||||
|
['vram_total', 'vram_free', 'torch_vram_total', 'torch_vram_free'].includes(
|
||||||
|
field
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const mb = Math.round(value / (1024 * 1024))
|
||||||
|
if (mb >= 1024) {
|
||||||
|
return `${(mb / 1024).toFixed(2)} GB`
|
||||||
|
}
|
||||||
|
return `${mb} MB`
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
</script>
|
||||||
55
src/components/common/SystemStatsPanel.vue
Normal file
55
src/components/common/SystemStatsPanel.vue
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<div class="system-stats">
|
||||||
|
<div class="mb-6">
|
||||||
|
<h2 class="text-2xl font-semibold mb-4">{{ $t('systemInfo') }}</h2>
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<template v-for="col in systemColumns" :key="col.field">
|
||||||
|
<div class="font-medium">{{ $t(col.header) }}</div>
|
||||||
|
<div>{{ systemInfo[col.field] }}</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2 class="text-2xl font-semibold mb-4">{{ $t('devices') }}</h2>
|
||||||
|
<TabView v-if="props.stats.devices.length > 1">
|
||||||
|
<TabPanel
|
||||||
|
v-for="device in props.stats.devices"
|
||||||
|
:key="device.index"
|
||||||
|
:header="device.name"
|
||||||
|
>
|
||||||
|
<DeviceInfo :device="device" />
|
||||||
|
</TabPanel>
|
||||||
|
</TabView>
|
||||||
|
<DeviceInfo v-else :device="props.stats.devices[0]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import TabView from 'primevue/tabview'
|
||||||
|
import TabPanel from 'primevue/tabpanel'
|
||||||
|
import Divider from 'primevue/divider'
|
||||||
|
import type { SystemStats } from '@/types/apiTypes'
|
||||||
|
import DeviceInfo from '@/components/common/DeviceInfo.vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
stats: SystemStats
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const systemInfo = computed(() => ({
|
||||||
|
...props.stats.system,
|
||||||
|
argv: props.stats.system.argv.join(' ')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const systemColumns = [
|
||||||
|
{ field: 'os', header: 'OS' },
|
||||||
|
{ field: 'python_version', header: 'Python Version' },
|
||||||
|
{ field: 'embedded_python', header: 'Embedded Python' },
|
||||||
|
{ field: 'pytorch_version', header: 'Pytorch Version' },
|
||||||
|
{ field: 'argv', header: 'Arguments' }
|
||||||
|
]
|
||||||
|
</script>
|
||||||
@@ -51,6 +51,9 @@
|
|||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
<TabPanel key="about" value="About">
|
||||||
|
<AboutPanel />
|
||||||
|
</TabPanel>
|
||||||
</TabPanels>
|
</TabPanels>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
@@ -70,17 +73,25 @@ import SettingGroup from './setting/SettingGroup.vue'
|
|||||||
import SearchBox from '@/components/common/SearchBox.vue'
|
import SearchBox from '@/components/common/SearchBox.vue'
|
||||||
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
||||||
import { flattenTree } from '@/utils/treeUtil'
|
import { flattenTree } from '@/utils/treeUtil'
|
||||||
|
import AboutPanel from './setting/AboutPanel.vue'
|
||||||
|
|
||||||
interface ISettingGroup {
|
interface ISettingGroup {
|
||||||
label: string
|
label: string
|
||||||
settings: SettingParams[]
|
settings: SettingParams[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const aboutPanelNode: SettingTreeNode = {
|
||||||
|
key: 'about',
|
||||||
|
label: 'About',
|
||||||
|
children: []
|
||||||
|
}
|
||||||
|
|
||||||
const settingStore = useSettingStore()
|
const settingStore = useSettingStore()
|
||||||
const settingRoot = computed<SettingTreeNode>(() => settingStore.settingTree)
|
const settingRoot = computed<SettingTreeNode>(() => settingStore.settingTree)
|
||||||
const categories = computed<SettingTreeNode[]>(
|
const categories = computed<SettingTreeNode[]>(() => [
|
||||||
() => settingRoot.value.children || []
|
...(settingRoot.value.children || []),
|
||||||
)
|
aboutPanelNode
|
||||||
|
])
|
||||||
const activeCategory = ref<SettingTreeNode | null>(null)
|
const activeCategory = ref<SettingTreeNode | null>(null)
|
||||||
const searchResults = ref<ISettingGroup[]>([])
|
const searchResults = ref<ISettingGroup[]>([])
|
||||||
|
|
||||||
|
|||||||
68
src/components/dialog/content/setting/AboutPanel.vue
Normal file
68
src/components/dialog/content/setting/AboutPanel.vue
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h2 class="text-2xl font-bold mb-2">{{ $t('about') }}</h2>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<a
|
||||||
|
v-for="link in links"
|
||||||
|
:key="link.url"
|
||||||
|
:href="link.url"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="inline-flex items-center no-underline"
|
||||||
|
>
|
||||||
|
<Tag class="mr-2">
|
||||||
|
<template #icon>
|
||||||
|
<i :class="[link.icon, 'mr-2 text-xl']"></i>
|
||||||
|
</template>
|
||||||
|
{{ link.label }}
|
||||||
|
</Tag>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<SystemStatsPanel
|
||||||
|
v-if="systemStatsStore.systemStats"
|
||||||
|
:stats="systemStatsStore.systemStats"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
||||||
|
import Tag from 'primevue/tag'
|
||||||
|
import Divider from 'primevue/divider'
|
||||||
|
import { computed, onMounted } from 'vue'
|
||||||
|
import SystemStatsPanel from '@/components/common/SystemStatsPanel.vue'
|
||||||
|
|
||||||
|
const systemStatsStore = useSystemStatsStore()
|
||||||
|
const frontendVersion = window['__COMFYUI_FRONTEND_VERSION__']
|
||||||
|
const coreVersion = computed(
|
||||||
|
() => systemStatsStore.systemStats?.system?.comfyui_version ?? ''
|
||||||
|
)
|
||||||
|
|
||||||
|
const links = computed(() => [
|
||||||
|
{
|
||||||
|
label: `ComfyUI ${coreVersion.value}`,
|
||||||
|
url: 'https://github.com/comfyanonymous/ComfyUI',
|
||||||
|
icon: 'pi pi-github'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: `ComfyUI_frontend v${frontendVersion}`,
|
||||||
|
url: 'https://github.com/Comfy-Org/ComfyUI_frontend',
|
||||||
|
icon: 'pi pi-github'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Discord',
|
||||||
|
url: 'https://www.comfy.org/discord',
|
||||||
|
icon: 'pi pi-discord'
|
||||||
|
},
|
||||||
|
{ label: 'ComfyOrg', url: 'https://www.comfy.org/', icon: 'pi pi-globe' }
|
||||||
|
])
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!systemStatsStore.systemStats) {
|
||||||
|
await systemStatsStore.fetchSystemStats()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -3,16 +3,10 @@
|
|||||||
<h2>
|
<h2>
|
||||||
<i class="pi pi-cog"></i>
|
<i class="pi pi-cog"></i>
|
||||||
<span>{{ $t('settings') }}</span>
|
<span>{{ $t('settings') }}</span>
|
||||||
<Tag :value="frontendVersion" severity="secondary" class="version-tag" />
|
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import Tag from 'primevue/tag'
|
|
||||||
const frontendVersion = 'v' + window['__COMFYUI_FRONTEND_VERSION__']
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.pi-cog {
|
.pi-cog {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ import { createI18n } from 'vue-i18n'
|
|||||||
|
|
||||||
const messages = {
|
const messages = {
|
||||||
en: {
|
en: {
|
||||||
|
systemInfo: 'System Info',
|
||||||
|
devices: 'Devices',
|
||||||
|
about: 'About',
|
||||||
add: 'Add',
|
add: 'Add',
|
||||||
confirm: 'Confirm',
|
confirm: 'Confirm',
|
||||||
reset: 'Reset',
|
reset: 'Reset',
|
||||||
|
|||||||
34
src/stores/systemStatsStore.ts
Normal file
34
src/stores/systemStatsStore.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { api } from '@/scripts/api'
|
||||||
|
import type { SystemStats } from '@/types/apiTypes'
|
||||||
|
|
||||||
|
export const useSystemStatsStore = defineStore('systemStats', () => {
|
||||||
|
const systemStats = ref<SystemStats | null>(null)
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const error = ref<string | null>(null)
|
||||||
|
|
||||||
|
async function fetchSystemStats() {
|
||||||
|
isLoading.value = true
|
||||||
|
error.value = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
systemStats.value = await api.getSystemStats()
|
||||||
|
} catch (err) {
|
||||||
|
error.value =
|
||||||
|
err instanceof Error
|
||||||
|
? err.message
|
||||||
|
: 'An error occurred while fetching system stats'
|
||||||
|
console.error('Error fetching system stats:', err)
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
systemStats,
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
fetchSystemStats
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -387,6 +387,17 @@ const zPromptResponse = z.object({
|
|||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const zDeviceStats = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
index: z.number(),
|
||||||
|
vram_total: z.number(),
|
||||||
|
vram_free: z.number(),
|
||||||
|
torch_vram_total: z.number(),
|
||||||
|
torch_vram_free: z.number()
|
||||||
|
})
|
||||||
|
|
||||||
export const zSystemStats = z.object({
|
export const zSystemStats = z.object({
|
||||||
system: z.object({
|
system: z.object({
|
||||||
os: z.string(),
|
os: z.string(),
|
||||||
@@ -396,17 +407,7 @@ export const zSystemStats = z.object({
|
|||||||
pytorch_version: z.string(),
|
pytorch_version: z.string(),
|
||||||
argv: z.array(z.string())
|
argv: z.array(z.string())
|
||||||
}),
|
}),
|
||||||
devices: z.array(
|
devices: z.array(zDeviceStats)
|
||||||
z.object({
|
|
||||||
name: z.string(),
|
|
||||||
type: z.string(),
|
|
||||||
index: z.number().optional(),
|
|
||||||
vram_total: z.number(),
|
|
||||||
vram_free: z.number(),
|
|
||||||
torch_vram_total: z.number(),
|
|
||||||
torch_vram_free: z.number()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
const zUser = z.object({
|
const zUser = z.object({
|
||||||
storage: z.enum(['server', 'browser']),
|
storage: z.enum(['server', 'browser']),
|
||||||
@@ -500,6 +501,7 @@ export type EmbeddingsResponse = z.infer<typeof zEmbeddingsResponse>
|
|||||||
export type ExtensionsResponse = z.infer<typeof zExtensionsResponse>
|
export type ExtensionsResponse = z.infer<typeof zExtensionsResponse>
|
||||||
export type PromptResponse = z.infer<typeof zPromptResponse>
|
export type PromptResponse = z.infer<typeof zPromptResponse>
|
||||||
export type Settings = z.infer<typeof zSettings>
|
export type Settings = z.infer<typeof zSettings>
|
||||||
|
export type DeviceStats = z.infer<typeof zDeviceStats>
|
||||||
export type SystemStats = z.infer<typeof zSystemStats>
|
export type SystemStats = z.infer<typeof zSystemStats>
|
||||||
export type User = z.infer<typeof zUser>
|
export type User = z.infer<typeof zUser>
|
||||||
export type UserData = z.infer<typeof zUserData>
|
export type UserData = z.infer<typeof zUserData>
|
||||||
|
|||||||
Reference in New Issue
Block a user