Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/func bar #183

Merged
merged 7 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions swanlab/server/api/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ async def get_experiment(experiment_id: int):
path = os.path.join(SWANLOG_DIR, experiment["name"], "logs")
experiment["tags"] = __list_subdirectories(path)
experiment["default_color"] = DEFAULT_COLOR
# COMPAT
return SUCCESS_200(experiment)


Expand Down
2 changes: 1 addition & 1 deletion vue/src/assets/iconfont.js

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions vue/src/components/SLButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,18 @@ button {
}

// ----------------------- hollow 样式 -----------------------
.sa-button-default-hollow {
@apply text-default border-default bg-default transition-all;

&:hover {
@apply border-primary-dimmer;
}

&:active {
@apply border-primary-default;
}
}

.sa-button-negative-hollow {
@apply text-negative-default border-negative-default bg-default transition-all;

Expand Down
5 changes: 4 additions & 1 deletion vue/src/components/SLCopy.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<template>
<Tippy trigger="click">
<template v-slot="{ hide }">
<SLIcon icon="copy" :class="iconClass" @click="copy(hide)" />
<div class="flex gap-2 items-center">
<SLIcon icon="copy" :class="iconClass" @click="copy(hide)" />
<slot></slot>
</div>
</template>
<template #content> {{ $t('common.copy') }}</template>
</Tippy>
Expand Down
2 changes: 1 addition & 1 deletion vue/src/components/SLSearch.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<div class="w-full border flex gap-2 justify-between items-center py-2 px-2 rounded-lg">
<SLIcon class="w-4 h-4" icon="search"></SLIcon>
<SLIcon class="w-4 h-4 shrink-0" icon="search"></SLIcon>
<input
type="text"
v-model="value"
Expand Down
5 changes: 5 additions & 0 deletions vue/src/i18n/en-US/experiment.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,10 @@
"empty": {
"requirements": "There are no dependencies"
}
},
"func-bar": {
"copy": "Copy",
"download": "Download",
"copy-success": "Copied"
}
}
5 changes: 5 additions & 0 deletions vue/src/i18n/zh-CN/experiment.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,10 @@
"empty": {
"requirements": "There are no dependencies"
}
},
"func-bar": {
"copy": "Copy",
"download": "Download",
"copy-success": "Copied"
}
}
18 changes: 18 additions & 0 deletions vue/src/utils/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,21 @@ export const addTaskToBrowserMainThread = (task) => {
export const uuid = (now = Math.round(new Date() / 1000)) => {
return now.toString(36) + Math.random().toString(36).slice(-4)
}

/**
* 将目标字符串下载成文件
* @param {string} content 下载的内容
* @param {string} fileName 文件名
*/
export const downloadStringAsFile = (content, fileName) => {
// 创建一个包含字符串内容的 Blob 对象
const blob = new Blob([content], { type: 'text/plain' })

// 创建一个下载链接
const downloadLink = document.createElement('a')

// 设置链接的属性,包括下载文件的名称
downloadLink.href = window.URL.createObjectURL(blob)
downloadLink.download = fileName
downloadLink.click()
}
69 changes: 69 additions & 0 deletions vue/src/views/experiment/components/FuncBar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<template>
<div class="w-full flex gap-5 justify-between">
<SLSearch @input="input" class="max-w-[400px]" />
<div class="flex gap-3">
<SLButton hollow @click="copy">
<SLIcon icon="copy" class="icon"></SLIcon>
{{ $t('experiment.func-bar.copy') }}
</SLButton>
<SLButton hollow @click="download">
<SLIcon icon="download" class="icon"></SLIcon>
{{ $t('experiment.func-bar.download') }}
</SLButton>
</div>
</div>
</template>

<script setup>
/**
* @description: 功能条:搜索、复制、下载
* @file: FuncBar.vue
* @since: 2024-01-10 12:54:19
**/

import SLSearch from '@swanlab-vue/components/SLSearch.vue'
import { message } from '@swanlab-vue/components/message'
import { t } from '@swanlab-vue/i18n'
import { copyTextToClipboard, downloadStringAsFile } from '@swanlab-vue/utils/browser'

const props = defineProps({
// 复制、下载的内容
content: {
type: String,
default: ''
},
// 下载时使用的文件名
filename: {
type: String,
default: 'swanlab.file'
}
})

const emits = defineEmits(['input', 'download'])

// 输入触发
const input = (value) => {
emits('input', value)
}

// 复制
const copy = () => {
copyTextToClipboard(props.content)
message.success(t('experiment.func-bar.copy-success'))
}

// 下载
const download = () => {
downloadStringAsFile(props.content, props.filename)
}
</script>

<style lang="scss" scoped>
button {
@apply rounded-lg px-3 py-1 flex items-center gap-2;
}

.icon {
@apply w-4 h-4;
}
</style>
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
<template>
<div class="w-full">
<div class="px-6 py-4 bg-higher rounded" v-if="dependencies && dependencies.length !== 0">
<p v-for="line in dependencies" :key="line">{{ line }}</p>
</div>
<template v-if="dependencies && dependencies.length !== 0">
<!-- 搜索、复制、下载 -->
<FuncBar class="pb-6 py-4" @input="search" :content="dependencies?.join('\n')" :filename="filename" />
<!-- 如果有依赖项 -->
<div class="px-6 py-4 bg-higher rounded">
<p v-for="line in lines" :key="line">
<span v-show="!line.isTarget">{{ line.value }}</span>
<span v-show="line.isTarget">
<span
v-for="substring in line.value"
:key="substring"
:class="substring.toLowerCase() === searchValue ? ' bg-warning-dimmest' : ''"
>
{{ substring }}
</span>
</span>
</p>
</div>
</template>
<!-- 没有依赖项时占位 -->
<div class="w-full text-center pt-10" v-else>
{{ $t('experiment.env.empty.requirements') }}
</div>
Expand All @@ -16,10 +33,50 @@
* @since: 2024-01-09 16:01:55
**/

import { ref, computed } from 'vue'
import { useExperimentStroe } from '@swanlab-vue/store'
import FuncBar from '@swanlab-vue/views/experiment/components/FuncBar.vue'

const experimentStore = useExperimentStroe()
const dependencies = experimentStore.experiment.system?.dependencies

// ---------------------------------- 搜索 ----------------------------------

// 查找字符
const searchValue = ref('')

const search = (value) => {
searchValue.value = value.toLowerCase().trim()
}

// 对依赖的每一行进行一些特殊处理
const lines = computed(() => {
return dependencies.map((line) => {
// 查找内容不为空,并且该行含有查找内容,说明是目标行
const isTarget = searchValue.value !== '' && line.toLowerCase().includes(searchValue.value)

return {
isTarget,
value: isTarget ? splitStringBySearch(line, searchValue.value) : line
}
})
})

// 以查找字符串作为分割点,将一行字符串分割成数组,且忽略大小写
function splitStringBySearch(target, substring) {
// 使用正则表达式进行大小写不敏感的分割,并保留分割点
let resultArray = target.split(new RegExp(`(${substring})`, 'i'))

// 去除数组中的空字符串
resultArray = resultArray.filter((item) => item !== '')

// 返回包含分割点的数组
return resultArray
}

// ---------------------------------- 下载成文件 ----------------------------------

const filename = 'requirements.txt'
</script>

<style lang="scss" scoped></style>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<ExtendBlock class="pt-2 pr-5" icon="config" :title="$t('experiment.index.config.title')" :retract="false">
<div class="pr-5" icon="config" :title="$t('experiment.index.config.title')" :retract="false">
<div class="pl-6 w-full grid lg:grid-cols-2 lg:gap-10">
<div>
<div class="pt-4">
<div class="flex items-center pb-4">
<p class="font-semibold pr-2">{{ $t('experiment.index.config.detail') }}</p>
<SLHelp document="https://geektechstudio.feishu.cn/wiki/EFi3wuACGiEWlLki5aDcQiSpngg">{{
Expand All @@ -10,7 +10,7 @@
</div>
<SLTable :column="column" :data="configs" flexable />
</div>
<div v-if="summaries?.length !== 0">
<div class="pt-4" v-if="summaries?.length !== 0">
<div class="flex items-center pb-4" v-if="summaries?.length !== 0">
<p class="font-semibold pr-2">{{ $t('experiment.index.config.summarize') }}</p>
<SLHelp document="https://geektechstudio.feishu.cn/wiki/TudNwOSMyihFetky7l5cTI8UnJf"
Expand All @@ -21,7 +21,7 @@
</div>
</div>
<div class="w-full h-6"></div>
</ExtendBlock>
</div>
</template>

<script setup>
Expand All @@ -30,7 +30,7 @@
* @file: ExperimentConfig.vue
* @since: 2023-12-11 17:07:31
**/
import ExtendBlock from '@swanlab-vue/views/experiment/components/ExtendBlock.vue'

import SLTable from '@swanlab-vue/components/table'
import SLHelp from '@swanlab-vue/components/SLHelp.vue'
import http from '@swanlab-vue/api/http'
Expand Down
58 changes: 36 additions & 22 deletions vue/src/views/experiment/pages/log/LogPage.vue
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
<template>
<div class="w-full h-full px-7 py-6 relative overflow-hidden">
<SLSearch class="max-w-[400px] mb-5" @input="search" dealy="300" />
<FuncBar class="pb-6" @input="search" :content="logs?.join('\n')" :filename="filename" v-if="logs" />
<section class="log-container">
<div class="log-area" ref="logAreaRef" v-if="logs">
<!-- 运行日志 -->
<div class="log-line" v-for="line in filteredLogs" :key="line">
<div class="log-line" v-for="line in lines" :key="line">
<!-- 行号 -->
<span class="w-8 text-right flex-shrink-0 text-dimmest select-none">{{ line.index }}</span>
<!-- 日志内容 -->
<!-- 如果没有搜索内容/不含搜索内容 -->
<span v-show="!searchValue || !line.lower.includes(searchValue)">{{ line.value }} </span>
<span v-show="!line.isTarget">{{ line.value }} </span>
<!-- 如果有搜索内容且含有搜索内容 -->
<p class="flex" v-show="searchValue && line.lower.includes(searchValue)">
<span>{{ line.value.substring(0, line.lower.indexOf(searchValue)) }}</span>
<!-- 高亮展示 -->
<span class="bg-warning-dimmest">{{
line.value.substring(
line.lower.indexOf(searchValue),
line.lower.indexOf(searchValue) + searchValue.length
)
}}</span>
<span>{{ line.value.substring(line.lower.indexOf(searchValue) + searchValue.length) }}</span>
</p>
<span v-show="line.isTarget">
<span
v-for="substring in line.value"
:key="substring"
:class="substring.toLowerCase() === searchValue ? ' bg-warning-dimmest' : ''"
>
{{ substring }}
</span>
</span>
</div>
<!-- 错误日志 -->
<div class="log-line text-negative-default" v-for="(line, index) in errorLogs" :key="line">
Expand Down Expand Up @@ -50,7 +48,7 @@ import http from '@swanlab-vue/api/http'
import { useExperimentStroe } from '@swanlab-vue/store'
import { addTaskToBrowserMainThread } from '@swanlab-vue/utils/browser'
import SLLoding from '@swanlab-vue/components/SLLoading.vue'
import SLSearch from '@swanlab-vue/components/SLSearch.vue'
import FuncBar from '../../components/FuncBar.vue'
import { computed } from 'vue'

const logAreaRef = ref()
Expand All @@ -66,16 +64,31 @@ const id = experimentStore.id
const logs = ref()

// 分开行号和内容之后的日志
const filteredLogs = computed(() => {
const lines = computed(() => {
return logs.value.map((line) => {
const index = line.substring(0, line.indexOf(' '))
const content = line.substring(line.indexOf(' '))
const isTarget = searchValue.value !== '' && content.toLowerCase().includes(searchValue.value)

return {
index: line.substring(0, line.indexOf(' ')),
value: line.substring(line.indexOf(' ')),
lower: line.substring(line.indexOf(' ')).toLowerCase()
isTarget,
index,
value: isTarget ? splitStringBySearch(content, searchValue.value) : content
}
})
})

function splitStringBySearch(target, substring) {
// 使用正则表达式进行大小写不敏感的分割,并保留分割点
let resultArray = target.split(new RegExp(`(${substring})`, 'i'))

// 去除数组中的空字符串
resultArray = resultArray.filter((item) => item !== '')

// 返回包含分割点的数组
return resultArray
}

// 错误日志
const errorLogs = ref([])
;(async function () {
Expand All @@ -97,6 +110,10 @@ const searchValue = ref('')
const search = (value) => {
searchValue.value = value.toLowerCase()
}

// ---------------------------------- 下载 ----------------------------------

const filename = 'print.log'
</script>

<style lang="scss" scoped>
Expand All @@ -117,9 +134,6 @@ const search = (value) => {

.log-line {
@apply flex gap-2 whitespace-pre-wrap;
span {
@apply block;
}
}
}
</style>