Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 24d1dbb5f3 | |||
| 7f2bf5a0db | |||
| f22221f781 | |||
| 4250d997b3 | |||
| 153d8cef6b |
@@ -88,8 +88,11 @@ class LocalPythonTool(FunctionTool):
|
||||
self, context: ContextWrapper[AstrAgentContext], code: str, silent: bool = False
|
||||
) -> ToolExecResult:
|
||||
if context.context.event.role != "admin":
|
||||
return "error: Permission denied. Local Python execution is only allowed for admin users. Tell user to set admins in AstrBot WebUI."
|
||||
|
||||
return (
|
||||
"error: Permission denied. Local Python execution is only allowed for admin users. "
|
||||
"Tell user to set admins in AstrBot WebUI by adding their user ID to the admins list if they need this feature."
|
||||
f"User's ID is: {context.context.event.get_sender_id()}. User's ID can be found by using /sid command."
|
||||
)
|
||||
sb = get_local_booter()
|
||||
try:
|
||||
result = await sb.python.exec(code, silent=silent)
|
||||
|
||||
@@ -47,7 +47,11 @@ class ExecuteShellTool(FunctionTool):
|
||||
env: dict = {},
|
||||
) -> ToolExecResult:
|
||||
if context.context.event.role != "admin":
|
||||
return "error: Permission denied. Shell execution is only allowed for admin users. Tell user to Set admins in AstrBot WebUI."
|
||||
return (
|
||||
"error: Permission denied. Local shell execution is only allowed for admin users. "
|
||||
"Tell user to set admins in AstrBot WebUI by adding their user ID to the admins list if they need this feature."
|
||||
f"User's ID is: {context.context.event.get_sender_id()}. User's ID can be found by using /sid command."
|
||||
)
|
||||
|
||||
if self.is_local:
|
||||
sb = get_local_booter()
|
||||
|
||||
@@ -1029,6 +1029,18 @@ CONFIG_METADATA_2 = {
|
||||
"proxy": "",
|
||||
"custom_headers": {},
|
||||
},
|
||||
"NVIDIA": {
|
||||
"id": "nvidia",
|
||||
"provider": "nvidia",
|
||||
"type": "openai_chat_completion",
|
||||
"provider_type": "chat_completion",
|
||||
"enable": True,
|
||||
"key": [],
|
||||
"api_base": "https://integrate.api.nvidia.com/v1",
|
||||
"timeout": 120,
|
||||
"proxy": "",
|
||||
"custom_headers": {},
|
||||
},
|
||||
"Azure OpenAI": {
|
||||
"id": "azure_openai",
|
||||
"provider": "azure",
|
||||
|
||||
@@ -14,8 +14,13 @@
|
||||
</div>
|
||||
|
||||
<!-- 选择对话框 -->
|
||||
<v-dialog v-model="dialog" max-width="1000px" min-width="800px">
|
||||
<v-card class="selector-dialog-card">
|
||||
<v-dialog
|
||||
v-model="dialog"
|
||||
:max-width="$vuetify.display.smAndDown ? undefined : '1000px'"
|
||||
:min-width="$vuetify.display.smAndDown ? undefined : '800px'"
|
||||
scrollable
|
||||
>
|
||||
<v-card class="selector-dialog-card" :class="{ 'selector-dialog-card-mobile': $vuetify.display.smAndDown }">
|
||||
<v-card-title class="dialog-title d-flex align-center py-4 px-5">
|
||||
<v-icon class="mr-3" color="primary">mdi-account-circle</v-icon>
|
||||
<span>{{ labels.dialogTitle || '选择项目' }}</span>
|
||||
@@ -23,7 +28,7 @@
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-text class="pa-0" style="height: 600px; max-height: 80vh; overflow: hidden;">
|
||||
<v-card-text class="selector-dialog-content pa-0">
|
||||
<div class="selector-layout">
|
||||
<!-- 左侧文件夹树 -->
|
||||
<div class="folder-sidebar">
|
||||
@@ -146,7 +151,7 @@
|
||||
</div>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions class="pa-4">
|
||||
<v-card-actions class="selector-dialog-actions pa-4">
|
||||
<v-btn v-if="showCreateButton" variant="text" color="primary" prepend-icon="mdi-plus"
|
||||
@click="$emit('create')">
|
||||
{{ labels.createButton || '新建' }}
|
||||
@@ -406,6 +411,12 @@ export default defineComponent({
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.selector-dialog-content {
|
||||
height: 600px;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
@@ -518,21 +529,44 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.selector-dialog-card-mobile {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.selector-dialog-content {
|
||||
height: calc(100vh - 132px);
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
font-size: 1.05rem;
|
||||
padding: 12px 16px !important;
|
||||
}
|
||||
|
||||
.selector-dialog-actions {
|
||||
padding: 12px 16px !important;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.selector-dialog-actions .v-btn {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.selector-layout {
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
max-height: 500px;
|
||||
height: 100%;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.folder-sidebar {
|
||||
width: 100%;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
||||
max-height: 150px;
|
||||
max-height: 35vh;
|
||||
}
|
||||
|
||||
.items-list {
|
||||
max-height: 300px;
|
||||
max-height: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="d-flex align-center ga-2">
|
||||
<h3 class="mb-0">{{ tm('providerSources.title') }}</h3>
|
||||
</div>
|
||||
<v-menu>
|
||||
<StyledMenu>
|
||||
<template #activator="{ props }">
|
||||
<v-btn
|
||||
v-bind="props"
|
||||
@@ -17,19 +17,61 @@
|
||||
{{ tm('providerSources.add') }}
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list density="compact">
|
||||
<v-list-item
|
||||
v-for="sourceType in availableSourceTypes"
|
||||
:key="sourceType.value"
|
||||
@click="emitAddSource(sourceType.value)"
|
||||
>
|
||||
<v-list-item-title>{{ sourceType.label }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<v-list-item
|
||||
v-for="sourceType in availableSourceTypes"
|
||||
:key="sourceType.value"
|
||||
class="styled-menu-item"
|
||||
@click="emitAddSource(sourceType.value)"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-avatar size="18" rounded="0" class="me-2">
|
||||
<v-img v-if="sourceType.icon" :src="sourceType.icon" alt="provider icon" cover></v-img>
|
||||
<v-icon v-else size="16">mdi-shape-outline</v-icon>
|
||||
</v-avatar>
|
||||
</template>
|
||||
<v-list-item-title>{{ sourceType.label }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</StyledMenu>
|
||||
</div>
|
||||
|
||||
<div v-if="displayedProviderSources.length > 0">
|
||||
<div v-if="isMobile && displayedProviderSources.length > 0" class="px-4 pb-3">
|
||||
<div class="d-flex align-center ga-2">
|
||||
<v-select
|
||||
:model-value="selectedId"
|
||||
:items="mobileSourceItems"
|
||||
item-title="label"
|
||||
item-value="value"
|
||||
:label="tm('providerSources.selectCreated')"
|
||||
variant="solo-filled"
|
||||
density="comfortable"
|
||||
flat
|
||||
hide-details
|
||||
class="mobile-source-select"
|
||||
@update:model-value="onMobileSourceChange"
|
||||
>
|
||||
<template #item="{ props: itemProps, item }">
|
||||
<v-list-item v-bind="itemProps">
|
||||
<template #prepend>
|
||||
<v-avatar size="18" rounded="0" class="me-2">
|
||||
<v-img v-if="item.raw.icon" :src="item.raw.icon" alt="provider icon" cover></v-img>
|
||||
<v-icon v-else size="16">mdi-shape-outline</v-icon>
|
||||
</v-avatar>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-select>
|
||||
<v-btn
|
||||
v-if="selectedProviderSource"
|
||||
icon="mdi-delete"
|
||||
variant="text"
|
||||
size="small"
|
||||
color="error"
|
||||
@click.stop="emitDeleteSource(selectedProviderSource)"
|
||||
></v-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="displayedProviderSources.length > 0">
|
||||
<v-list class="provider-source-list" nav density="compact" lines="two">
|
||||
<v-list-item
|
||||
v-for="source in displayedProviderSources"
|
||||
@@ -46,7 +88,7 @@
|
||||
<v-icon v-else size="32">mdi-creation</v-icon>
|
||||
</v-avatar>
|
||||
</template>
|
||||
<v-list-item-title class="font-weight-bold">{{ getSourceDisplayName(source) }}</v-list-item-title>
|
||||
<v-list-item-title class="font-weight-bold mb-1" style="font-family: Arial, Helvetica, sans-serif; font-size: 16px;">{{ getSourceDisplayName(source) }}</v-list-item-title>
|
||||
<v-list-item-subtitle class="text-truncate">{{ source.api_base || 'N/A' }}</v-list-item-subtitle>
|
||||
<template #append>
|
||||
<div class="d-flex align-center ga-1">
|
||||
@@ -72,6 +114,8 @@
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import StyledMenu from '@/components/shared/StyledMenu.vue'
|
||||
|
||||
const props = defineProps({
|
||||
displayedProviderSources: {
|
||||
@@ -106,13 +150,30 @@ const emit = defineEmits([
|
||||
'delete-provider-source'
|
||||
])
|
||||
|
||||
const { smAndDown } = useDisplay()
|
||||
const selectedId = computed(() => props.selectedProviderSource?.id || null)
|
||||
const isMobile = computed(() => smAndDown.value)
|
||||
const mobileSourceItems = computed(() =>
|
||||
(props.displayedProviderSources || []).map((source) => ({
|
||||
value: source.id,
|
||||
label: props.getSourceDisplayName(source),
|
||||
icon: props.resolveSourceIcon(source),
|
||||
source
|
||||
}))
|
||||
)
|
||||
|
||||
const isActive = (source) => {
|
||||
if (source.isPlaceholder) return false
|
||||
return selectedId.value !== null && selectedId.value === source.id
|
||||
}
|
||||
|
||||
const onMobileSourceChange = (sourceId) => {
|
||||
const matched = mobileSourceItems.value.find((item) => item.value === sourceId)
|
||||
if (matched?.source) {
|
||||
emitSelectSource(matched.source)
|
||||
}
|
||||
}
|
||||
|
||||
const emitAddSource = (type) => emit('add-provider-source', type)
|
||||
const emitSelectSource = (source) => emit('select-provider-source', source)
|
||||
const emitDeleteSource = (source) => emit('delete-provider-source', source)
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
<template>
|
||||
<v-dialog v-model="showDialog" max-width="500px">
|
||||
<v-card>
|
||||
<v-card-title class="text-h2">
|
||||
<v-dialog
|
||||
v-model="showDialog"
|
||||
:max-width="$vuetify.display.smAndDown ? undefined : '760px'"
|
||||
scrollable
|
||||
>
|
||||
<v-card class="persona-form-card" :class="{ 'persona-form-card-mobile': $vuetify.display.smAndDown }">
|
||||
<v-card-title class="persona-form-title text-h2">
|
||||
{{ editingPersona ? tm('dialog.edit.title') : tm('dialog.create.title') }}
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
<v-card-text class="persona-form-content">
|
||||
<!-- 创建位置提示 -->
|
||||
<v-alert
|
||||
v-if="!editingPersona"
|
||||
@@ -51,7 +55,7 @@
|
||||
</v-radio>
|
||||
</v-radio-group>
|
||||
|
||||
<div v-if="toolSelectValue === '1'" class="mt-3 ml-8">
|
||||
<div v-if="toolSelectValue === '1'" class="mt-3 selected-config-area">
|
||||
|
||||
<!-- 工具搜索 -->
|
||||
<v-text-field v-model="toolSearch" :label="tm('form.searchTools')"
|
||||
@@ -178,7 +182,7 @@
|
||||
<v-radio :label="tm('form.skillsSelectSpecific')" value="1"></v-radio>
|
||||
</v-radio-group>
|
||||
|
||||
<div v-if="skillSelectValue === '1'" class="mt-3 ml-8">
|
||||
<div v-if="skillSelectValue === '1'" class="mt-3 selected-config-area">
|
||||
<v-text-field v-model="skillSearch" :label="tm('form.searchSkills')"
|
||||
prepend-inner-icon="mdi-magnify" variant="outlined" density="compact"
|
||||
hide-details clearable class="mb-3" />
|
||||
@@ -288,7 +292,7 @@
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-card-actions class="persona-form-actions">
|
||||
<v-btn v-if="editingPersona" color="error" variant="text" @click="deletePersona">
|
||||
{{ tm('buttons.delete') }}
|
||||
</v-btn>
|
||||
@@ -799,6 +803,32 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.persona-form-card {
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.persona-form-content {
|
||||
max-height: min(78vh, 760px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.persona-form-title {
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.persona-form-actions {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
z-index: 2;
|
||||
background: rgb(var(--v-theme-surface));
|
||||
border-top: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
||||
}
|
||||
|
||||
.selected-config-area {
|
||||
margin-left: 32px;
|
||||
}
|
||||
|
||||
.tools-selection {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
@@ -812,4 +842,38 @@ export default {
|
||||
.v-virtual-scroll {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.persona-form-card-mobile {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.persona-form-content {
|
||||
max-height: calc(100vh - 128px);
|
||||
padding: 16px !important;
|
||||
}
|
||||
|
||||
.persona-form-title {
|
||||
font-size: 1.15rem !important;
|
||||
padding: 12px 16px !important;
|
||||
}
|
||||
|
||||
.selected-config-area {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.tools-selection,
|
||||
.skills-selection {
|
||||
max-height: 38vh;
|
||||
}
|
||||
|
||||
.persona-form-actions {
|
||||
padding: 12px 16px !important;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.persona-form-actions .v-btn {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -81,10 +81,14 @@ export function useProviderSources(options: UseProviderSourcesOptions) {
|
||||
return []
|
||||
}
|
||||
|
||||
const types: Array<{ value: string; label: string }> = []
|
||||
const types: Array<{ value: string; label: string; icon: string }> = []
|
||||
for (const [templateName, template] of Object.entries(providerTemplates.value)) {
|
||||
if (template.provider_type === selectedProviderType.value) {
|
||||
types.push({ value: templateName, label: templateName })
|
||||
types.push({
|
||||
value: templateName,
|
||||
label: templateName,
|
||||
icon: getProviderIcon(template.provider)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@
|
||||
"add": "Add",
|
||||
"empty": "No provider sources",
|
||||
"selectHint": "Please select a provider source",
|
||||
"selectCreated": "Select created provider source",
|
||||
"save": "Save Configuration",
|
||||
"saveAndFetchModels": "Save and Fetch Models",
|
||||
"fetchModels": "Fetch Model List",
|
||||
@@ -146,4 +147,4 @@
|
||||
"modelId": "Model ID"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@
|
||||
"add": "新增",
|
||||
"empty": "暂无提供商源",
|
||||
"selectHint": "请选择一个提供商源",
|
||||
"selectCreated": "选择已创建的提供商源",
|
||||
"save": "保存配置",
|
||||
"saveAndFetchModels": "保存并获取模型",
|
||||
"fetchModels": "获取模型列表",
|
||||
@@ -147,4 +148,4 @@
|
||||
"modelId": "模型 ID"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export function getProviderIcon(type) {
|
||||
'deepseek': 'https://registry.npmmirror.com/@lobehub/icons-static-svg/latest/files/icons/deepseek.svg',
|
||||
'modelscope': 'https://registry.npmmirror.com/@lobehub/icons-static-svg/latest/files/icons/modelscope.svg',
|
||||
'zhipu': 'https://registry.npmmirror.com/@lobehub/icons-static-svg/latest/files/icons/zhipu.svg',
|
||||
'nvidia': 'https://registry.npmmirror.com/@lobehub/icons-static-svg/latest/files/icons/nvidia-color.svg',
|
||||
'siliconflow': 'https://registry.npmmirror.com/@lobehub/icons-static-svg/latest/files/icons/siliconcloud.svg',
|
||||
'moonshot': 'https://registry.npmmirror.com/@lobehub/icons-static-svg/latest/files/icons/kimi.svg',
|
||||
'ppio': 'https://registry.npmmirror.com/@lobehub/icons-static-svg/latest/files/icons/ppio.svg',
|
||||
|
||||
Reference in New Issue
Block a user