feat: update AutoProbeResultsContainer and related interfaces for improved preview functionality

- Refactored AutoProbeResultsContainer to utilize new previewResult structure and updated API calls.
- Enhanced element styling and dynamic behavior for page elements in the preview.
- Introduced getIconByPageElementType utility for better icon management based on element type.
- Updated AutoProbe interfaces to reflect changes in data structure, including renaming PagePreview to PagePreviewResult and modifying page_elements type.
- Added rowKey functionality in AutoProbeList for improved table data handling.
This commit is contained in:
Marvin Zhang
2025-05-18 23:03:02 +08:00
parent 7afc4ba4c9
commit f1f74d8a6a
5 changed files with 62 additions and 41 deletions

View File

@@ -1,8 +1,8 @@
<script setup lang="tsx">
import { computed, ref } from 'vue';
import { computed, CSSProperties, ref } from 'vue';
import { CellStyle } from 'element-plus';
import { ClTag } from '@/components';
import { translate, getIconByItemType } from '@/utils';
import { translate, getIconByItemType, getIconByPageElementType } from '@/utils';
import { TAB_NAME_RESULTS, TAB_NAME_PREVIEW } from '@/constants';
import useRequest from '@/services/request';
@@ -22,17 +22,15 @@ const emit = defineEmits<{
(e: 'size-change', size: number): void;
}>();
const { post } = useRequest();
const { get } = useRequest();
// Refs
const resultsContainerRef = ref<HTMLElement | null>(null);
const iframeRef = ref<HTMLIFrameElement | null>(null);
const iframeLoading = ref(true);
const previewRef = ref<HTMLDivElement | null>(null);
const previewLoading = ref(false);
const pagePreview = ref<PagePreview>();
const previewResult = ref<PagePreviewResult>();
const overlayRef = ref<HTMLDivElement | null>(null);
const scrollPosition = ref({ top: 0, left: 0 });
// States
const activeTabName = ref<string | undefined>(TAB_NAME_RESULTS);
@@ -114,13 +112,6 @@ const toggleResults = () => {
}
};
const onIframeLoad = () => {
iframeLoading.value = false;
iframeRef.value?.addEventListener('focus', event => {
console.debug(event);
});
};
const getPreview = async () => {
const { activeId } = props;
const rect = previewRef.value?.getBoundingClientRect();
@@ -132,18 +123,34 @@ const getPreview = async () => {
: undefined;
previewLoading.value = true;
try {
const res = await post<any, ResponseWithData<PagePreview>>(
const res = await get<any, ResponseWithData<PagePreviewResult>>(
`/ai/autoprobes/${activeId}/preview`,
{
viewport,
}
);
pagePreview.value = res.data;
previewResult.value = res.data;
} finally {
previewLoading.value = false;
}
};
const overlayScale = computed(() => {
const rect = overlayRef.value?.getBoundingClientRect();
if (!rect) return 1;
return rect.width / 1280; // TODO: Adjust based on the actual viewport size
});
const getElementMaskStyle = (el: PageElement): CSSProperties => {
return {
position: 'absolute',
left: el.coordinates.left * overlayScale.value + 'px',
top: el.coordinates.top * overlayScale.value + 'px',
width: el.coordinates.width * overlayScale.value + 'px',
height: el.coordinates.height * overlayScale.value + 'px',
};
};
// Resize handler
const heightKey = 'autoprobe.results.containerHeight';
const onSizeChange = (size: number) => {
@@ -205,29 +212,20 @@ defineOptions({ name: 'ClAutoProbeResultsContainer' });
>
<!-- <el-skeleton :rows="15" animated v-if="iframeLoading && url" />-->
<div v-loading="previewLoading" class="preview-container">
<div v-if="pagePreview" ref="overlayRef" class="preview-overlay">
<img class="screenshot" :src="pagePreview.screenshot_base64" />
<div v-if="previewResult" ref="overlayRef" class="preview-overlay">
<img class="screenshot" :src="previewResult.screenshot_base64" />
<div
v-for="coord in pagePreview.page_items_coordinates"
:key="coord.id"
v-for="(el, index) in previewResult.page_elements"
:key="index"
class="element-mask"
:style="{
position: 'absolute',
left: coord.coordinates?.left + 'px',
top: coord.coordinates?.top + 'px',
width: coord.coordinates?.width + 'px',
height: coord.coordinates?.height + 'px',
}"
:style="getElementMaskStyle(el)"
>
<el-badge
type="primary"
:badge-style="{opacity: 0.5}"
>
<el-badge type="primary" :badge-style="{ opacity: 0.8 }">
<template #content>
<span style="margin-right: 5px">
<cl-icon :icon="getIconByItemType('field')" />
<cl-icon :icon="getIconByPageElementType(el.type)" />
</span>
{{ coord.id }}
{{ el.name }}
</template>
</el-badge>
</div>
@@ -308,19 +306,20 @@ defineOptions({ name: 'ClAutoProbeResultsContainer' });
top: 0;
left: 0;
width: 100%;
z-index: 1;
img.screenshot {
width: 100%;
}
.element-mask {
border: 1px solid var(--el-color-primary);
border: 1px solid var(--el-color-primary-light-5);
border-radius: 4px;
pointer-events: none;
z-index: 1;
&:hover {
background-color: var(--cl-primary-color);
opacity: 0.8;
background: rgba(64, 156, 255, 0.2);
cursor: pointer;
}
}
}

View File

@@ -66,7 +66,7 @@ export declare global {
html?: string;
page_pattern?: PagePattern;
page_data?: PageData;
page_elements?: PageItemCoordinates[];
page_elements?: PageElement[];
provider_id?: string;
model?: string;
usage?: LLMResponseUsage;
@@ -77,7 +77,7 @@ export declare global {
screenshot_base64?: string;
page_pattern?: PagePattern;
page_data?: PageData;
page_elements?: PageItemCoordinates[];
page_elements?: PageElement[];
}
type AutoProbeItemType = 'page_pattern' | 'list' | 'field' | 'pagination';
@@ -111,14 +111,14 @@ export declare global {
type PageElementType = 'list' | 'list-item' | 'field' | 'pagination';
interface PageItemCoordinates {
interface PageElement {
name: string;
type: PageElementType;
coordinates: ElementCoordinates;
}
interface PagePreview {
interface PagePreviewResult {
screenshot_base64: string;
page_items_coordinates: PageItemCoordinates[];
page_elements: PageElement[];
}
}

View File

@@ -36,3 +36,18 @@ export const getIconByItemType = (itemType?: AutoProbeItemType): Icon => {
return ['fa', 'question'];
}
};
export const getIconByPageElementType = (itemType?: PageElementType): Icon => {
switch (itemType) {
case 'field':
return ['fa', 'tag'];
case 'list':
return ['fa', 'list'];
case 'list-item':
return ['fa', 'list-alt'];
case 'pagination':
return ['fa', 'ellipsis-h'];
default:
return ['fa', 'question'];
}
}

View File

@@ -8,6 +8,7 @@ const {
tableTotal,
tablePagination,
actionFunctions,
rowKey,
} = useAutoProbeList();
defineOptions({ name: 'ClAutoProbeList' });
@@ -22,6 +23,7 @@ defineOptions({ name: 'ClAutoProbeList' });
:table-columns="tableColumns"
:table-data="tableData"
:table-total="tableTotal"
:row-key="rowKey"
>
<template #extra>
<!-- Dialogs (handled by store) -->

View File

@@ -190,12 +190,17 @@ const useAutoProbeList = () => {
] as TableColumns<AutoProbe>
);
const rowKey = (row: AutoProbe) => {
return JSON.stringify([row._id, row.url, row.last_task?.status, row.page_pattern]);
}
setupAutoUpdate(getList);
return {
...useList<AutoProbe>(ns, store),
navActions,
tableColumns,
rowKey,
};
};