|
@@ -16,7 +16,6 @@ import ComputerPanel from '@/components/workspace/ComputerPanel.vue'
|
|
|
import TaskPlan from '@/components/workspace/TaskPlan.vue'
|
|
import TaskPlan from '@/components/workspace/TaskPlan.vue'
|
|
|
import type { AgentAssignment } from '@shared/agentTasks'
|
|
import type { AgentAssignment } from '@shared/agentTasks'
|
|
|
import type { WorkspaceSendReceipt, WorkspaceTask } from '@shared/workspace'
|
|
import type { WorkspaceSendReceipt, WorkspaceTask } from '@shared/workspace'
|
|
|
-import { grokBotDraft } from '@shared/grokBotImport'
|
|
|
|
|
import { workspaceHasUnread } from '@shared/workspace'
|
|
import { workspaceHasUnread } from '@shared/workspace'
|
|
|
import type { WorkspaceLifecycleAction, WorkspaceLifecyclePreview } from '@shared/workspaceLifecycle'
|
|
import type { WorkspaceLifecycleAction, WorkspaceLifecyclePreview } from '@shared/workspaceLifecycle'
|
|
|
import FloatingResourceSearch from '@/components/app/FloatingResourceSearch.vue'
|
|
import FloatingResourceSearch from '@/components/app/FloatingResourceSearch.vue'
|
|
@@ -199,36 +198,6 @@ const form = reactive({
|
|
|
autoReplyIds: [] as string[],
|
|
autoReplyIds: [] as string[],
|
|
|
maxReplyRounds: 3,
|
|
maxReplyRounds: 3,
|
|
|
})
|
|
})
|
|
|
-const importUrl = ref(''), importing = ref(false), importError = ref(''), pastedDescription = ref('')
|
|
|
|
|
-const importPreview = ref<{ name: string; description: string; imageDataUrl?: string; draft: { kind: string; job?: string; antiJobs?: string[]; actBias?: string; canManageTeam?: boolean; instructions: string; suggestions: string[] } } | null>(null)
|
|
|
|
|
-function applyGrokBotDraft(name: string, description: string, draft: NonNullable<typeof importPreview.value>['draft']) {
|
|
|
|
|
- importPreview.value = { name, description, draft }
|
|
|
|
|
- if (name) form.name = name
|
|
|
|
|
- form.instructions = draft.instructions
|
|
|
|
|
- form.description = description.slice(0, 500)
|
|
|
|
|
- form.job = draft.job ?? ''
|
|
|
|
|
- form.antiJobs = draft.antiJobs ?? []
|
|
|
|
|
- form.actBias = (draft.actBias ?? '') as typeof form.actBias
|
|
|
|
|
- if (draft.canManageTeam) form.canManageTeam = true
|
|
|
|
|
-}
|
|
|
|
|
-async function runGrokBotImport() {
|
|
|
|
|
- if (importing.value) return
|
|
|
|
|
- if (!importUrl.value.trim()) { importError.value = '请粘贴 x.ai/bot/<id> 形式的 Grok Bot 链接'; return }
|
|
|
|
|
- importing.value = true; importError.value = ''
|
|
|
|
|
- try {
|
|
|
|
|
- const result = await apiRequest<{ name: string; description: string; imageDataUrl?: string; draft: NonNullable<typeof importPreview.value>['draft'] }>('/api/app/agents/import-preview', { method: 'POST', body: { url: importUrl.value.trim() } })
|
|
|
|
|
- if (result.imageDataUrl) form.avatar = result.imageDataUrl
|
|
|
|
|
- applyGrokBotDraft(result.name, result.description, result.draft)
|
|
|
|
|
- } catch (cause) { importError.value = cause instanceof Error ? cause.message : '导入失败,请稍后重试' }
|
|
|
|
|
- finally { importing.value = false }
|
|
|
|
|
-}
|
|
|
|
|
-function applyPastedGrokBot() {
|
|
|
|
|
- const text = pastedDescription.value.trim()
|
|
|
|
|
- if (!text) { importError.value = '请粘贴 Bot 的描述文字'; return }
|
|
|
|
|
- importError.value = ''
|
|
|
|
|
- const name = form.name.trim() || '导入的 Bot'
|
|
|
|
|
- applyGrokBotDraft(name, text.slice(0, 1200), grokBotDraft({ name, description: text.slice(0, 1200) }))
|
|
|
|
|
-}
|
|
|
|
|
const selectedPresetId = ref('custom')
|
|
const selectedPresetId = ref('custom')
|
|
|
const selectedPreset = computed(() => dialog.value === 'group' ? TEAM_PRESETS.find(p => p.id === selectedPresetId.value) : undefined)
|
|
const selectedPreset = computed(() => dialog.value === 'group' ? TEAM_PRESETS.find(p => p.id === selectedPresetId.value) : undefined)
|
|
|
function applyPresetRoles(preset: TeamPreset) {
|
|
function applyPresetRoles(preset: TeamPreset) {
|
|
@@ -242,7 +211,6 @@ function choosePreset(preset?: TeamPreset) {
|
|
|
const available = agents.value.filter(a => !a.archived && !a.temporaryGoalId)
|
|
const available = agents.value.filter(a => !a.archived && !a.temporaryGoalId)
|
|
|
if (preset && available.length < preset.roles.length) return
|
|
if (preset && available.length < preset.roles.length) return
|
|
|
selectedPresetId.value = preset?.id ?? 'custom'
|
|
selectedPresetId.value = preset?.id ?? 'custom'
|
|
|
- importUrl.value=''; importPreview.value=null; importError.value=''; pastedDescription.value=''
|
|
|
|
|
Object.assign(form, { name: preset?.name ?? '', instructions: preset?.instructions ?? '', job: '', antiJobs: [], voice: '', voiceCustom: '', actBias: '', memberIds: preset ? available.slice(0, preset.roles.length).map(a => a.id) : [], memberRoles: {}, administratorId: '', mode: knowledgeEnabled.value ? 'discussion' : 'host', autoReplyIds: [], maxReplyRounds: 3 })
|
|
Object.assign(form, { name: preset?.name ?? '', instructions: preset?.instructions ?? '', job: '', antiJobs: [], voice: '', voiceCustom: '', actBias: '', memberIds: preset ? available.slice(0, preset.roles.length).map(a => a.id) : [], memberRoles: {}, administratorId: '', mode: knowledgeEnabled.value ? 'discussion' : 'host', autoReplyIds: [], maxReplyRounds: 3 })
|
|
|
if (preset) applyPresetRoles(preset)
|
|
if (preset) applyPresetRoles(preset)
|
|
|
}
|
|
}
|
|
@@ -1079,18 +1047,6 @@ onBeforeUnmount(() => {
|
|
|
<div class="editor-content">
|
|
<div class="editor-content">
|
|
|
<p v-if="error" class="error" role="alert">{{ error }}</p>
|
|
<p v-if="error" class="error" role="alert">{{ error }}</p>
|
|
|
<label>名称<input v-model="form.name" :readonly="isRemoteAgent" required maxlength="100" /></label>
|
|
<label>名称<input v-model="form.name" :readonly="isRemoteAgent" required maxlength="100" /></label>
|
|
|
- <div v-if="dialog === 'agent' && !isRemoteAgent" class="grok-import">
|
|
|
|
|
- <label>从 Grok Bot 导入(可选)<div class="grok-import-row"><input v-model="importUrl" placeholder="https://x.ai/bot/…" @keydown.enter.prevent="runGrokBotImport" /><button type="button" :disabled="importing || !importUrl.trim()" @click="runGrokBotImport">{{ importing ? '导入中…' : '导入' }}</button></div></label>
|
|
|
|
|
- <p v-if="importError" class="error" role="alert">{{ importError }}</p>
|
|
|
|
|
- <details class="grok-import-manual"><summary>无法访问链接?粘贴 Bot 描述导入</summary>
|
|
|
|
|
- <label>Bot 描述<textarea v-model="pastedDescription" rows="3" maxlength="1200" placeholder="粘贴 Grok Bot 页面上的描述文字,会按同一套模板生成职责与工作规则" /></label>
|
|
|
|
|
- <button type="button" :disabled="!pastedDescription.trim()" @click="applyPastedGrokBot">按描述生成</button>
|
|
|
|
|
- </details>
|
|
|
|
|
- <div v-if="importPreview" class="grok-import-preview">
|
|
|
|
|
- <p>{{ importPreview.draft.kind === 'manager' ? '已按「项目管理」模板生成职责与工作规则' : importPreview.draft.kind === 'housekeeper' ? '已按「家庭管家」模板生成职责与工作规则' : '已导入描述,可再补充一句话职责与反任务' }}</p>
|
|
|
|
|
- <ul><li v-for="item in importPreview.draft.suggestions" :key="item">{{ item }}</li></ul>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
<details v-if="dialog === 'group'" class="group-options">
|
|
<details v-if="dialog === 'group'" class="group-options">
|
|
|
<summary>从团队模板选择(可选)</summary>
|
|
<summary>从团队模板选择(可选)</summary>
|
|
|
<TeamPresetPicker :selected="selectedPresetId" :available="agents.filter(a => !a.archived && !a.temporaryGoalId).length" @select="choosePreset" />
|
|
<TeamPresetPicker :selected="selectedPresetId" :available="agents.filter(a => !a.archived && !a.temporaryGoalId).length" @select="choosePreset" />
|
|
@@ -1320,13 +1276,4 @@ onBeforeUnmount(() => {
|
|
|
.lifecycle-error button{display:grid;place-items:center;flex:0 0 28px;width:28px;height:28px;border:0;border-radius:6px;background:transparent;color:var(--text-secondary);cursor:pointer}
|
|
.lifecycle-error button{display:grid;place-items:center;flex:0 0 28px;width:28px;height:28px;border:0;border-radius:6px;background:transparent;color:var(--text-secondary);cursor:pointer}
|
|
|
.lifecycle-error button:hover{background:var(--surface-hover)}
|
|
.lifecycle-error button:hover{background:var(--surface-hover)}
|
|
|
.lifecycle-error button:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
|
.lifecycle-error button:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
|
|
-.grok-import{display:grid;gap:8px;padding:10px 12px;border:1px dashed var(--line);border-radius:10px}
|
|
|
|
|
-.grok-import-row{display:flex;gap:8px}
|
|
|
|
|
-.grok-import-row input{flex:1;min-width:0}
|
|
|
|
|
-.grok-import-preview{display:grid;gap:6px;font-size:13px;color:var(--text-secondary)}
|
|
|
|
|
-.grok-import-preview p{margin:0}
|
|
|
|
|
-.grok-import-preview ul{margin:0;padding-left:18px;display:grid;gap:4px}
|
|
|
|
|
-.grok-import-manual{display:grid;gap:8px}
|
|
|
|
|
-.grok-import-manual summary{cursor:pointer;font-size:12.5px;color:var(--text-muted)}
|
|
|
|
|
-.grok-import-manual button{align-self:start}
|
|
|
|
|
</style>
|
|
</style>
|