feat: 更新模型选择组件和聊天组件的输入逻辑
This commit is contained in:
parent
3525e357d3
commit
21b16c7c06
@ -25,6 +25,7 @@
|
|||||||
- [ ] rename database table name, such as skills -> agent_skills, subagents -> agent_subagents, mcp, tool_call, 等等
|
- [ ] rename database table name, such as skills -> agent_skills, subagents -> agent_subagents, mcp, tool_call, 等等
|
||||||
- [x] department 的 id 也不能使用那个索引的 id 来使用了,应该是一个独立的 dept_id,需要确认
|
- [x] department 的 id 也不能使用那个索引的 id 来使用了,应该是一个独立的 dept_id,需要确认
|
||||||
- [ ] allow user config skill, and envs
|
- [ ] allow user config skill, and envs
|
||||||
|
- [ ] 添加 Skill 的权限设计
|
||||||
- [ ] add model retry times to agent context config
|
- [ ] add model retry times to agent context config
|
||||||
- [ ] add user envs when load sandbox
|
- [ ] add user envs when load sandbox
|
||||||
- [ ] Config spacy model 的 load
|
- [ ] Config spacy model 的 load
|
||||||
|
|||||||
@ -7,7 +7,7 @@ from fastapi import APIRouter, Depends, HTTPException
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from server.utils.auth_middleware import get_admin_user, get_db
|
from server.utils.auth_middleware import get_admin_user, get_db, get_required_user
|
||||||
from yuxi.services.model_provider_service import (
|
from yuxi.services.model_provider_service import (
|
||||||
check_credential_status,
|
check_credential_status,
|
||||||
create_provider_config,
|
create_provider_config,
|
||||||
@ -201,7 +201,7 @@ async def refresh_model_cache(
|
|||||||
@model_providers.get("/models/v2")
|
@model_providers.get("/models/v2")
|
||||||
async def get_v2_models(
|
async def get_v2_models(
|
||||||
model_type: str = "chat",
|
model_type: str = "chat",
|
||||||
current_user: User = Depends(get_admin_user),
|
_current_user: User = Depends(get_required_user),
|
||||||
):
|
):
|
||||||
"""获取 v2 格式的模型列表,按 provider 分组。
|
"""获取 v2 格式的模型列表,按 provider 分组。
|
||||||
|
|
||||||
|
|||||||
@ -108,7 +108,7 @@ export const modelProviderApi = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getV2Models: async (modelType = 'chat') => {
|
getV2Models: async (modelType = 'chat') => {
|
||||||
return apiAdminGet(`/api/system/model-providers/models/v2?model_type=${modelType}`)
|
return apiGet(`/api/system/model-providers/models/v2?model_type=${modelType}`)
|
||||||
},
|
},
|
||||||
|
|
||||||
getCacheStatus: async () => {
|
getCacheStatus: async () => {
|
||||||
|
|||||||
@ -120,6 +120,9 @@
|
|||||||
<template #actions-left-extra>
|
<template #actions-left-extra>
|
||||||
<slot name="input-actions-left" :has-active-thread="!!currentChatId"></slot>
|
<slot name="input-actions-left" :has-active-thread="!!currentChatId"></slot>
|
||||||
</template>
|
</template>
|
||||||
|
<template #actions-right-extra>
|
||||||
|
<slot name="input-actions-right" :has-active-thread="!!currentChatId"></slot>
|
||||||
|
</template>
|
||||||
</AgentInputArea>
|
</AgentInputArea>
|
||||||
|
|
||||||
<div class="bottom-actions" v-if="conversations.length > 0">
|
<div class="bottom-actions" v-if="conversations.length > 0">
|
||||||
@ -205,7 +208,8 @@ import AgentPanel from '@/components/AgentPanel.vue'
|
|||||||
// ==================== PROPS & EMITS ====================
|
// ==================== PROPS & EMITS ====================
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
agentId: { type: String, default: '' },
|
agentId: { type: String, default: '' },
|
||||||
singleMode: { type: Boolean, default: true }
|
singleMode: { type: Boolean, default: true },
|
||||||
|
sendDisabled: { type: Boolean, default: false }
|
||||||
})
|
})
|
||||||
const emit = defineEmits(['thread-change'])
|
const emit = defineEmits(['thread-change'])
|
||||||
|
|
||||||
@ -581,7 +585,9 @@ const isReplyLoading = computed(() => {
|
|||||||
})
|
})
|
||||||
const isSendButtonDisabled = computed(() => {
|
const isSendButtonDisabled = computed(() => {
|
||||||
return (
|
return (
|
||||||
sendCooldownActive.value || ((!userInput.value || !currentAgent.value) && !isProcessing.value)
|
sendCooldownActive.value ||
|
||||||
|
(props.sendDisabled && !isProcessing.value) ||
|
||||||
|
((!userInput.value || !currentAgent.value) && !isProcessing.value)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1259,7 +1265,13 @@ const selectThreadFromRoute = async (threadId) => {
|
|||||||
const handleSendMessage = async ({ image } = {}) => {
|
const handleSendMessage = async ({ image } = {}) => {
|
||||||
const text = userInput.value.trim()
|
const text = userInput.value.trim()
|
||||||
const imageContent = image?.imageContent || null
|
const imageContent = image?.imageContent || null
|
||||||
if ((!text && !image) || !currentAgent.value || isProcessing.value || sendCooldownActive.value)
|
if (
|
||||||
|
(!text && !image) ||
|
||||||
|
!currentAgent.value ||
|
||||||
|
isProcessing.value ||
|
||||||
|
sendCooldownActive.value ||
|
||||||
|
props.sendDisabled
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
// 发送后进入短暂冷却,防止连续触发停止
|
// 发送后进入短暂冷却,防止连续触发停止
|
||||||
@ -1457,6 +1469,7 @@ const handleSendOrStop = async (payload) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (props.sendDisabled) return
|
||||||
await handleSendMessage(payload)
|
await handleSendMessage(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -77,6 +77,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #actions-left>
|
<template #actions-left>
|
||||||
<div class="input-actions-left">
|
<div class="input-actions-left">
|
||||||
|
<slot name="actions-left-extra"></slot>
|
||||||
<a-popover
|
<a-popover
|
||||||
v-if="showTodoEntry"
|
v-if="showTodoEntry"
|
||||||
v-model:open="todoPopoverOpen"
|
v-model:open="todoPopoverOpen"
|
||||||
@ -133,7 +134,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #actions-right>
|
<template #actions-right>
|
||||||
<div class="input-actions-right">
|
<div class="input-actions-right">
|
||||||
<slot name="actions-left-extra"></slot>
|
<slot name="actions-right-extra"></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</MessageInputComponent>
|
</MessageInputComponent>
|
||||||
@ -401,7 +402,7 @@ const getTodoStatusLabel = (status) => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
padding: 6px 8px;
|
padding: 6px 8px;
|
||||||
// height: 28px;
|
height: 30px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: var(--gray-600);
|
color: var(--gray-600);
|
||||||
|
|||||||
@ -218,7 +218,7 @@
|
|||||||
size="small"
|
size="small"
|
||||||
type="text"
|
type="text"
|
||||||
danger
|
danger
|
||||||
v-if="!isBuildActive"
|
v-if="graphBuildStatus?.locked && !isBuildActive"
|
||||||
@click="confirmResetGraph"
|
@click="confirmResetGraph"
|
||||||
>重置</a-button
|
>重置</a-button
|
||||||
>
|
>
|
||||||
@ -378,7 +378,7 @@ const extractorTypeOptions = [
|
|||||||
{
|
{
|
||||||
value: 'spacy',
|
value: 'spacy',
|
||||||
label: 'spaCy',
|
label: 'spaCy',
|
||||||
description: '使用本地 NER 模型抽取实体',
|
description: '【Beta】使用本地 NER 模型抽取实体',
|
||||||
icon: ScanText
|
icon: ScanText
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,18 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-dropdown trigger="click" :open="dropdownOpen" @open-change="handleOpenChange">
|
<a-dropdown trigger="click" :open="dropdownOpen" :disabled="props.disabled" @open-change="handleOpenChange">
|
||||||
<div class="model-select" :class="modelSelectClasses" @click.prevent>
|
<div class="model-select" :class="modelSelectClasses" @click.prevent.stop @mousedown.stop>
|
||||||
<div class="model-select-content">
|
<div class="model-select-content">
|
||||||
<div class="model-info">
|
<div class="model-info">
|
||||||
<a-tooltip :title="displayModelTooltip" placement="right">
|
<span class="model-text text" :title="displayModelTitle">{{ displayModelText }}</span>
|
||||||
<span class="model-text text"> {{ displayModelText }} </span>
|
|
||||||
</a-tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="model-status-controls">
|
<div v-if="resolvedSize !== 'nano'" class="model-status-controls">
|
||||||
<span
|
<span
|
||||||
v-if="state.currentModelStatus"
|
v-if="state.currentModelStatus"
|
||||||
class="model-status-indicator"
|
class="model-status-indicator"
|
||||||
:class="state.currentModelStatus.status"
|
:class="state.currentModelStatus.status"
|
||||||
:title="getCurrentModelStatusTooltip()"
|
:title="getCurrentModelStatusTitle()"
|
||||||
>
|
>
|
||||||
{{ modelStatusIcon }}
|
{{ modelStatusIcon }}
|
||||||
</span>
|
</span>
|
||||||
@ -21,7 +19,7 @@
|
|||||||
type="text"
|
type="text"
|
||||||
:loading="state.checkingStatus"
|
:loading="state.checkingStatus"
|
||||||
@click.stop="checkCurrentModelStatus"
|
@click.stop="checkCurrentModelStatus"
|
||||||
:disabled="state.checkingStatus"
|
:disabled="props.disabled || state.checkingStatus"
|
||||||
class="status-check-button"
|
class="status-check-button"
|
||||||
>
|
>
|
||||||
{{ state.checkingStatus ? '检查中...' : '检查' }}
|
{{ state.checkingStatus ? '检查中...' : '检查' }}
|
||||||
@ -34,16 +32,15 @@
|
|||||||
<div class="model-search">
|
<div class="model-search">
|
||||||
<a-input v-model:value="modelSearchKeyword" placeholder="搜索模型" allow-clear @keydown.stop>
|
<a-input v-model:value="modelSearchKeyword" placeholder="搜索模型" allow-clear @keydown.stop>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<a-tooltip title="刷新缓存">
|
<button
|
||||||
<button
|
:disabled="props.disabled || state.refreshingCache"
|
||||||
:disabled="state.refreshingCache"
|
:title="state.refreshingCache ? '刷新中...' : '刷新缓存'"
|
||||||
class="cache-refresh-button"
|
class="cache-refresh-button"
|
||||||
@mousedown.prevent.stop
|
@mousedown.prevent.stop
|
||||||
@click.stop="refreshCache"
|
@click.stop="refreshCache"
|
||||||
>
|
>
|
||||||
<RefreshCw :size="13" :class="{ spin: state.refreshingCache }" />
|
<RefreshCw :size="13" :class="{ spin: state.refreshingCache }" />
|
||||||
</button>
|
</button>
|
||||||
</a-tooltip>
|
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</div>
|
</div>
|
||||||
@ -91,7 +88,11 @@ const props = defineProps({
|
|||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'small',
|
default: 'small',
|
||||||
validator: (value) => ['small', 'middle', 'large'].includes(value)
|
validator: (value) => ['nano', 'small', 'middle', 'large'].includes(value)
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
},
|
},
|
||||||
displayName: {
|
displayName: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -151,13 +152,17 @@ const fetchV2Models = async () => {
|
|||||||
|
|
||||||
// 下拉展开时触发实时刷新(仅在打开瞬间触发,关闭时忽略)
|
// 下拉展开时触发实时刷新(仅在打开瞬间触发,关闭时忽略)
|
||||||
const handleOpenChange = (open) => {
|
const handleOpenChange = (open) => {
|
||||||
|
if (props.disabled) {
|
||||||
|
dropdownOpen.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
dropdownOpen.value = open
|
dropdownOpen.value = open
|
||||||
if (open) fetchV2Models()
|
if (open) fetchV2Models()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 强制刷新缓存
|
// 强制刷新缓存
|
||||||
const refreshCache = async () => {
|
const refreshCache = async () => {
|
||||||
if (state.refreshingCache) return
|
if (props.disabled || state.refreshingCache) return
|
||||||
state.refreshingCache = true
|
state.refreshingCache = true
|
||||||
try {
|
try {
|
||||||
await modelProviderApi.refreshModelCache()
|
await modelProviderApi.refreshModelCache()
|
||||||
@ -180,8 +185,10 @@ const state = reactive({
|
|||||||
|
|
||||||
const resolvedSize = computed(() => props.size || 'small')
|
const resolvedSize = computed(() => props.size || 'small')
|
||||||
const modelSelectClasses = computed(() => ({
|
const modelSelectClasses = computed(() => ({
|
||||||
|
'model-select--nano': resolvedSize.value === 'nano',
|
||||||
'model-select--middle': resolvedSize.value === 'middle',
|
'model-select--middle': resolvedSize.value === 'middle',
|
||||||
'model-select--large': resolvedSize.value === 'large'
|
'model-select--large': resolvedSize.value === 'large',
|
||||||
|
'model-select--disabled': props.disabled
|
||||||
}))
|
}))
|
||||||
const buttonSize = computed(() => {
|
const buttonSize = computed(() => {
|
||||||
if (resolvedSize.value === 'large') return 'large'
|
if (resolvedSize.value === 'large') return 'large'
|
||||||
@ -206,10 +213,11 @@ const displayModelText = computed(() => {
|
|||||||
return spec
|
return spec
|
||||||
})
|
})
|
||||||
|
|
||||||
const displayModelTooltip = computed(() => props.model_spec || props.placeholder)
|
const displayModelTitle = computed(() => props.model_spec || props.placeholder)
|
||||||
|
|
||||||
// 检查当前模型状态
|
// 检查当前模型状态
|
||||||
const checkCurrentModelStatus = async () => {
|
const checkCurrentModelStatus = async () => {
|
||||||
|
if (props.disabled) return
|
||||||
const spec = props.model_spec
|
const spec = props.model_spec
|
||||||
if (!spec) return
|
if (!spec) return
|
||||||
|
|
||||||
@ -238,7 +246,7 @@ const modelStatusIcon = computed(() => {
|
|||||||
return '○'
|
return '○'
|
||||||
})
|
})
|
||||||
|
|
||||||
const getCurrentModelStatusTooltip = () => {
|
const getCurrentModelStatusTitle = () => {
|
||||||
const status = state.currentModelStatus
|
const status = state.currentModelStatus
|
||||||
if (!status) return '状态未知'
|
if (!status) return '状态未知'
|
||||||
|
|
||||||
@ -253,6 +261,7 @@ const getCurrentModelStatusTooltip = () => {
|
|||||||
|
|
||||||
// 选择 v2 模型的方法
|
// 选择 v2 模型的方法
|
||||||
const handleSelectV2Model = (spec) => {
|
const handleSelectV2Model = (spec) => {
|
||||||
|
if (props.disabled) return
|
||||||
emit('select-model', spec)
|
emit('select-model', spec)
|
||||||
dropdownOpen.value = false
|
dropdownOpen.value = false
|
||||||
}
|
}
|
||||||
@ -282,6 +291,34 @@ const handleSelectV2Model = (spec) => {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.model-select--nano {
|
||||||
|
max-width: 100%;
|
||||||
|
height: 30px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1;
|
||||||
|
color: var(--gray-600);
|
||||||
|
background: transparent;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-select--nano:hover {
|
||||||
|
color: var(--gray-900);
|
||||||
|
background: var(--gray-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-select--nano .model-text {
|
||||||
|
color: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-select--disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
.model-dropdown {
|
.model-dropdown {
|
||||||
min-width: 280px;
|
min-width: 280px;
|
||||||
max-width: 420px;
|
max-width: 420px;
|
||||||
@ -302,5 +339,24 @@ const handleSelectV2Model = (spec) => {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-dropdown-menu-item,
|
||||||
|
.ant-dropdown-menu-submenu-title {
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-dropdown-menu-item:hover,
|
||||||
|
.ant-dropdown-menu-submenu-title:hover,
|
||||||
|
.ant-dropdown-menu-item-active,
|
||||||
|
.ant-dropdown-menu-submenu-title-active {
|
||||||
|
background: var(--gray-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-dropdown-menu-item-selected,
|
||||||
|
.ant-dropdown-menu-item-selected:hover,
|
||||||
|
.ant-dropdown-menu-item-selected.ant-dropdown-menu-item-active {
|
||||||
|
color: var(--gray-1000);
|
||||||
|
background: var(--main-50);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -6,9 +6,22 @@
|
|||||||
<AgentChatComponent
|
<AgentChatComponent
|
||||||
ref="chatComponentRef"
|
ref="chatComponentRef"
|
||||||
:single-mode="false"
|
:single-mode="false"
|
||||||
|
:send-disabled="isSavingInputModel"
|
||||||
@thread-change="handleThreadChange"
|
@thread-change="handleThreadChange"
|
||||||
>
|
>
|
||||||
<template #input-actions-left="{ hasActiveThread }">
|
<template #input-actions-left>
|
||||||
|
<div v-if="showInputModelSelector" class="input-model-selector">
|
||||||
|
<ModelSelectorComponent
|
||||||
|
:model_spec="currentInputModelSpec"
|
||||||
|
:disabled="isInputModelSelectorDisabled"
|
||||||
|
size="nano"
|
||||||
|
display-name="mini"
|
||||||
|
@select-model="handleInputModelChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #input-actions-right="{ hasActiveThread }">
|
||||||
<a-dropdown
|
<a-dropdown
|
||||||
v-if="selectedAgentId"
|
v-if="selectedAgentId"
|
||||||
v-model:open="agentDropdownOpen"
|
v-model:open="agentDropdownOpen"
|
||||||
@ -146,6 +159,7 @@ import { message } from 'ant-design-vue'
|
|||||||
import { Settings2, Ellipsis, ChevronDown, Check, FolderKanban } from 'lucide-vue-next'
|
import { Settings2, Ellipsis, ChevronDown, Check, FolderKanban } from 'lucide-vue-next'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import AgentChatComponent from '@/components/AgentChatComponent.vue'
|
import AgentChatComponent from '@/components/AgentChatComponent.vue'
|
||||||
|
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue'
|
||||||
import FeedbackModalComponent from '@/components/dashboard/FeedbackModalComponent.vue'
|
import FeedbackModalComponent from '@/components/dashboard/FeedbackModalComponent.vue'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import { isBuiltinAgent, useAgentStore } from '@/stores/agent'
|
import { isBuiltinAgent, useAgentStore } from '@/stores/agent'
|
||||||
@ -169,9 +183,11 @@ const route = useRoute()
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
// 从 agentStore 中获取响应式状态
|
// 从 agentStore 中获取响应式状态
|
||||||
const { agents, selectedAgentId, isLoadingConfig } = storeToRefs(agentStore)
|
const { agents, selectedAgentId, selectedAgent, agentConfig, configurableItems, isLoadingConfig } =
|
||||||
|
storeToRefs(agentStore)
|
||||||
|
|
||||||
const syncingRouteThread = ref(false)
|
const syncingRouteThread = ref(false)
|
||||||
|
const isSavingInputModel = ref(false)
|
||||||
|
|
||||||
const getRouteThreadId = () => {
|
const getRouteThreadId = () => {
|
||||||
const value = route.params.thread_id
|
const value = route.params.thread_id
|
||||||
@ -246,6 +262,42 @@ const currentAgentLabel = computed(() => {
|
|||||||
|
|
||||||
const currentAgentIcon = computed(() => currentAgentOption.value?.icon || defaultAgentIcon)
|
const currentAgentIcon = computed(() => currentAgentOption.value?.icon || defaultAgentIcon)
|
||||||
|
|
||||||
|
const inputModelKey = computed(() => {
|
||||||
|
if (configurableItems.value?.model?.kind === 'llm') return 'model'
|
||||||
|
return (
|
||||||
|
Object.entries(configurableItems.value || {}).find(
|
||||||
|
([key, item]) => key !== 'subagents_model' && item?.kind === 'llm'
|
||||||
|
)?.[0] || ''
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentInputModelSpec = computed(() => {
|
||||||
|
const key = inputModelKey.value
|
||||||
|
return key ? agentConfig.value?.[key] || '' : ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const showInputModelSelector = computed(() => Boolean(selectedAgentId.value && inputModelKey.value))
|
||||||
|
const isInputModelSelectorDisabled = computed(
|
||||||
|
() => isLoadingConfig.value || isSavingInputModel.value || !selectedAgent.value?.can_manage
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleInputModelChange = async (spec) => {
|
||||||
|
const key = inputModelKey.value
|
||||||
|
if (!key || typeof spec !== 'string' || !spec || spec === currentInputModelSpec.value) return
|
||||||
|
if (isInputModelSelectorDisabled.value) return
|
||||||
|
|
||||||
|
const previousSpec = currentInputModelSpec.value
|
||||||
|
isSavingInputModel.value = true
|
||||||
|
try {
|
||||||
|
agentStore.updateAgentConfig({ [key]: spec })
|
||||||
|
await agentStore.saveAgentConfig()
|
||||||
|
} catch {
|
||||||
|
agentStore.updateAgentConfig({ [key]: previousSpec })
|
||||||
|
} finally {
|
||||||
|
isSavingInputModel.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const agentDropdownOpen = ref(false)
|
const agentDropdownOpen = ref(false)
|
||||||
|
|
||||||
const handleAgentSwitch = async (agentId, hasActiveThread) => {
|
const handleAgentSwitch = async (agentId, hasActiveThread) => {
|
||||||
@ -375,6 +427,14 @@ const handleFeedback = () => {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-model-selector {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: min(168px, calc(100vw - 160px));
|
||||||
|
}
|
||||||
|
|
||||||
.config-dropdown-trigger {
|
.config-dropdown-trigger {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -391,6 +451,7 @@ const handleFeedback = () => {
|
|||||||
.config-dropdown-agent-icon {
|
.config-dropdown-agent-icon {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
|
border-radius: 3px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
@ -585,6 +646,7 @@ const handleFeedback = () => {
|
|||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.config-dropdown-overlay .config-dropdown-item-badge {
|
.config-dropdown-overlay .config-dropdown-item-badge {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user