diff --git a/vue/components.d.ts b/vue/components.d.ts index 4f8b7bf..c3196ef 100644 --- a/vue/components.d.ts +++ b/vue/components.d.ts @@ -14,12 +14,15 @@ declare module '@vue/runtime-core' { ABreadcrumbItem: typeof import('ant-design-vue/es')['BreadcrumbItem'] AButton: typeof import('ant-design-vue/es')['Button'] ACheckbox: typeof import('ant-design-vue/es')['Checkbox'] + ACollapse: typeof import('ant-design-vue/es')['Collapse'] + ACollapsePanel: typeof import('ant-design-vue/es')['CollapsePanel'] ADrawer: typeof import('ant-design-vue/es')['Drawer'] ADropdown: typeof import('ant-design-vue/es')['Dropdown'] AForm: typeof import('ant-design-vue/es')['Form'] AFormItem: typeof import('ant-design-vue/es')['FormItem'] AImage: typeof import('ant-design-vue/es')['Image'] AInput: typeof import('ant-design-vue/es')['Input'] + AInputGroup: typeof import('ant-design-vue/es')['InputGroup'] AInputNumber: typeof import('ant-design-vue/es')['InputNumber'] AMenu: typeof import('ant-design-vue/es')['Menu'] AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider'] @@ -36,8 +39,10 @@ declare module '@vue/runtime-core' { ATabPane: typeof import('ant-design-vue/es')['TabPane'] ATabs: typeof import('ant-design-vue/es')['Tabs'] ATag: typeof import('ant-design-vue/es')['Tag'] + ATextarea: typeof import('ant-design-vue/es')['Textarea'] ATooltip: typeof import('ant-design-vue/es')['Tooltip'] BaseFileListInfo: typeof import('./src/components/BaseFileListInfo.vue')['default'] + ChangeIndicator: typeof import('./src/components/ChangeIndicator.vue')['default'] ContextMenu: typeof import('./src/components/ContextMenu.vue')['default'] FileItem: typeof import('./src/components/FileItem.vue')['default'] NumInput: typeof import('./src/components/numInput.vue')['default'] diff --git a/vue/src/api/files.ts b/vue/src/api/files.ts index c1dbb5b..0ebc18c 100644 --- a/vue/src/api/files.ts +++ b/vue/src/api/files.ts @@ -9,6 +9,15 @@ export interface FileNodeInfo { bytes: number fullpath: string is_under_scanned_path: boolean + gen_info_raw?: string + gen_info_obj?: object +} + +export interface GenDiffInfo { + empty: boolean + ownFile: string + otherFile: string + diff: object } export const getTargetFolderFiles = async (folder_path: string) => { diff --git a/vue/src/components/ChangeIndicator.vue b/vue/src/components/ChangeIndicator.vue new file mode 100644 index 0000000..6d90129 --- /dev/null +++ b/vue/src/components/ChangeIndicator.vue @@ -0,0 +1,301 @@ + + + + + \ No newline at end of file diff --git a/vue/src/components/FileItem.vue b/vue/src/components/FileItem.vue index 173609a..f0f1363 100644 --- a/vue/src/components/FileItem.vue +++ b/vue/src/components/FileItem.vue @@ -8,10 +8,12 @@ import { toImageThumbnailUrl, toRawFileUrl } from '@/util/file' import type { MenuInfo } from 'ant-design-vue/lib/menu/src/interface' import { computed } from 'vue' import ContextMenu from './ContextMenu.vue' +import ChangeIndicator from './ChangeIndicator.vue' import { useTagStore } from '@/store/useTagStore' import { CloseCircleOutlined, StarFilled, StarOutlined, PlayCircleFilled } from '@/icon' import { Tag } from '@/api/db' import { openVideoModal } from './functionalCallableComp' +import type { GenDiffInfo } from '@/api/files' const global = useGlobalStore() const tagStore = useTagStore() @@ -26,6 +28,10 @@ const props = withDefaults( enableRightClickMenu?: boolean, enableCloseIcon?: boolean isSelectedMutilFiles?: boolean + genDiffToPrevious: GenDiffInfo + genDiffToNext: GenDiffInfo + genInfo?: string + enableChangeIndicator: boolean }>(), { selected: false, enableRightClickMenu: true, enableCloseIcon: false } ) @@ -105,6 +111,10 @@ const taggleLikeTag = () => { 这么复杂是因为再全屏查看时可能因为直接删除导致fullpath变化,然后整个预览直接退出-->
+ + + + + .center { display: flex; justify-content: center; @@ -322,5 +333,4 @@ const taggleLikeTag = () => { flex-direction: column; align-items: flex-end; } -} - +} diff --git a/vue/src/i18n/en.ts b/vue/src/i18n/en.ts index eb32721..3cab0a1 100644 --- a/vue/src/i18n/en.ts +++ b/vue/src/i18n/en.ts @@ -38,6 +38,8 @@ export const en: IIBI18nMap = { other: 'Other', livePreview: 'Live Preview', gridCellWidth: 'Grid Cell Width (px)', + showChangeIndicators: 'Show Change Indicators', + seedAsChange: 'Seed as Change', defaultGridCellWidth: 'Default Grid Cell Width (px)', thumbnailResolution: 'Thumbnail Resolution (px)', inputTargetFolderPath: 'Enter the absolute path of the target folder', diff --git a/vue/src/page/fileTransfer/stackView.vue b/vue/src/page/fileTransfer/stackView.vue index 8648360..a71f9b7 100644 --- a/vue/src/page/fileTransfer/stackView.vue +++ b/vue/src/page/fileTransfer/stackView.vue @@ -23,9 +23,12 @@ import FileItem from '@/components/FileItem.vue' import fullScreenContextMenu from './fullScreenContextMenu.vue' import BaseFileListInfo from '@/components/BaseFileListInfo.vue' import { copy2clipboardI18n } from '@/util' -import { openFolder } from '@/api' +import { openFolder, getImageGenerationInfo } from '@/api' import { sortMethods } from './fileSort' import { isTauri } from '@/util/env' +import { parse } from 'stable-diffusion-image-metadata' +import { ref } from 'vue' +import type { GenDiffInfo } from '@/api/files' const global = useGlobalStore() const props = defineProps<{ @@ -83,7 +86,99 @@ watch( { immediate: true } ) +const changeIndchecked = ref(true); +const seedChangeChecked = ref(false); +function getRawGenParams() { + for (let f in sortedFiles.value) { + if (sortedFiles.value[f].gen_info_raw) { + continue + } + let path = sortedFiles.value[f].fullpath + q.pushAction(() => getImageGenerationInfo(path)).res.then((v) => { + sortedFiles.value[f].gen_info_raw = v + sortedFiles.value[f].gen_info_obj = parse(v) + }) + } +} + +function getGenDiff(ownGenInfo: any, idx: any, increment: any, ownFileName?: any) { + //init result obj + let result: GenDiffInfo = { + diff: {}, + empty: true, + ownFile: "", + otherFile: "" + } + + //check for out of bounds + if(idx + increment < 0 + || idx + increment >= sortedFiles.value.length + || sortedFiles.value[idx] == undefined) { + return result + } + //check for gen_info_obj existence + if(!("gen_info_obj" in sortedFiles.value[idx]) + || !("gen_info_obj" in sortedFiles.value[idx + increment])) { + return result + } + + //diff vars init + let gen_a = ownGenInfo + let gen_b = sortedFiles.value[idx + increment].gen_info_obj + if(gen_b == undefined) { + return result + } + + //further vars + let skip = ["hashes", "resources"] + result.diff = {} + result.ownFile = ownFileName, + result.otherFile = sortedFiles.value[idx + increment].name, + result.empty = false + + if(!seedChangeChecked.value) { + skip.push("seed") + } + + //actual per property diff + for (let k in gen_a) { + //skip unwanted values + if (skip.includes(k)) { + continue + } + //for all non-identical values, compare type based + //existence test + if (!(k in gen_b)) { + result.diff[k] = "+" + continue + } + //content test + if (gen_a[k] != gen_b[k]) { + if(k.includes("rompt") && gen_a[k] != "" && gen_b[k] != "") { + //prompt values are comma separated, handle them differently + let tokenize_a = gen_a[k].split(",") + let tokenize_b = gen_b[k].split(",") + //count how many tokens are different or at a different place + let diff_count = 0 + for (let i in tokenize_a) { + if(tokenize_a[i] != tokenize_b[i]) { + diff_count++ + } + } + result.diff[k] = diff_count; + } else { + //all others + result.diff[k] = [gen_a[k],gen_b[k]] + } + } + } + + //result + return result +} + +getRawGenParams();