mirror of
https://github.com/crawlab-team/crawlab.git
synced 2026-01-24 17:41:03 +01:00
feat: implement AutoProbe components and enhance LLM provider functionality
- Introduced AutoProbeForm and AutoProbeTaskStatus components for managing AutoProbe configurations and task statuses. - Updated LLMProvider model to include a default model field for improved provider management. - Enhanced AutoProbeList and AutoProbeDetail components for better user interaction and data display. - Refactored related Vue components and services to streamline AutoProbe functionality and improve state management. - Added internationalization support for new AutoProbe features and updated existing translations.
This commit is contained in:
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func GetSystemInfo(c *gin.Context) (response *Response[entity.SystemInfo], err error) {
|
||||
func GetSystemInfo(_ *gin.Context) (response *Response[entity.SystemInfo], err error) {
|
||||
info := entity.SystemInfo{
|
||||
Edition: utils.GetEdition(),
|
||||
Version: utils.GetVersion(),
|
||||
|
||||
@@ -6,10 +6,10 @@ type LLMProvider struct {
|
||||
BaseModel `bson:",inline"`
|
||||
Type string `json:"type" bson:"type" description:"Provider type (e.g., 'openai', 'azure-openai', 'anthropic', 'gemini')"`
|
||||
Name string `json:"name" bson:"name" description:"Display name for UI"`
|
||||
Enabled bool `json:"enabled" bson:"enabled" description:"Whether this provider is enabled"`
|
||||
ApiKey string `json:"api_key" bson:"api_key" description:"API key for the provider"`
|
||||
ApiBaseUrl string `json:"api_base_url" bson:"api_base_url" description:"API base URL for the provider"`
|
||||
DeploymentName string `json:"deployment_name" bson:"deployment_name" description:"Deployment name for the provider"`
|
||||
ApiVersion string `json:"api_version" bson:"api_version" description:"API version for the provider"`
|
||||
Models []string `json:"models" bson:"models" description:"Models supported by this provider"`
|
||||
DefaultModel string `json:"default_model" bson:"default_model" description:"Default model for this provider"`
|
||||
}
|
||||
|
||||
@@ -108,9 +108,19 @@ const toggleModel = (model: string) => {
|
||||
if (index === -1) {
|
||||
// Enable model
|
||||
modelValue.models.push(model);
|
||||
|
||||
// If this is the first model being added, set it as default
|
||||
if (modelValue.models.length === 1 && !modelValue.default_model) {
|
||||
modelValue.default_model = model;
|
||||
}
|
||||
} else {
|
||||
// Disable model
|
||||
modelValue.models.splice(index, 1);
|
||||
|
||||
// If default model is being disabled, update default model
|
||||
if (modelValue.default_model === model) {
|
||||
modelValue.default_model = modelValue.models.length > 0 ? modelValue.models[0] : '';
|
||||
}
|
||||
}
|
||||
|
||||
emit('update:modelValue', modelValue);
|
||||
@@ -121,6 +131,10 @@ const updateDefaultModels = () => {
|
||||
if (!modelValue) return;
|
||||
if (defaultModels.value.length > 0) {
|
||||
modelValue.models = [...defaultModels.value];
|
||||
// Set default model to first enabled model
|
||||
if (modelValue.models.length > 0 && !modelValue.default_model) {
|
||||
modelValue.default_model = modelValue.models[0];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -220,9 +234,6 @@ defineOptions({ name: 'ClLlmProviderForm' });
|
||||
:placeholder="t('views.system.ai.name')"
|
||||
/>
|
||||
</cl-form-item>
|
||||
<cl-form-item :label="t('views.system.ai.enabled')" :span="4">
|
||||
<cl-switch v-model="modelValue.enabled" />
|
||||
</cl-form-item>
|
||||
<cl-form-item
|
||||
:label="t('views.system.ai.apiKey')"
|
||||
:span="4"
|
||||
@@ -272,9 +283,6 @@ defineOptions({ name: 'ClLlmProviderForm' });
|
||||
<div class="models-section">
|
||||
<!-- Default models from provider -->
|
||||
<div v-if="defaultModels.length > 0" class="default-models">
|
||||
<div class="section-title">
|
||||
{{ t('views.system.ai.defaultModels') }}
|
||||
</div>
|
||||
<div class="model-list">
|
||||
<el-checkbox
|
||||
v-for="model in defaultModels"
|
||||
@@ -283,17 +291,16 @@ defineOptions({ name: 'ClLlmProviderForm' });
|
||||
@change="() => toggleModel(model)"
|
||||
class="model-checkbox"
|
||||
>
|
||||
{{ model }}
|
||||
<span>{{ model }}</span>
|
||||
<span class="default-model-badge" v-if="model === modelValue.default_model">
|
||||
({{ t('common.mode.default') }})
|
||||
</span>
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Custom models -->
|
||||
<div class="custom-models">
|
||||
<div class="section-title">
|
||||
{{ t('views.system.ai.customModels') }}
|
||||
</div>
|
||||
|
||||
<!-- Add custom model input -->
|
||||
<div class="add-model">
|
||||
<el-input
|
||||
@@ -334,6 +341,21 @@ defineOptions({ name: 'ClLlmProviderForm' });
|
||||
</div>
|
||||
</div>
|
||||
</cl-form-item>
|
||||
|
||||
<cl-form-item :label="t('views.system.ai.defaultModel')" :span="4" prop="default_model">
|
||||
<el-select
|
||||
v-model="modelValue.default_model"
|
||||
:placeholder="t('views.system.ai.defaultModel')"
|
||||
:disabled="!modelValue.models || modelValue.models.length === 0"
|
||||
>
|
||||
<el-option
|
||||
v-for="model in modelValue.models || []"
|
||||
:key="model"
|
||||
:label="model"
|
||||
:value="model"
|
||||
/>
|
||||
</el-select>
|
||||
</cl-form-item>
|
||||
</cl-form>
|
||||
</template>
|
||||
|
||||
@@ -386,5 +408,11 @@ defineOptions({ name: 'ClLlmProviderForm' });
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.default-model-badge {
|
||||
margin-left: 5px;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<script setup lang="ts">
|
||||
import { useStore } from 'vuex';
|
||||
import { useAutoProbe } from '@/components';
|
||||
import { translate } from '@/utils';
|
||||
|
||||
// i18n
|
||||
const t = translate;
|
||||
|
||||
// store
|
||||
const store = useStore();
|
||||
const { form, formRef, formRules, isSelectiveForm, isFormItemDisabled } =
|
||||
useAutoProbe(store);
|
||||
defineOptions({ name: 'ClAutoProbeForm' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<cl-form
|
||||
v-if="form"
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="formRules"
|
||||
:selective="isSelectiveForm"
|
||||
>
|
||||
<cl-form-item
|
||||
:span="2"
|
||||
:offset="2"
|
||||
:label="t('components.project.form.name')"
|
||||
not-editable
|
||||
prop="name"
|
||||
required
|
||||
>
|
||||
<el-input
|
||||
v-model="form.name"
|
||||
:disabled="isFormItemDisabled('name')"
|
||||
:placeholder="t('components.autoprobe.form.name')"
|
||||
/>
|
||||
</cl-form-item>
|
||||
<cl-form-item
|
||||
:span="4"
|
||||
:label="t('components.autoprobe.form.url')"
|
||||
prop="url"
|
||||
required
|
||||
>
|
||||
<el-input
|
||||
v-model="form.url"
|
||||
:disabled="isFormItemDisabled('url')"
|
||||
:placeholder="t('components.autoprobe.form.url')"
|
||||
type="textarea"
|
||||
/>
|
||||
</cl-form-item>
|
||||
<cl-form-item
|
||||
:span="4"
|
||||
:label="t('components.autoprobe.form.query')"
|
||||
prop="query"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.query"
|
||||
:disabled="isFormItemDisabled('query')"
|
||||
:placeholder="t('components.autoprobe.form.query')"
|
||||
type="textarea"
|
||||
/>
|
||||
</cl-form-item>
|
||||
</cl-form>
|
||||
</template>
|
||||
@@ -0,0 +1,92 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { translate } from '@/utils';
|
||||
import { TagProps } from '@/components/ui/tag/types';
|
||||
|
||||
const props = defineProps<{
|
||||
status: AutoProbeTaskStatus;
|
||||
size?: BasicSize;
|
||||
error?: string;
|
||||
clickable?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'click'): void;
|
||||
}>();
|
||||
|
||||
// i18n
|
||||
const t = translate;
|
||||
|
||||
const data = computed<TagProps>(() => {
|
||||
const { status, error } = props;
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return {
|
||||
label: t('components.autoprobe.task.status.label.pending'),
|
||||
tooltip: t('components.autoprobe.task.status.tooltip.pending'),
|
||||
type: 'primary',
|
||||
icon: ['fa', 'hourglass-start'],
|
||||
spinning: true,
|
||||
};
|
||||
case 'running':
|
||||
return {
|
||||
label: t('components.autoprobe.task.status.label.running'),
|
||||
tooltip: t('components.autoprobe.task.status.tooltip.running'),
|
||||
type: 'warning',
|
||||
icon: ['fa', 'spinner'],
|
||||
spinning: true,
|
||||
};
|
||||
case 'completed':
|
||||
return {
|
||||
label: t('components.autoprobe.task.status.label.completed'),
|
||||
tooltip: t('components.autoprobe.task.status.tooltip.completed'),
|
||||
type: 'success',
|
||||
icon: ['fa', 'check'],
|
||||
};
|
||||
case 'failed':
|
||||
return {
|
||||
label: t('components.autoprobe.task.status.label.failed'),
|
||||
tooltip: `${t('components.autoprobe.task.status.tooltip.failed')}<br><span style="color: 'var(--cl-red)">${error}</span>`,
|
||||
type: 'danger',
|
||||
icon: ['fa', 'times'],
|
||||
};
|
||||
case 'cancelled':
|
||||
return {
|
||||
label: t('components.autoprobe.task.status.label.cancelled'),
|
||||
tooltip: t('components.autoprobe.task.status.tooltip.cancelled'),
|
||||
type: 'info',
|
||||
icon: ['fa', 'ban'],
|
||||
};
|
||||
default:
|
||||
return {
|
||||
label: t('components.autoprobe.task.status.label.unknown'),
|
||||
tooltip: t('components.autoprobe.task.status.tooltip.unknown'),
|
||||
type: 'info',
|
||||
icon: ['fa', 'question'],
|
||||
};
|
||||
}
|
||||
});
|
||||
defineOptions({ name: 'ClAutoProbeTaskStatus' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<cl-tag
|
||||
class-name="autoprobe-task-status"
|
||||
:key="data"
|
||||
:icon="data.icon"
|
||||
:label="data.label"
|
||||
:spinning="data.spinning"
|
||||
:type="data.type"
|
||||
:size="size"
|
||||
:clickable="clickable"
|
||||
@click="emit('click')"
|
||||
>
|
||||
<template #tooltip>
|
||||
<div v-html="data.tooltip" />
|
||||
</template>
|
||||
</cl-tag>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { useStore } from 'vuex';
|
||||
import { useAutoProbe } from '@/components';
|
||||
|
||||
// store
|
||||
const store = useStore();
|
||||
|
||||
const {
|
||||
activeDialogKey,
|
||||
createEditDialogVisible,
|
||||
actionFunctions,
|
||||
formList,
|
||||
confirmDisabled,
|
||||
confirmLoading,
|
||||
} = useAutoProbe(store);
|
||||
defineOptions({ name: 'ClCreateEditAutoProbeDialog' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<cl-create-edit-dialog
|
||||
:type="activeDialogKey"
|
||||
:visible="createEditDialogVisible"
|
||||
:action-functions="actionFunctions"
|
||||
:batch-form-data="formList"
|
||||
:confirm-disabled="confirmDisabled"
|
||||
:confirm-loading="confirmLoading"
|
||||
>
|
||||
<template #default>
|
||||
<cl-auto-probe-form />
|
||||
</template>
|
||||
</cl-create-edit-dialog>
|
||||
</template>
|
||||
@@ -0,0 +1,40 @@
|
||||
import { computed } from 'vue';
|
||||
import { Store } from 'vuex';
|
||||
import { useForm } from '@/components';
|
||||
import useAutoProbeService from '@/services/autoprobe/autoprobeService';
|
||||
import { getDefaultFormComponentData } from '@/utils/form';
|
||||
|
||||
// form component data
|
||||
const formComponentData = getDefaultFormComponentData<AutoProbe>();
|
||||
|
||||
const useAutoProbe = (store: Store<RootStoreState>) => {
|
||||
// store
|
||||
const ns = 'autoprobe';
|
||||
const state = store.state[ns];
|
||||
|
||||
// form rules
|
||||
const formRules: FormRules = {};
|
||||
|
||||
// all autoprobe select options
|
||||
const allAutoProbeSelectOptions = computed<SelectOption[]>(() =>
|
||||
state.allList.map(d => {
|
||||
return {
|
||||
label: d.name,
|
||||
value: d._id,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
...useForm<AutoProbe>(
|
||||
'autoprobe',
|
||||
store,
|
||||
useAutoProbeService(store),
|
||||
formComponentData
|
||||
),
|
||||
formRules,
|
||||
allAutoProbeSelectOptions,
|
||||
};
|
||||
};
|
||||
|
||||
export default useAutoProbe;
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
TASK_STATUS_RUNNING,
|
||||
} from '@/constants/task';
|
||||
import { translate } from '@/utils';
|
||||
import { TagProps } from '@/components/ui/tag/types';
|
||||
|
||||
const props = defineProps<{
|
||||
status: TaskStatus;
|
||||
|
||||
@@ -19,6 +19,8 @@ import * as theme from './ui/lexical/utils/theme';
|
||||
import * as VariableNode from './ui/lexical/nodes/VariableNode';
|
||||
import AssistantConsole from './core/ai/AssistantConsole.vue';
|
||||
import AtomMaterialIcon from './ui/icon/AtomMaterialIcon.vue';
|
||||
import AutoProbeForm from './core/autoprobe/AutoProbeForm.vue';
|
||||
import AutoProbeTaskStatus from './core/autoprobe/AutoProbeTaskStatus.vue';
|
||||
import BlockOptionsDropdownList from './ui/lexical/components/BlockOptionsDropdownList.vue';
|
||||
import Box from './ui/box/Box.vue';
|
||||
import Button from './ui/button/Button.vue';
|
||||
@@ -38,6 +40,7 @@ import CheckTagGroup from './ui/tag/CheckTagGroup.vue';
|
||||
import ConfirmDialog from './ui/dialog/ConfirmDialog.vue';
|
||||
import ContextMenu from './ui/context-menu/ContextMenu.vue';
|
||||
import ContextMenuList from './ui/context-menu/ContextMenuList.vue';
|
||||
import CreateEditAutoProbeDialog from './core/autoprobe/CreateEditAutoProbeDialog.vue';
|
||||
import CreateEditDatabaseDialog from './core/database/CreateEditDatabaseDialog.vue';
|
||||
import CreateEditDatabaseTableDialog from './core/database/CreateEditDatabaseTableDialog.vue';
|
||||
import CreateEditDialog from './ui/dialog/CreateEditDialog.vue';
|
||||
@@ -218,6 +221,7 @@ import UploadFilesDialog from './ui/file/UploadFilesDialog.vue';
|
||||
import UploadGitFilesDialog from './core/git/UploadGitFilesDialog.vue';
|
||||
import UploadSpiderFilesDialog from './core/spider/UploadSpiderFilesDialog.vue';
|
||||
import useAssistantConsole from './core/ai/useAssistantConsole';
|
||||
import useAutoProbe from './core/autoprobe/useAutoProbe';
|
||||
import useCanShowPlaceholder from './ui/lexical/composables/useCanShowPlaceholder';
|
||||
import useDatabase from './core/database/useDatabase';
|
||||
import useDecorators from './ui/lexical/composables/useDecorators';
|
||||
@@ -265,6 +269,8 @@ export {
|
||||
VariableNode as VariableNode,
|
||||
AssistantConsole as ClAssistantConsole,
|
||||
AtomMaterialIcon as ClAtomMaterialIcon,
|
||||
AutoProbeForm as ClAutoProbeForm,
|
||||
AutoProbeTaskStatus as ClAutoProbeTaskStatus,
|
||||
BlockOptionsDropdownList as ClBlockOptionsDropdownList,
|
||||
Box as ClBox,
|
||||
Button as ClButton,
|
||||
@@ -284,6 +290,7 @@ export {
|
||||
ConfirmDialog as ClConfirmDialog,
|
||||
ContextMenu as ClContextMenu,
|
||||
ContextMenuList as ClContextMenuList,
|
||||
CreateEditAutoProbeDialog as ClCreateEditAutoProbeDialog,
|
||||
CreateEditDatabaseDialog as ClCreateEditDatabaseDialog,
|
||||
CreateEditDatabaseTableDialog as ClCreateEditDatabaseTableDialog,
|
||||
CreateEditDialog as ClCreateEditDialog,
|
||||
@@ -464,6 +471,7 @@ export {
|
||||
UploadGitFilesDialog as ClUploadGitFilesDialog,
|
||||
UploadSpiderFilesDialog as ClUploadSpiderFilesDialog,
|
||||
useAssistantConsole as useAssistantConsole,
|
||||
useAutoProbe as useAutoProbe,
|
||||
useCanShowPlaceholder as useCanShowPlaceholder,
|
||||
useDatabase as useDatabase,
|
||||
useDecorators as useDecorators,
|
||||
|
||||
@@ -80,6 +80,7 @@ const common: LCommon = {
|
||||
proceed: 'Are you sure to proceed?',
|
||||
create: 'Are you sure to create?',
|
||||
continue: 'Are you sure to continue?',
|
||||
setDefault: 'Are you sure to set as default?',
|
||||
},
|
||||
},
|
||||
message: {
|
||||
@@ -159,6 +160,7 @@ const common: LCommon = {
|
||||
other: 'Other',
|
||||
all: 'All',
|
||||
unlimited: 'Unlimited',
|
||||
preview: 'Preview',
|
||||
},
|
||||
placeholder: {
|
||||
empty: 'Empty',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const ai: LangAi = {
|
||||
const ai: LComponentsAI = {
|
||||
chatbot: {
|
||||
title: 'Crawlab AI Assistant',
|
||||
tooltip: 'Chat with AI Assistant',
|
||||
|
||||
29
frontend/crawlab-ui/src/i18n/lang/en/components/autoprobe.ts
Normal file
29
frontend/crawlab-ui/src/i18n/lang/en/components/autoprobe.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
const autoprobe: LComponentsAutoProbe = {
|
||||
form: {
|
||||
name: 'Name',
|
||||
url: 'URL',
|
||||
query: 'Query',
|
||||
},
|
||||
task: {
|
||||
status: {
|
||||
label: {
|
||||
pending: 'Pending',
|
||||
running: 'Running',
|
||||
completed: 'Completed',
|
||||
failed: 'Failed',
|
||||
cancelled: 'Cancelled',
|
||||
unknown: 'Unknown',
|
||||
},
|
||||
tooltip: {
|
||||
pending: 'The task is waiting to be processed',
|
||||
running: 'The task is currently running',
|
||||
completed: 'The task has been completed successfully',
|
||||
failed: 'The task has failed',
|
||||
cancelled: 'The task has been cancelled',
|
||||
unknown: 'The task status is unknown',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default autoprobe;
|
||||
@@ -27,6 +27,7 @@ import role from './role';
|
||||
import tag from './tag';
|
||||
import environment from './environment';
|
||||
import ai from './ai';
|
||||
import autoprobe from './autoprobe';
|
||||
|
||||
const components: LComponents = {
|
||||
chart,
|
||||
@@ -58,6 +59,7 @@ const components: LComponents = {
|
||||
tag,
|
||||
environment,
|
||||
ai,
|
||||
autoprobe,
|
||||
};
|
||||
|
||||
export default components;
|
||||
|
||||
@@ -227,6 +227,18 @@ const layouts: LLayouts = {
|
||||
disclaimer: 'Disclaimer',
|
||||
},
|
||||
},
|
||||
autoprobe: {
|
||||
list: {
|
||||
title: 'AutoProbe List',
|
||||
},
|
||||
detail: {
|
||||
title: 'AutoProbe Detail',
|
||||
tabs: {
|
||||
overview: 'Overview',
|
||||
tasks: 'Task',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ const router: LRouter = {
|
||||
disclaimer: 'Disclaimer',
|
||||
},
|
||||
},
|
||||
autoprobe: 'AutoProbe',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
27
frontend/crawlab-ui/src/i18n/lang/en/views/autoprobe.ts
Normal file
27
frontend/crawlab-ui/src/i18n/lang/en/views/autoprobe.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
const autoprobe: LViewsAutoProbe = {
|
||||
table: {
|
||||
columns: {
|
||||
name: 'Name',
|
||||
url: 'URL',
|
||||
query: 'Query',
|
||||
status: 'Status',
|
||||
},
|
||||
},
|
||||
navActions: {
|
||||
new: {
|
||||
label: 'New AutoProbe',
|
||||
tooltip: 'Create a new AutoProbe',
|
||||
},
|
||||
filter: {
|
||||
search: {
|
||||
placeholder: 'Search by name',
|
||||
},
|
||||
},
|
||||
run: {
|
||||
label: 'Run AutoProbe',
|
||||
tooltip: 'Run the selected AutoProbe',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default autoprobe;
|
||||
@@ -17,6 +17,7 @@ import environment from './environment';
|
||||
import llm from './llm';
|
||||
import system from './system';
|
||||
import misc from './misc';
|
||||
import autoprobe from './autoprobe';
|
||||
|
||||
const views: LViews = {
|
||||
login,
|
||||
@@ -38,5 +39,6 @@ const views: LViews = {
|
||||
llm,
|
||||
system,
|
||||
misc,
|
||||
autoprobe,
|
||||
};
|
||||
export default views;
|
||||
|
||||
@@ -15,8 +15,7 @@ const system: LViewsSystem = {
|
||||
apiBaseUrl: 'API Base URL',
|
||||
apiVersion: 'API Version',
|
||||
models: 'Models',
|
||||
defaultModels: 'Default Models',
|
||||
customModels: 'Custom Models',
|
||||
defaultModel: 'Default Model',
|
||||
addCustomModel: 'Add custom model',
|
||||
noCustomModels: 'No custom models added',
|
||||
modelAlreadyExists: 'Model already exists',
|
||||
|
||||
@@ -80,6 +80,7 @@ const common: LCommon = {
|
||||
proceed: '您是否确定继续?',
|
||||
create: '您是否确定创建?',
|
||||
continue: '您是否确定继续?',
|
||||
setDefault: '您是否确定设为默认?',
|
||||
},
|
||||
},
|
||||
message: {
|
||||
@@ -159,6 +160,7 @@ const common: LCommon = {
|
||||
other: '其他',
|
||||
all: '全部',
|
||||
unlimited: '无限制',
|
||||
preview: '预览',
|
||||
},
|
||||
placeholder: {
|
||||
empty: '空',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const ai: LangAi = {
|
||||
const ai: LComponentsAI = {
|
||||
chatbot: {
|
||||
title: 'Crawlab AI 助手',
|
||||
tooltip: '与 AI 助手聊天',
|
||||
|
||||
29
frontend/crawlab-ui/src/i18n/lang/zh/components/autoprobe.ts
Normal file
29
frontend/crawlab-ui/src/i18n/lang/zh/components/autoprobe.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
const autoprobe: LComponentsAutoProbe = {
|
||||
form: {
|
||||
name: '名称',
|
||||
url: 'URL',
|
||||
query: '查询',
|
||||
},
|
||||
task: {
|
||||
status: {
|
||||
label: {
|
||||
pending: '等待中',
|
||||
running: '运行中',
|
||||
completed: '已完成',
|
||||
failed: '失败',
|
||||
cancelled: '已取消',
|
||||
unknown: '未知',
|
||||
},
|
||||
tooltip: {
|
||||
pending: '任务正在等待处理',
|
||||
running: '任务正在运行',
|
||||
completed: '任务已成功完成',
|
||||
failed: '任务执行失败',
|
||||
cancelled: '任务已被取消',
|
||||
unknown: '任务状态未知',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default autoprobe;
|
||||
@@ -27,6 +27,7 @@ import role from './role';
|
||||
import tag from './tag';
|
||||
import environment from './environment';
|
||||
import ai from './ai';
|
||||
import autoprobe from './autoprobe';
|
||||
|
||||
const components: LComponents = {
|
||||
chart,
|
||||
@@ -58,6 +59,7 @@ const components: LComponents = {
|
||||
tag,
|
||||
environment,
|
||||
ai,
|
||||
autoprobe,
|
||||
};
|
||||
|
||||
export default components;
|
||||
|
||||
@@ -227,6 +227,18 @@ const layouts: LLayouts = {
|
||||
disclaimer: '免责声明',
|
||||
},
|
||||
},
|
||||
autoprobe: {
|
||||
list: {
|
||||
title: 'AutoProbe 列表',
|
||||
},
|
||||
detail: {
|
||||
title: 'AutoProbe 详情',
|
||||
tabs: {
|
||||
overview: '概览',
|
||||
tasks: '任务',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ const router: LRouter = {
|
||||
disclaimer: '免责声明',
|
||||
},
|
||||
},
|
||||
autoprobe: 'AutoProbe',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
27
frontend/crawlab-ui/src/i18n/lang/zh/views/autoprobe.ts
Normal file
27
frontend/crawlab-ui/src/i18n/lang/zh/views/autoprobe.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
const autoprobe: LViewsAutoProbe = {
|
||||
table: {
|
||||
columns: {
|
||||
name: '名称',
|
||||
url: 'URL',
|
||||
query: '查询',
|
||||
status: '状态',
|
||||
},
|
||||
},
|
||||
navActions: {
|
||||
new: {
|
||||
label: '新建 AutoProbe',
|
||||
tooltip: '创建新的 AutoProbe',
|
||||
},
|
||||
filter: {
|
||||
search: {
|
||||
placeholder: '按名称搜索',
|
||||
},
|
||||
},
|
||||
run: {
|
||||
label: '运行 AutoProbe',
|
||||
tooltip: '运行选定的 AutoProbe',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default autoprobe;
|
||||
@@ -17,6 +17,7 @@ import environment from './environment';
|
||||
import llm from './llm';
|
||||
import system from './system';
|
||||
import misc from './misc';
|
||||
import autoprobe from './autoprobe';
|
||||
|
||||
const views: LViews = {
|
||||
login,
|
||||
@@ -38,5 +39,6 @@ const views: LViews = {
|
||||
llm,
|
||||
system,
|
||||
misc,
|
||||
autoprobe,
|
||||
};
|
||||
export default views;
|
||||
|
||||
@@ -15,8 +15,7 @@ const system: LViewsSystem = {
|
||||
apiBaseUrl: 'API 基础 URL',
|
||||
apiVersion: 'API 版本',
|
||||
models: '模型',
|
||||
defaultModels: '默认模型',
|
||||
customModels: '自定义模型',
|
||||
defaultModel: '默认模型',
|
||||
addCustomModel: '添加自定义模型',
|
||||
noCustomModels: '暂无自定义模型',
|
||||
modelAlreadyExists: '模型已存在',
|
||||
|
||||
@@ -93,6 +93,7 @@ export declare global {
|
||||
proceed: string;
|
||||
create: string;
|
||||
continue: string;
|
||||
setDefault: string;
|
||||
};
|
||||
};
|
||||
message: {
|
||||
@@ -172,6 +173,7 @@ export declare global {
|
||||
other: string;
|
||||
all: string;
|
||||
unlimited: string;
|
||||
preview: string;
|
||||
};
|
||||
placeholder: {
|
||||
empty: string;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export declare global {
|
||||
interface LangAi {
|
||||
interface LComponentsAI {
|
||||
chatbot: {
|
||||
title: string;
|
||||
tooltip: string;
|
||||
|
||||
27
frontend/crawlab-ui/src/interfaces/i18n/components/autoprobe.d.ts
vendored
Normal file
27
frontend/crawlab-ui/src/interfaces/i18n/components/autoprobe.d.ts
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
interface LComponentsAutoProbe {
|
||||
form: {
|
||||
name: string;
|
||||
url: string;
|
||||
query: string;
|
||||
};
|
||||
task: {
|
||||
status: {
|
||||
label: {
|
||||
pending: string;
|
||||
running: string;
|
||||
completed: string;
|
||||
failed: string;
|
||||
cancelled: string;
|
||||
unknown: string;
|
||||
};
|
||||
tooltip: {
|
||||
pending: string;
|
||||
running: string;
|
||||
completed: string;
|
||||
failed: string;
|
||||
cancelled: string;
|
||||
unknown: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -20,7 +20,8 @@ export declare global {
|
||||
environment: LComponentsEnvironment;
|
||||
notification: LComponentsNotification;
|
||||
editor: LComponentsEditor;
|
||||
ai: LAi;
|
||||
ai: LComponentsAI;
|
||||
autoprobe: LComponentsAutoProbe;
|
||||
|
||||
// model-related components
|
||||
node: LComponentsNode;
|
||||
|
||||
@@ -122,6 +122,10 @@ export declare global {
|
||||
pat: string;
|
||||
disclaimer: string;
|
||||
}>;
|
||||
autoprobe: LListLayoutPage<{
|
||||
overview: string;
|
||||
tasks: string;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ export declare global {
|
||||
disclaimer: string;
|
||||
};
|
||||
};
|
||||
autoprobe: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
16
frontend/crawlab-ui/src/interfaces/i18n/views/autoprobe.d.ts
vendored
Normal file
16
frontend/crawlab-ui/src/interfaces/i18n/views/autoprobe.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
interface LViewsAutoProbe {
|
||||
table: {
|
||||
columns: {
|
||||
name: string;
|
||||
url: string;
|
||||
query: string;
|
||||
status: string;
|
||||
};
|
||||
};
|
||||
navActions: LNavActions & {
|
||||
run: {
|
||||
label: string;
|
||||
tooltip: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -19,5 +19,6 @@ export declare global {
|
||||
environment: LViewsEnvironments;
|
||||
llm: LViewsLLM;
|
||||
system: LViewsSystem;
|
||||
autoprobe: LViewsAutoProbe;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@ interface LViewsSystem {
|
||||
deploymentName: string;
|
||||
apiVersion: string;
|
||||
models: string;
|
||||
defaultModels: string;
|
||||
customModels: string;
|
||||
defaultModel: string;
|
||||
addCustomModel: string;
|
||||
noCustomModels: string;
|
||||
modelAlreadyExists: string;
|
||||
|
||||
@@ -5,5 +5,7 @@ export declare global {
|
||||
icon?: Icon;
|
||||
hidden?: boolean;
|
||||
routeConcept?: RouteConcept;
|
||||
badge?: string;
|
||||
badgeType?: BasicType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ export declare global {
|
||||
query?: string;
|
||||
}
|
||||
|
||||
type AutoProbeTaskStatus = 'pending' | 'running' | 'completed' | 'failed';
|
||||
type AutoProbeTaskStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
||||
|
||||
type SelectorType = 'css' | 'xpath' | 'regex';
|
||||
type ExtractType = 'text' | 'attribute' | 'html';
|
||||
@@ -59,6 +59,7 @@ export declare global {
|
||||
url?: string;
|
||||
query?: string;
|
||||
status: AutoProbeTaskStatus;
|
||||
error?: string;
|
||||
page_pattern?: PagePattern;
|
||||
page_data?: PageData;
|
||||
provider_id?: string;
|
||||
|
||||
@@ -19,11 +19,11 @@ export declare global {
|
||||
interface LLMProvider extends BaseModel {
|
||||
type?: LLMProviderType;
|
||||
name?: string;
|
||||
enabled?: boolean;
|
||||
api_key?: string;
|
||||
api_base_url?: string;
|
||||
api_version?: string;
|
||||
models?: string[];
|
||||
default_model?: string;
|
||||
}
|
||||
|
||||
interface LLMProviderItem {
|
||||
|
||||
@@ -16,11 +16,7 @@ export declare global {
|
||||
auto_install?: boolean;
|
||||
}
|
||||
|
||||
interface SettingAi {
|
||||
enable_ai?: boolean;
|
||||
api_key?: string;
|
||||
model?: string;
|
||||
max_tokens?: number;
|
||||
temperature?: number;
|
||||
interface SettingAI {
|
||||
default_provider_id?: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,4 +11,7 @@ type AutoProbeStoreGetters = BaseStoreGetters<AutoProbe>;
|
||||
|
||||
interface AutoProbeStoreMutations extends BaseStoreMutations<AutoProbe> {}
|
||||
|
||||
interface AutoProbeStoreActions extends BaseStoreActions<AutoProbe> {}
|
||||
interface AutoProbeStoreActions extends BaseStoreActions<AutoProbe> {
|
||||
runTask: StoreAction<AutoProbeStoreState, { id: string }>;
|
||||
cancelTask: StoreAction<AutoProbeStoreState, { id: string }>;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,11 @@ defineOptions({ name: 'ClSidebarItem' });
|
||||
>
|
||||
<cl-menu-item-icon :item="item" size="normal" />
|
||||
<template #title>
|
||||
<span class="menu-item-title">{{ t(item.title) }}</span>
|
||||
<span class="menu-item-title">
|
||||
<el-badge :value="item.badge" :type="item.badgeType" :offset="[12, 0]">
|
||||
{{ t(item.title) }}
|
||||
</el-badge>
|
||||
</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
|
||||
@@ -58,6 +62,8 @@ defineOptions({ name: 'ClSidebarItem' });
|
||||
}
|
||||
|
||||
.menu-item-title {
|
||||
display: inline;
|
||||
line-height: 24px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
47
frontend/crawlab-ui/src/router/autoprobe.ts
Normal file
47
frontend/crawlab-ui/src/router/autoprobe.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { TAB_NAME_OVERVIEW, TAB_NAME_TASKS } from '@/constants/tab';
|
||||
import {
|
||||
ClAutoProbeList,
|
||||
ClAutoProbeDetail,
|
||||
ClAutoProbeDetailTabOverview,
|
||||
ClAutoProbeDetailTabTasks,
|
||||
} from '@/views';
|
||||
import { getIconByTabName, translate } from '@/utils';
|
||||
import { RouteLocation } from 'vue-router';
|
||||
|
||||
const t = translate;
|
||||
|
||||
const endpoint = '/autoprobes';
|
||||
|
||||
export default [
|
||||
{
|
||||
routeConcept: 'autoprobe',
|
||||
name: 'AutoProbeList',
|
||||
path: endpoint,
|
||||
title: t('layouts.routes.autoprobe.list.title'),
|
||||
component: async () => ClAutoProbeList,
|
||||
},
|
||||
{
|
||||
routeConcept: 'autoprobe',
|
||||
name: 'AutoProbeDetail',
|
||||
path: `${endpoint}/:id`,
|
||||
title: t('layouts.routes.autoprobe.detail.title'),
|
||||
redirect: (to: RouteLocation) => {
|
||||
return { path: to.path + '/overview' };
|
||||
},
|
||||
component: async () => ClAutoProbeDetail,
|
||||
children: [
|
||||
{
|
||||
path: TAB_NAME_OVERVIEW,
|
||||
title: t('layouts.routes.autoprobe.detail.tabs.overview'),
|
||||
icon: getIconByTabName(TAB_NAME_OVERVIEW),
|
||||
component: async () => ClAutoProbeDetailTabOverview,
|
||||
},
|
||||
{
|
||||
path: TAB_NAME_TASKS,
|
||||
title: t('layouts.routes.autoprobe.detail.tabs.tasks'),
|
||||
icon: getIconByTabName(TAB_NAME_TASKS),
|
||||
component: async () => ClAutoProbeDetailTabTasks,
|
||||
},
|
||||
],
|
||||
},
|
||||
] as Array<ExtendedRouterRecord>;
|
||||
@@ -13,18 +13,19 @@ import task from '@/router/task';
|
||||
import schedule from '@/router/schedule';
|
||||
import user from '@/router/user';
|
||||
import role from '@/router/role';
|
||||
import token from '@/router/token';
|
||||
import notification from '@/router/notification';
|
||||
import git from '@/router/git';
|
||||
import database from '@/router/database';
|
||||
import dependency from '@/router/dependency';
|
||||
import environment from '@/router/environment';
|
||||
import system from '@/router/system';
|
||||
import misc from '@/router/misc';
|
||||
import autoprobe from '@/router/autoprobe';
|
||||
import { initRouterAuth } from '@/router/hooks/auth';
|
||||
import { ROUTER_ROOT_NAME_ROOT } from '@/constants/router';
|
||||
import { ClNormalLayout } from '@/layouts';
|
||||
import { getIconByRouteConcept } from '@/utils';
|
||||
import { getIconByRouteConcept, translate } from '@/utils';
|
||||
|
||||
const t = translate;
|
||||
|
||||
export function getDefaultRoutes(): Array<ExtendedRouterRecord> {
|
||||
return [
|
||||
@@ -48,9 +49,8 @@ export function getDefaultRoutes(): Array<ExtendedRouterRecord> {
|
||||
...database,
|
||||
...dependency,
|
||||
...system,
|
||||
// ...environment,
|
||||
// ...token,
|
||||
...misc,
|
||||
...autoprobe,
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -130,6 +130,13 @@ export function getDefaultSidebarMenuItems(): MenuItem[] {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/autoprobes',
|
||||
title: 'router.menuItems.autoprobe',
|
||||
icon: getIconByRouteConcept('autoprobe'),
|
||||
badge: t('common.mode.preview'),
|
||||
badgeType: 'primary',
|
||||
},
|
||||
{
|
||||
path: '/users',
|
||||
title: 'router.menuItems.users',
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Store } from 'vuex';
|
||||
import { getDefaultService } from '@/utils';
|
||||
|
||||
const useAutoProbeService = (
|
||||
store: Store<RootStoreState>
|
||||
): Services<AutoProbe> => {
|
||||
const ns: ListStoreNamespace = 'autoprobe';
|
||||
|
||||
return {
|
||||
...getDefaultService<AutoProbe>(ns, store),
|
||||
};
|
||||
};
|
||||
|
||||
export default useAutoProbeService;
|
||||
@@ -20,6 +20,7 @@ import database from '@/store/modules/database';
|
||||
import dependency from '@/store/modules/dependency';
|
||||
import environment from '@/store/modules/environment';
|
||||
import system from '@/store/modules/system';
|
||||
import autoprobe from '@/store/modules/autoprobe';
|
||||
|
||||
let _store: Store<RootStoreState>;
|
||||
|
||||
@@ -47,6 +48,7 @@ export const createStore = (): Store<RootStoreState> => {
|
||||
dependency,
|
||||
environment,
|
||||
system,
|
||||
autoprobe,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -4,16 +4,20 @@ import {
|
||||
getDefaultStoreMutations,
|
||||
getDefaultStoreState,
|
||||
} from '@/utils/store';
|
||||
import { TAB_NAME_OVERVIEW } from '@/constants/tab';
|
||||
import { TAB_NAME_OVERVIEW, TAB_NAME_TASKS } from '@/constants/tab';
|
||||
import { translate } from '@/utils/i18n';
|
||||
import useRequest from '@/services/request';
|
||||
|
||||
// i18n
|
||||
const t = translate;
|
||||
|
||||
const { post } = useRequest();
|
||||
|
||||
const state = {
|
||||
...getDefaultStoreState<AutoProbe>('autoprobe'),
|
||||
tabs: [
|
||||
{ id: TAB_NAME_OVERVIEW, title: t('common.tabs.overview') },
|
||||
{ id: TAB_NAME_TASKS, title: t('common.tabs.tasks') },
|
||||
],
|
||||
} as AutoProbeStoreState;
|
||||
|
||||
@@ -26,7 +30,19 @@ const mutations = {
|
||||
} as AutoProbeStoreMutations;
|
||||
|
||||
const actions = {
|
||||
...getDefaultStoreActions<AutoProbe>('/projects'),
|
||||
...getDefaultStoreActions<AutoProbe>('/ai/autoprobes'),
|
||||
runTask: async (
|
||||
_: StoreActionContext<AutoProbeStoreState>,
|
||||
{ id }: { id: string }
|
||||
) => {
|
||||
await post(`/ai/autoprobes/${id}/tasks`);
|
||||
},
|
||||
cancelTask: async (
|
||||
_: StoreActionContext<AutoProbeStoreState>,
|
||||
{ id }: { id: string }
|
||||
) => {
|
||||
await post(`/ai/autoprobes/tasks/${id}/cancel`);
|
||||
},
|
||||
} as AutoProbeStoreActions;
|
||||
|
||||
export default {
|
||||
|
||||
@@ -300,6 +300,8 @@ export const getIconByRouteConcept = (concept: RouteConcept): Icon => {
|
||||
return ['fa', 'key'];
|
||||
case 'disclaimer':
|
||||
return ['fa', 'info-circle'];
|
||||
case 'autoprobe':
|
||||
return ['fa', 'satellite-dish'];
|
||||
default:
|
||||
return ['fa', 'circle'];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'ClAutoProbeDetail' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<cl-detail-layout store-namespace="autoprobe" />
|
||||
</template>
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'ClAutoProbeDetailTabOverview' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="autoprobe-detail-tab-overview">
|
||||
<cl-auto-probe-form />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.autoprobe-detail-tab-overview {
|
||||
margin: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,236 @@
|
||||
<script setup lang="tsx">
|
||||
import { computed, onBeforeMount, ref } from 'vue';
|
||||
import { getDefaultPagination, translate } from '@/utils';
|
||||
import useRequest from '@/services/request';
|
||||
import { ClNavLink, ClAutoProbeTaskStatus } from '@/components';
|
||||
import { useDetail } from '@/layouts';
|
||||
import { useStore } from 'vuex';
|
||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
import {
|
||||
ACTION_CANCEL,
|
||||
ACTION_DELETE,
|
||||
ACTION_RESTART,
|
||||
ACTION_VIEW,
|
||||
TABLE_COLUMN_NAME_ACTIONS,
|
||||
} from '@/constants';
|
||||
|
||||
const t = translate;
|
||||
|
||||
const ns: ListStoreNamespace = 'autoprobe';
|
||||
const store = useStore();
|
||||
|
||||
const { getList, del } = useRequest();
|
||||
|
||||
const { activeId } = useDetail<AutoProbe>('autoprobe');
|
||||
|
||||
const tasks = ref<AutoProbeTask[]>([]);
|
||||
const taskTotal = ref(0);
|
||||
|
||||
const getTasks = async () => {
|
||||
const res = await getList(`/ai/autoprobes/${activeId.value}/tasks`, {
|
||||
page: tablePagination.value.page,
|
||||
size: tablePagination.value.size,
|
||||
});
|
||||
tasks.value = res.data || [];
|
||||
taskTotal.value = res.total || 0;
|
||||
};
|
||||
|
||||
// Cancel task function
|
||||
const cancelTask = async (row: AutoProbeTask, force: boolean = false) => {
|
||||
if (force) {
|
||||
ElMessage.info(t('common.message.info.forceCancel'));
|
||||
} else {
|
||||
ElMessage.info(t('common.message.info.cancel'));
|
||||
}
|
||||
|
||||
try {
|
||||
await store.dispatch(`${ns}/cancelTask`, { id: row._id });
|
||||
await getTasks(); // Refresh the list
|
||||
ElMessage.success(t('common.message.success.cancel'));
|
||||
} catch (error) {
|
||||
console.error('Failed to cancel task:', error);
|
||||
ElMessage.error(t('common.message.error.action'));
|
||||
}
|
||||
};
|
||||
|
||||
// Delete task function
|
||||
const deleteTask = async (row: AutoProbeTask) => {
|
||||
try {
|
||||
await del(`/ai/autoprobes/tasks/${row._id}`);
|
||||
ElMessage.success(t('common.message.success.delete'));
|
||||
await getTasks(); // Refresh the list
|
||||
} catch (error) {
|
||||
console.error('Failed to delete task:', error);
|
||||
ElMessage.error(t('common.message.error.action'));
|
||||
}
|
||||
};
|
||||
|
||||
const tableColumns = computed<TableColumns<AutoProbeTask>>(() => {
|
||||
return [
|
||||
{
|
||||
key: 'url',
|
||||
label: t('views.autoprobe.table.columns.url'),
|
||||
icon: ['fa', 'at'],
|
||||
width: 'auto',
|
||||
minWidth: '400px',
|
||||
value: (row: AutoProbeTask) => (
|
||||
<ClNavLink path={row.url} label={row.url} external />
|
||||
),
|
||||
allowFilterSearch: true,
|
||||
},
|
||||
{
|
||||
key: 'query',
|
||||
label: t('views.autoprobe.table.columns.query'),
|
||||
icon: ['fa', 'search'],
|
||||
width: '200px',
|
||||
value: (row: AutoProbeTask) => row.query,
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
label: t('views.autoprobe.table.columns.status'),
|
||||
icon: ['fa', 'info-circle'],
|
||||
width: '120px',
|
||||
value: (row: AutoProbeTask) => (
|
||||
<ClAutoProbeTaskStatus status={row.status} error={row.error} />
|
||||
),
|
||||
},
|
||||
{
|
||||
key: TABLE_COLUMN_NAME_ACTIONS,
|
||||
label: t('components.table.columns.actions'),
|
||||
icon: ['fa', 'tools'],
|
||||
width: '150px',
|
||||
fixed: 'right',
|
||||
buttons: (row: AutoProbeTask) =>
|
||||
(
|
||||
[
|
||||
{
|
||||
tooltip: t('common.actions.view'),
|
||||
onClick: async (row: AutoProbeTask) => {
|
||||
// View task details implementation
|
||||
ElMessage.info('View task details - to be implemented');
|
||||
},
|
||||
action: ACTION_VIEW,
|
||||
},
|
||||
{
|
||||
tooltip: t('common.actions.restart'),
|
||||
contextMenu: true,
|
||||
onClick: async (_: AutoProbeTask) => {
|
||||
await ElMessageBox.confirm(
|
||||
t('common.messageBox.confirm.restart'),
|
||||
t('common.actions.restart'),
|
||||
{
|
||||
type: 'warning',
|
||||
confirmButtonClass: 'restart-confirm-btn',
|
||||
}
|
||||
);
|
||||
await store.dispatch(`${ns}/runTask`, {
|
||||
id: activeId.value,
|
||||
});
|
||||
ElMessage.success(t('common.message.success.restart'));
|
||||
await getTasks();
|
||||
},
|
||||
action: ACTION_RESTART,
|
||||
},
|
||||
{
|
||||
tooltip: t('common.actions.cancel'),
|
||||
contextMenu: true,
|
||||
onClick: async (row: AutoProbeTask) => {
|
||||
await ElMessageBox.confirm(
|
||||
t('common.messageBox.confirm.cancel'),
|
||||
t('common.actions.cancel'),
|
||||
{
|
||||
type: 'warning',
|
||||
confirmButtonClass: 'cancel-confirm-btn',
|
||||
}
|
||||
);
|
||||
await cancelTask(row, false);
|
||||
},
|
||||
action: ACTION_CANCEL,
|
||||
},
|
||||
{
|
||||
tooltip: t('common.actions.delete'),
|
||||
contextMenu: true,
|
||||
onClick: async (row: AutoProbeTask) => {
|
||||
await ElMessageBox.confirm(
|
||||
t('common.messageBox.confirm.delete'),
|
||||
t('common.actions.delete'),
|
||||
{
|
||||
type: 'warning',
|
||||
confirmButtonClass: 'delete-confirm-btn',
|
||||
}
|
||||
);
|
||||
await deleteTask(row);
|
||||
},
|
||||
action: ACTION_DELETE,
|
||||
},
|
||||
] as TableColumnButton<AutoProbeTask>[]
|
||||
).filter(btn => {
|
||||
switch (btn.action) {
|
||||
case ACTION_CANCEL:
|
||||
return row.status === 'pending' || row.status === 'running';
|
||||
case ACTION_DELETE:
|
||||
return row.status !== 'pending' && row.status !== 'running';
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}),
|
||||
disableTransfer: true,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const tableData = computed(() => tasks.value);
|
||||
const tablePagination = ref<TablePagination>(getDefaultPagination());
|
||||
const tableTotal = computed(() => taskTotal.value);
|
||||
const onTablePaginationChange = async (pagination: TablePagination) => {
|
||||
tablePagination.value = pagination;
|
||||
await getTasks();
|
||||
};
|
||||
|
||||
const onClickRun = async () => {
|
||||
try {
|
||||
await store.dispatch(`${ns}/runTask`, { id: activeId.value });
|
||||
await getTasks(); // Refresh the list after running a task
|
||||
} catch (error) {
|
||||
console.error('Failed to run task:', error);
|
||||
ElMessage.error(t('common.message.error.action'));
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await Promise.all([getTasks()]);
|
||||
});
|
||||
|
||||
defineOptions({ name: 'ClAutoProbeDetailTabTasks' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tasks-container">
|
||||
<cl-table
|
||||
:columns="tableColumns"
|
||||
:data="tableData"
|
||||
:page="tablePagination.page"
|
||||
:page-size="tablePagination.size"
|
||||
:total="tableTotal"
|
||||
selectable
|
||||
embedded
|
||||
@pagination-change="onTablePaginationChange"
|
||||
>
|
||||
<template #empty>
|
||||
<cl-label-button
|
||||
:icon="['fa', 'play']"
|
||||
:label="t('views.autoprobe.navActions.run.label')"
|
||||
@click="onClickRun"
|
||||
/>
|
||||
</template>
|
||||
</cl-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tasks-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'ClExtractList' });
|
||||
import { useAutoProbeList } from '@/views';
|
||||
|
||||
const {
|
||||
navActions,
|
||||
tableColumns,
|
||||
tableData,
|
||||
tableTotal,
|
||||
tablePagination,
|
||||
actionFunctions,
|
||||
} = useAutoProbeList();
|
||||
|
||||
defineOptions({ name: 'ClAutoProbeList' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<cl-list-layout
|
||||
store-namespace="extract"
|
||||
/>
|
||||
class="autoprobe-list"
|
||||
:action-functions="actionFunctions"
|
||||
:nav-actions="navActions"
|
||||
:table-pagination="tablePagination"
|
||||
:table-columns="tableColumns"
|
||||
:table-data="tableData"
|
||||
:table-total="tableTotal"
|
||||
>
|
||||
<template #extra>
|
||||
<!-- Dialogs (handled by store) -->
|
||||
<cl-create-edit-auto-probe-dialog />
|
||||
<!-- ./Dialogs -->
|
||||
</template>
|
||||
</cl-list-layout>
|
||||
</template>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { useStore } from 'vuex';
|
||||
import { useList } from '@/layouts';
|
||||
|
||||
const useAutoProbeList = () => {
|
||||
const ns: ListStoreNamespace = 'autoprobe';
|
||||
const store = useStore();
|
||||
|
||||
return {
|
||||
...useList<AutoProbe>(ns, store),
|
||||
};
|
||||
};
|
||||
|
||||
export default useAutoProbeList;
|
||||
@@ -0,0 +1,154 @@
|
||||
import { useStore } from 'vuex';
|
||||
import { useList } from '@/layouts';
|
||||
import { computed } from 'vue';
|
||||
import {
|
||||
ACTION_ADD,
|
||||
ACTION_DELETE,
|
||||
ACTION_FILTER,
|
||||
ACTION_FILTER_SEARCH,
|
||||
ACTION_RUN,
|
||||
ACTION_VIEW,
|
||||
ACTION_VIEW_SPIDERS,
|
||||
FILTER_OP_CONTAINS,
|
||||
TABLE_COLUMN_NAME_ACTIONS,
|
||||
} from '@/constants';
|
||||
import { getIconByAction, onListFilterChangeByKey, translate } from '@/utils';
|
||||
import { ClNavLink } from '@/components';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
|
||||
const t = translate;
|
||||
|
||||
const useAutoProbeList = () => {
|
||||
const router = useRouter();
|
||||
|
||||
const ns: ListStoreNamespace = 'autoprobe';
|
||||
const store = useStore();
|
||||
const { commit } = store;
|
||||
|
||||
const { actionFunctions } = useList<AutoProbe>(ns, store);
|
||||
const { deleteByIdConfirm } = actionFunctions;
|
||||
|
||||
// nav actions
|
||||
const navActions = computed<ListActionGroup[]>(() => [
|
||||
{
|
||||
name: 'common',
|
||||
children: [
|
||||
{
|
||||
action: ACTION_ADD,
|
||||
id: 'add-btn',
|
||||
className: 'add-btn',
|
||||
buttonType: 'label',
|
||||
label: t('views.autoprobe.navActions.new.label'),
|
||||
tooltip: t('views.autoprobe.navActions.new.tooltip'),
|
||||
icon: getIconByAction(ACTION_ADD),
|
||||
onClick: () => {
|
||||
commit(`${ns}/showDialog`, 'create');
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
action: ACTION_FILTER,
|
||||
name: 'filter',
|
||||
children: [
|
||||
{
|
||||
action: ACTION_FILTER_SEARCH,
|
||||
id: 'filter-search',
|
||||
className: 'search',
|
||||
placeholder: t(
|
||||
'views.autoprobe.navActions.filter.search.placeholder'
|
||||
),
|
||||
onChange: onListFilterChangeByKey(
|
||||
store,
|
||||
ns,
|
||||
'name',
|
||||
FILTER_OP_CONTAINS
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
// table columns
|
||||
const tableColumns = computed<TableColumns<AutoProbe>>(
|
||||
() =>
|
||||
[
|
||||
{
|
||||
className: 'name',
|
||||
key: 'name',
|
||||
label: t('views.autoprobe.table.columns.name'),
|
||||
icon: ['fa', 'font'],
|
||||
width: '150',
|
||||
value: (row: AutoProbe) => (
|
||||
<ClNavLink path={`/autoprobes/${row._id}`} label={row.name} />
|
||||
),
|
||||
hasSort: true,
|
||||
hasFilter: true,
|
||||
allowFilterSearch: true,
|
||||
},
|
||||
{
|
||||
key: 'url',
|
||||
label: t('views.autoprobe.table.columns.url'),
|
||||
icon: ['fa', 'at'],
|
||||
width: 'auto',
|
||||
value: (row: AutoProbe) => (
|
||||
<ClNavLink path={row.url} label={row.url} external />
|
||||
),
|
||||
hasFilter: true,
|
||||
allowFilterSearch: true,
|
||||
},
|
||||
{
|
||||
key: TABLE_COLUMN_NAME_ACTIONS,
|
||||
label: t('components.table.columns.actions'),
|
||||
fixed: 'right',
|
||||
width: '150',
|
||||
buttons: [
|
||||
{
|
||||
tooltip: t('common.actions.view'),
|
||||
onClick: async row => {
|
||||
await router.push(`/autoprobes/${row._id}`);
|
||||
},
|
||||
action: ACTION_VIEW,
|
||||
},
|
||||
{
|
||||
tooltip: t('common.actions.run'),
|
||||
onClick: async row => {
|
||||
await ElMessageBox.confirm(
|
||||
t('common.messageBox.confirm.run'),
|
||||
t('common.actions.restart'),
|
||||
{
|
||||
type: 'warning',
|
||||
confirmButtonClass: 'confirm-btn',
|
||||
}
|
||||
);
|
||||
try {
|
||||
await store.dispatch(`${ns}/runTask`, { id: row._id });
|
||||
ElMessage.success(t('common.message.success.run'));
|
||||
} catch (e) {
|
||||
ElMessage.error((e as Error).message);
|
||||
}
|
||||
},
|
||||
action: ACTION_RUN,
|
||||
contextMenu: true,
|
||||
},
|
||||
{
|
||||
tooltip: t('common.actions.delete'),
|
||||
onClick: deleteByIdConfirm,
|
||||
action: ACTION_DELETE,
|
||||
contextMenu: true,
|
||||
},
|
||||
],
|
||||
disableTransfer: true,
|
||||
},
|
||||
] as TableColumns<AutoProbe>
|
||||
);
|
||||
|
||||
return {
|
||||
...useList<AutoProbe>(ns, store),
|
||||
navActions,
|
||||
tableColumns,
|
||||
};
|
||||
};
|
||||
|
||||
export default useAutoProbeList;
|
||||
@@ -35,7 +35,6 @@ const useEnvironmentList = () => {
|
||||
label: t('views.environment.navActions.new.label'),
|
||||
tooltip: t('views.environment.navActions.new.tooltip'),
|
||||
icon: ['fa', 'plus'],
|
||||
type: 'success',
|
||||
onClick: async () => {
|
||||
commit(`${ns}/showDialog`, 'create');
|
||||
},
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import AutoProbeDetail from './autoprobe/detail/AutoProbeDetail.vue';
|
||||
import AutoProbeDetailTabOverview from './autoprobe/detail/tabs/AutoProbeDetailTabOverview.vue';
|
||||
import AutoProbeDetailTabTasks from './autoprobe/detail/tabs/AutoProbeDetailTabTasks.vue';
|
||||
import AutoProbeList from './autoprobe/list/AutoProbeList.vue';
|
||||
import DatabaseDetail from './database/detail/DatabaseDetail.vue';
|
||||
import DatabaseDetailActionsCommon from './database/detail/actions/DatabaseDetailActionsCommon.vue';
|
||||
import DatabaseDetailActionsConsole from './database/detail/actions/DatabaseDetailActionsConsole.vue';
|
||||
@@ -87,6 +91,7 @@ import TaskDetailTabLogs from './task/detail/tabs/TaskDetailTabLogs.vue';
|
||||
import TaskDetailTabOverview from './task/detail/tabs/TaskDetailTabOverview.vue';
|
||||
import TaskList from './task/list/TaskList.vue';
|
||||
import TokenList from './token/list/TokenList.vue';
|
||||
import useAutoProbeList from './autoprobe/list/useAutoProbeList';
|
||||
import useDatabaseDetail from './database/detail/useDatabaseDetail';
|
||||
import useDatabaseList from './database/list/useDatabaseList';
|
||||
import useDependencyList from './dependency/list/useDependencyList';
|
||||
@@ -118,6 +123,10 @@ import useUserDetail from './user/detail/useUserDetail';
|
||||
import useUserList from './user/list/useUserList';
|
||||
|
||||
export {
|
||||
AutoProbeDetail as ClAutoProbeDetail,
|
||||
AutoProbeDetailTabOverview as ClAutoProbeDetailTabOverview,
|
||||
AutoProbeDetailTabTasks as ClAutoProbeDetailTabTasks,
|
||||
AutoProbeList as ClAutoProbeList,
|
||||
DatabaseDetail as ClDatabaseDetail,
|
||||
DatabaseDetailActionsCommon as ClDatabaseDetailActionsCommon,
|
||||
DatabaseDetailActionsConsole as ClDatabaseDetailActionsConsole,
|
||||
@@ -207,6 +216,7 @@ export {
|
||||
TaskDetailTabOverview as ClTaskDetailTabOverview,
|
||||
TaskList as ClTaskList,
|
||||
TokenList as ClTokenList,
|
||||
useAutoProbeList as useAutoProbeList,
|
||||
useDatabaseDetail as useDatabaseDetail,
|
||||
useDatabaseList as useDatabaseList,
|
||||
useDependencyList as useDependencyList,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { computed, h } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import { TABLE_COLUMN_NAME_ACTIONS } from '@/constants/table';
|
||||
import { useStore } from 'vuex';
|
||||
import useList from '@/layouts/content/list/useList';
|
||||
|
||||
@@ -1,26 +1,53 @@
|
||||
<script setup lang="tsx">
|
||||
import { ref, computed, onBeforeMount } from 'vue';
|
||||
import { ElSpace, ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { ClSwitch, ClTag, ClNavLink, ClIcon } from '@/components';
|
||||
import { ElSpace, ElMessage, ElMessageBox, ElCheckbox } from 'element-plus';
|
||||
import { ClTag, ClNavLink, ClIcon } from '@/components';
|
||||
import useRequest from '@/services/request';
|
||||
import { getDefaultPagination, plainClone, translate } from '@/utils';
|
||||
import {
|
||||
ACTION_DELETE,
|
||||
ACTION_EDIT,
|
||||
ACTION_VIEW,
|
||||
TABLE_COLUMN_NAME_ACTIONS,
|
||||
} from '@/constants';
|
||||
import { getLLMProviderItems } from '@/utils/ai';
|
||||
|
||||
const t = translate;
|
||||
|
||||
const { getList, put, post, del } = useRequest();
|
||||
const { get, getList, put, post, del } = useRequest();
|
||||
|
||||
const llmProviders = ref<LLMProvider[]>([]);
|
||||
const llmProvidersTotal = ref(0);
|
||||
const form = ref<LLMProvider>();
|
||||
const formRef = ref();
|
||||
|
||||
const settingAI = ref<Setting<SettingAI>>();
|
||||
const defaultProviderId = computed(
|
||||
() => settingAI.value?.value?.default_provider_id
|
||||
);
|
||||
const getSettingAI = async () => {
|
||||
const res = await get('/settings/ai');
|
||||
settingAI.value = res.data;
|
||||
};
|
||||
const updateDefaultProviderId = async (id: string) => {
|
||||
try {
|
||||
const data = {
|
||||
...settingAI.value,
|
||||
value: {
|
||||
...settingAI.value?.value,
|
||||
default_provider_id: id,
|
||||
},
|
||||
};
|
||||
if (!settingAI.value) {
|
||||
await post('/settings/ai', { data });
|
||||
} else {
|
||||
await put('/settings/ai', { data });
|
||||
}
|
||||
await getSettingAI();
|
||||
} catch (e) {
|
||||
ElMessage.error((e as Error).message);
|
||||
}
|
||||
};
|
||||
|
||||
const getLlmProviderItem = (type: LLMProviderType) => {
|
||||
return getLLMProviderItems().find(item => item.type === type);
|
||||
};
|
||||
@@ -54,9 +81,15 @@ const onConfirm = async () => {
|
||||
data: form.value,
|
||||
});
|
||||
} else {
|
||||
await post('/ai/llm/providers', {
|
||||
data: form.value,
|
||||
});
|
||||
const res = await post<any, ResponseWithData<LLMProvider>>(
|
||||
'/ai/llm/providers',
|
||||
{
|
||||
data: form.value,
|
||||
}
|
||||
);
|
||||
if (!settingAI.value?.value?.default_provider_id) {
|
||||
await updateDefaultProviderId(res.data?._id!);
|
||||
}
|
||||
}
|
||||
dialogVisible.value = false;
|
||||
form.value = undefined;
|
||||
@@ -108,29 +141,24 @@ const tableColumns = computed<TableColumns<LLMProvider>>(() => {
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'enabled',
|
||||
label: t('views.system.ai.enabled'),
|
||||
key: 'default',
|
||||
label: t('common.mode.default'),
|
||||
width: '90px',
|
||||
value: (row: LLMProvider) => {
|
||||
const isDefault = row._id === defaultProviderId.value;
|
||||
return (
|
||||
<ClSwitch
|
||||
modelValue={row.enabled}
|
||||
onChange={async (enabled: boolean) => {
|
||||
const originalEnabled = row.enabled;
|
||||
row.enabled = enabled;
|
||||
try {
|
||||
await put(`/ai/llm/providers/${row._id}`, {
|
||||
data: { ...row, enabled },
|
||||
});
|
||||
ElMessage.success(
|
||||
t(
|
||||
`common.message.success.${enabled ? 'enabled' : 'disabled'}`
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
ElMessage.error((e as Error).message);
|
||||
row.enabled = originalEnabled;
|
||||
}
|
||||
<ElCheckbox
|
||||
modelValue={isDefault}
|
||||
disabled={row._id === defaultProviderId.value}
|
||||
onChange={async () => {
|
||||
await ElMessageBox.confirm(
|
||||
t('common.messageBox.confirm.setDefault'),
|
||||
{
|
||||
type: 'warning',
|
||||
}
|
||||
);
|
||||
await updateDefaultProviderId(row._id!);
|
||||
ElMessage.success(t('common.message.success.action'));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -145,7 +173,15 @@ const tableColumns = computed<TableColumns<LLMProvider>>(() => {
|
||||
return (
|
||||
<ElSpace direction="horizontal" gap={8} wrap>
|
||||
{row.models?.map(model => {
|
||||
return <ClTag label={model} />;
|
||||
if (row.default_model === model) {
|
||||
return (
|
||||
<ClTag
|
||||
label={`${model} (${t('common.mode.default')})`}
|
||||
type="warning"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <ClTag label={model} type="primary" />;
|
||||
})}
|
||||
</ElSpace>
|
||||
);
|
||||
@@ -194,7 +230,9 @@ const dialogTitle = computed(() => {
|
||||
});
|
||||
const dialogConfirmLoading = ref(false);
|
||||
|
||||
onBeforeMount(getLLMProviderList);
|
||||
onBeforeMount(async () => {
|
||||
await Promise.all([getSettingAI(), getLLMProviderList()]);
|
||||
});
|
||||
|
||||
defineOptions({ name: 'ClSystemDetailTabModels' });
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user