diff --git a/.vscode/launch.json b/.vscode/launch.json index 0961acd46..1d151b1fe 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -25,15 +25,17 @@ //sys.path 会加入顶层目录,影响模块导入查询路径 "env": { "PYTHONPATH": "${workspaceFolder}" } }, - // 打包命令 + // 开发调试 { - "name": "构建项目", + "name": "开发调试", "type": "debugpy", "request": "launch", - "program": "${workspaceFolder}/build_pypi.py", + "program": "${workspaceFolder}/test/test_create_experiment.py", "console": "integratedTerminal", "justMyCode": true, - "cwd": "${workspaceFolder}" + "cwd": "${workspaceFolder}", + //sys.path 会加入顶层目录,影响模块导入查询路径 + "env": { "PYTHONPATH": "${workspaceFolder}" } }, // 模拟实验开启 { @@ -69,6 +71,16 @@ "cwd": "${workspaceFolder}", //sys.path 会加入顶层目录,影响模块导入查询路径 "env": { "PYTHONPATH": "${workspaceFolder}" } + }, + // 打包命令 + { + "name": "构建项目", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/build_pypi.py", + "console": "integratedTerminal", + "justMyCode": true, + "cwd": "${workspaceFolder}" } ] } diff --git a/swanlab/__init__.py b/swanlab/__init__.py index b2bbe6a13..5b55d2425 100755 --- a/swanlab/__init__.py +++ b/swanlab/__init__.py @@ -6,6 +6,7 @@ config, Audio, Image, + Text, Run, ) diff --git a/swanlab/data/modules/__init__.py b/swanlab/data/modules/__init__.py index 63eb8a113..22f447eae 100644 --- a/swanlab/data/modules/__init__.py +++ b/swanlab/data/modules/__init__.py @@ -9,7 +9,8 @@ class FloatConvertible(Protocol): - def __float__(self) -> float: ... + def __float__(self) -> float: + ... DataType = Union[float, FloatConvertible, int, BaseType] diff --git a/swanlab/data/modules/text.py b/swanlab/data/modules/text.py index 9bac75bee..435effbf5 100644 --- a/swanlab/data/modules/text.py +++ b/swanlab/data/modules/text.py @@ -2,6 +2,7 @@ from .base import BaseType import os from typing import Union, List +from ..utils.file import get_text_sha256_hash class Text(BaseType): @@ -25,25 +26,29 @@ def get_data(self): # 如果传入的是Text类列表 if isinstance(self.value, list): return self.get_data_list() - else: - # 预处理文本数据 - self.__preprocess(self.value) - # 设置文本数据的保存路径 - save_dir = os.path.join(self.settings.static_dir, self.tag) - save_name = ( - f"{self.caption}-step{self.step}-{self.text_data[:16]}.txt" - if self.caption is not None - else f"text-step{self.step}-{self.text_data[:16]}.txt" - ) - # 如果路径不存在,则创建路径 - if not os.path.exists(save_dir): - os.mkdir(save_dir) - save_path = os.path.join(save_dir, save_name) - - # 保存文本数据写入到指定目录的指定json文件 - self.__save(save_path) - return save_name + # 预处理文本数据 + self.__preprocess(self.value) + + # 获取文本的hash值 + hash_name = get_text_sha256_hash(self.text_data)[:16] + + # 设置文本数据的保存路径 + save_dir = os.path.join(self.settings.static_dir, self.tag) + save_name = ( + f"{self.caption}-step{self.step}-{hash_name}.txt" + if self.caption is not None + else f"text-step{self.step}-{hash_name}.txt" + ) + + # 如果路径不存在,则创建路径 + if not os.path.exists(save_dir): + os.mkdir(save_dir) + save_path = os.path.join(save_dir, save_name) + + # 保存文本数据写入到指定目录的指定json文件 + self.__save(save_path) + return save_name def expect_types(self, *args, **kwargs) -> list: return ["str", "int", "float"] diff --git a/swanlab/data/utils/file.py b/swanlab/data/utils/file.py index 0903ddefd..1245a3d0a 100644 --- a/swanlab/data/utils/file.py +++ b/swanlab/data/utils/file.py @@ -109,3 +109,16 @@ def get_file_hash_pil(image) -> str: image.save(buffer, format="PNG") # 可以选择其他格式,如'JPEG' hash_sha256.update(buffer.getvalue()) return hash_sha256.hexdigest() + + +def get_text_sha256_hash(text): + """计算并返回给定文本的SHA-256哈希值。""" + import hashlib + + # 使用hashlib库创建一个sha256哈希对象 + hash_object = hashlib.sha256() + # 对输入的文本进行编码,因为hashlib需要的是字节对象 + hash_object.update(text.encode()) + # 获取十六进制格式的哈希值 + hex_dig = hash_object.hexdigest() + return hex_dig diff --git a/test/create_experiment.py b/test/create_experiment.py index f3c1f67b3..40dc2feaf 100644 --- a/test/create_experiment.py +++ b/test/create_experiment.py @@ -34,6 +34,7 @@ swanlab.log( { "test/image": swanlab.Image(test_image, caption="test"), + "test/text": swanlab.Text("hello swanlab!", caption="swanlab official"), }, step=epoch, ) diff --git a/vue/src/charts/components/ChartContainer.vue b/vue/src/charts/components/ChartContainer.vue index 56f86c1ce..ff59f4acd 100644 --- a/vue/src/charts/components/ChartContainer.vue +++ b/vue/src/charts/components/ChartContainer.vue @@ -38,6 +38,7 @@ import SLIcon from '@swanlab-vue/components/SLIcon.vue' import { addTaskToBrowserMainThread } from '@swanlab-vue/utils/browser' import LineChart from '../package/LineChart.vue' import AudioChart from '../package/AudioChart.vue' +import TextChart from '../package/TextChart.vue' import UnknownChart from '../package/UnknownChart.vue' import PannelButton from './PannelButton.vue' import { debounce } from '@swanlab-vue/utils/common' @@ -98,6 +99,11 @@ const chartComponent = computed(() => { type: ImageChart, class: 'image-chart' } + case 'text': + return { + type: TextChart, + class: 'text-chart' + } default: return { type: UnknownChart @@ -215,7 +221,8 @@ defineExpose({ // ---------------------------------- 图表样式 ---------------------------------- .audio-chart, -.image-chart { +.image-chart, +.text-chart { @apply col-span-3; } diff --git a/vue/src/charts/components/SlideBar.vue b/vue/src/charts/components/SlideBar.vue index da48a3b77..0cf27c358 100644 --- a/vue/src/charts/components/SlideBar.vue +++ b/vue/src/charts/components/SlideBar.vue @@ -7,14 +7,9 @@ -
- -
+
+ +
@@ -28,7 +23,7 @@ * @file: SlideBar.vue * @since: 2024-01-30 16:18:31 **/ -import { computed } from 'vue' +import { computed, ref } from 'vue' const props = defineProps({ max: { @@ -55,6 +50,8 @@ const props = defineProps({ const emits = defineEmits(['update:modelValue', 'change', 'turn']) +const input = ref(null) + const _modelValue = computed({ get() { return props.modelValue @@ -67,6 +64,7 @@ const _modelValue = computed({ } emits('update:modelValue', value) emits('change', value) + input.value.value = _modelValue.value } }) @@ -91,8 +89,10 @@ const handleClickUp = () => { } // ---------------------------------- 当input输入结束时,再次赋值 ---------------------------------- + const handleChange = (e) => { - e.target.value = _modelValue.value + if (e.target.value == _modelValue.value) return + _modelValue.value = e.target.value } @@ -112,4 +112,8 @@ const handleChange = (e) => { appearance: textfield; } } + +input { + @apply w-16 h-6 pl-1 pr-5 rounded border outline-none bg-transparent text-xs focus:border-primary-default; +} diff --git a/vue/src/charts/modules/TextDetail.vue b/vue/src/charts/modules/TextDetail.vue new file mode 100644 index 000000000..1dd1d6439 --- /dev/null +++ b/vue/src/charts/modules/TextDetail.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/vue/src/charts/modules/TextModule.vue b/vue/src/charts/modules/TextModule.vue new file mode 100644 index 000000000..689f70ae4 --- /dev/null +++ b/vue/src/charts/modules/TextModule.vue @@ -0,0 +1,280 @@ + + + + + diff --git a/vue/src/charts/package/TextChart.vue b/vue/src/charts/package/TextChart.vue new file mode 100644 index 000000000..359374167 --- /dev/null +++ b/vue/src/charts/package/TextChart.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/vue/src/i18n/en-US/common.json b/vue/src/i18n/en-US/common.json index 93c8c4480..956ff0459 100644 --- a/vue/src/i18n/en-US/common.json +++ b/vue/src/i18n/en-US/common.json @@ -82,5 +82,34 @@ }, "table": { "empty": "Empty Table" + }, + "chart": { + "empty": "Empty Experiment with No Charts", + "error": "This chart is not displayed correctly.", + "unkonwn": "Unknown chart type: {type}", + "label": { + "default": "Charts" + }, + "zoom": "Zoom In", + "charts": { + "line": { + "error": "The Chart cannot be displayed because the value of \"{tag}\" is type {type}, and Line Chart do not support displaying type {type}." + }, + "audio": { + "error": "The Chart cannot be displayed because the value of \"{tag}\" is type {type}, and Audio Chart do not support displaying type {type}." + }, + "image": { + "error": "The Chart cannot be displayed because the value of \"{tag}\" is type {type}, and Image Chart do not support displaying type {type}." + } + }, + "text-chart": { + "titles": { + "tag": "tag", + "step": "step", + "caption": "caption", + "count": "number of words", + "text": "Text" + } + } } } diff --git a/vue/src/i18n/zh-CN/common.json b/vue/src/i18n/zh-CN/common.json index 77a03007b..a0c6bc308 100644 --- a/vue/src/i18n/zh-CN/common.json +++ b/vue/src/i18n/zh-CN/common.json @@ -82,5 +82,34 @@ }, "table": { "empty": "暂无数据" + }, + "chart": { + "empty": "缺少实验数据", + "error": "此图表无法正确显示", + "unkonwn": "未知的图表类型: {type}", + "label": { + "default": "图表" + }, + "zoom": "放大展示", + "charts": { + "line": { + "error": "图表无法被显示,因为\"{tag}\"是 {type} 类型, 但是折线图并不支持此类型." + }, + "audio": { + "error": "图表无法被显示,因为\"{tag}\"是 {type} 类型, 但是音频图并不支持此类型." + }, + "image": { + "error": "图表无法被显示,因为\"{tag}\"是 {type} 类型, 但是图片图并不支持此类型." + } + }, + "text-chart": { + "titles": { + "tag": "tag", + "step": "step", + "caption": "caption", + "count": "number of words", + "text": "Text" + } + } } } diff --git a/vue/src/views/experiment/pages/chart/ChartPage.vue b/vue/src/views/experiment/pages/chart/ChartPage.vue index 6b73016b7..fc19ea7d8 100644 --- a/vue/src/views/experiment/pages/chart/ChartPage.vue +++ b/vue/src/views/experiment/pages/chart/ChartPage.vue @@ -1,6 +1,6 @@