mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-05-25 08:00:16 +08:00
288 lines
10 KiB
Vue
288 lines
10 KiB
Vue
<script setup lang="ts">
|
|
import { ref, watch, computed } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { useApiServerStore, type DataSource, type RemoteSession } from '@/stores/apiServer'
|
|
|
|
const props = defineProps<{
|
|
open: boolean
|
|
manageSource?: DataSource
|
|
subscribedRemoteIds?: Set<string>
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
'update:open': [value: boolean]
|
|
sourceAdded: []
|
|
sessionsAdded: [sourceId: string, sessions: Array<{ name: string; remoteSessionId: string }>]
|
|
}>()
|
|
|
|
const { t } = useI18n()
|
|
const store = useApiServerStore()
|
|
|
|
const isManageMode = computed(() => !!props.manageSource)
|
|
|
|
const formData = ref({
|
|
name: '',
|
|
baseUrl: '',
|
|
token: '',
|
|
intervalMinutes: 60,
|
|
})
|
|
|
|
const remoteSessions = ref<RemoteSession[]>([])
|
|
const selectedSessionIds = ref<Set<string>>(new Set())
|
|
const discovering = ref(false)
|
|
const discoveryError = ref('')
|
|
|
|
watch(
|
|
() => props.open,
|
|
async (val) => {
|
|
if (val) {
|
|
if (isManageMode.value && props.manageSource) {
|
|
formData.value = {
|
|
baseUrl: props.manageSource.baseUrl,
|
|
token: props.manageSource.token,
|
|
intervalMinutes: props.manageSource.intervalMinutes,
|
|
}
|
|
} else {
|
|
formData.value = { name: '', baseUrl: '', token: '', intervalMinutes: 60 }
|
|
}
|
|
remoteSessions.value = []
|
|
selectedSessionIds.value = new Set()
|
|
discoveryError.value = ''
|
|
if (isManageMode.value) {
|
|
await discoverSessions()
|
|
}
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
|
|
const availableSessions = computed(() => remoteSessions.value.filter((s) => !props.subscribedRemoteIds?.has(s.id)))
|
|
|
|
const allSelected = computed(
|
|
() => availableSessions.value.length > 0 && selectedSessionIds.value.size === availableSessions.value.length
|
|
)
|
|
|
|
function toggleSelectAll() {
|
|
if (allSelected.value) {
|
|
selectedSessionIds.value = new Set()
|
|
} else {
|
|
selectedSessionIds.value = new Set(availableSessions.value.map((s) => s.id))
|
|
}
|
|
}
|
|
|
|
function toggleSession(id: string) {
|
|
if (props.subscribedRemoteIds?.has(id)) return
|
|
const next = new Set(selectedSessionIds.value)
|
|
if (next.has(id)) next.delete(id)
|
|
else next.add(id)
|
|
selectedSessionIds.value = next
|
|
}
|
|
|
|
function closeModal() {
|
|
emit('update:open', false)
|
|
}
|
|
|
|
async function discoverSessions() {
|
|
if (!formData.value.baseUrl) return
|
|
discovering.value = true
|
|
discoveryError.value = ''
|
|
remoteSessions.value = []
|
|
selectedSessionIds.value = new Set()
|
|
try {
|
|
remoteSessions.value = await store.fetchRemoteSessions(formData.value.baseUrl, formData.value.token)
|
|
} catch (err: any) {
|
|
discoveryError.value = err.message || t('settings.api.dataSources.discovery.error')
|
|
} finally {
|
|
discovering.value = false
|
|
}
|
|
}
|
|
|
|
async function handleSubmit() {
|
|
if (isManageMode.value) {
|
|
if (selectedSessionIds.value.size === 0 || !props.manageSource) return
|
|
const sessions = remoteSessions.value
|
|
.filter((s) => selectedSessionIds.value.has(s.id))
|
|
.map((s) => ({ name: s.name, remoteSessionId: s.id }))
|
|
emit('sessionsAdded', props.manageSource.id, sessions)
|
|
closeModal()
|
|
} else {
|
|
const ds = await store.addDataSource({
|
|
name: formData.value.name || undefined,
|
|
baseUrl: formData.value.baseUrl,
|
|
token: formData.value.token,
|
|
intervalMinutes: formData.value.intervalMinutes,
|
|
})
|
|
if (ds && selectedSessionIds.value.size > 0) {
|
|
const sessions = remoteSessions.value
|
|
.filter((s) => selectedSessionIds.value.has(s.id))
|
|
.map((s) => ({ name: s.name, remoteSessionId: s.id }))
|
|
await store.addImportSessions(ds.id, sessions)
|
|
}
|
|
emit('sourceAdded')
|
|
closeModal()
|
|
}
|
|
}
|
|
|
|
function formatMessageCount(count?: number): string {
|
|
if (count === undefined) return '-'
|
|
if (count >= 10000) return `${(count / 10000).toFixed(1)}w`
|
|
if (count >= 1000) return `${(count / 1000).toFixed(1)}k`
|
|
return String(count)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<UModal :open="open" @update:open="emit('update:open', $event)">
|
|
<template #content>
|
|
<div class="p-6" style="min-width: 480px; max-height: 80vh; overflow-y: auto">
|
|
<h3 class="mb-4 text-lg font-semibold text-gray-900 dark:text-white">
|
|
{{
|
|
isManageMode
|
|
? t('settings.api.dataSources.edit.manageSessions')
|
|
: t('settings.api.dataSources.form.modalTitle')
|
|
}}
|
|
</h3>
|
|
|
|
<div class="space-y-4">
|
|
<!-- New data source: show all fields -->
|
|
<template v-if="!isManageMode">
|
|
<div>
|
|
<label class="mb-1 block text-xs font-medium text-gray-700 dark:text-gray-300">
|
|
{{ t('settings.api.dataSources.form.name') }}
|
|
</label>
|
|
<UInput
|
|
v-model="formData.name"
|
|
class="w-full"
|
|
:placeholder="t('settings.api.dataSources.form.namePlaceholder')"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="mb-1 block text-xs font-medium text-gray-700 dark:text-gray-300">
|
|
{{ t('settings.api.dataSources.discovery.baseUrl') }}
|
|
</label>
|
|
<UInput
|
|
v-model="formData.baseUrl"
|
|
class="w-full"
|
|
:placeholder="t('settings.api.dataSources.discovery.baseUrlPlaceholder')"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="mb-1 block text-xs font-medium text-gray-700 dark:text-gray-300">
|
|
{{ t('settings.api.dataSources.form.token') }}
|
|
</label>
|
|
<UInput
|
|
v-model="formData.token"
|
|
class="w-full"
|
|
:placeholder="t('settings.api.dataSources.form.tokenPlaceholder')"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="mb-1 block text-xs font-medium text-gray-700 dark:text-gray-300">
|
|
{{ t('settings.api.dataSources.form.interval') }}
|
|
</label>
|
|
<UInput v-model.number="formData.intervalMinutes" type="number" :min="1" class="w-full" />
|
|
</div>
|
|
|
|
<div class="flex items-center gap-2">
|
|
<UButton
|
|
color="primary"
|
|
variant="soft"
|
|
:loading="discovering"
|
|
:disabled="!formData.baseUrl"
|
|
@click="discoverSessions"
|
|
>
|
|
<UIcon name="i-heroicons-magnifying-glass" class="mr-1 h-4 w-4" />
|
|
{{ t('settings.api.dataSources.discovery.browse') }}
|
|
</UButton>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Error -->
|
|
<div
|
|
v-if="discoveryError"
|
|
class="rounded-md bg-red-50 p-3 text-xs text-red-600 dark:bg-red-900/20 dark:text-red-400"
|
|
>
|
|
{{ discoveryError }}
|
|
</div>
|
|
|
|
<!-- Session list -->
|
|
<div v-if="remoteSessions.length > 0">
|
|
<div class="mb-2 flex items-center justify-between">
|
|
<span class="text-xs font-medium text-gray-700 dark:text-gray-300">
|
|
{{ t('settings.api.dataSources.discovery.found', { count: remoteSessions.length }) }}
|
|
</span>
|
|
<button
|
|
class="text-xs text-blue-500 hover:text-blue-700 dark:hover:text-blue-400"
|
|
@click="toggleSelectAll"
|
|
>
|
|
{{
|
|
allSelected
|
|
? t('settings.api.dataSources.discovery.deselectAll')
|
|
: t('settings.api.dataSources.discovery.selectAll')
|
|
}}
|
|
</button>
|
|
</div>
|
|
<div class="max-h-64 overflow-y-auto rounded-lg border border-gray-200 dark:border-gray-600">
|
|
<div
|
|
v-for="session in remoteSessions"
|
|
:key="session.id"
|
|
class="flex items-center gap-3 border-b border-gray-100 px-3 py-2 last:border-0"
|
|
:class="
|
|
subscribedRemoteIds?.has(session.id)
|
|
? 'opacity-60'
|
|
: 'cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800'
|
|
"
|
|
@click="toggleSession(session.id)"
|
|
>
|
|
<UCheckbox
|
|
:model-value="subscribedRemoteIds?.has(session.id) || selectedSessionIds.has(session.id)"
|
|
:disabled="subscribedRemoteIds?.has(session.id)"
|
|
@click.stop
|
|
@update:model-value="toggleSession(session.id)"
|
|
/>
|
|
<div class="min-w-0 flex-1">
|
|
<div class="flex items-center gap-2 truncate text-sm font-medium text-gray-900 dark:text-white">
|
|
{{ session.name }}
|
|
<span
|
|
v-if="subscribedRemoteIds?.has(session.id)"
|
|
class="rounded-full bg-green-100 px-1.5 py-0.5 text-[10px] text-green-700 dark:bg-green-900/30 dark:text-green-400"
|
|
>
|
|
{{ t('settings.api.dataSources.edit.subscribed') }}
|
|
</span>
|
|
</div>
|
|
<div class="flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400">
|
|
<span class="rounded bg-gray-100 px-1 dark:bg-gray-700">{{ session.platform }}</span>
|
|
<span>{{ session.type }}</span>
|
|
<span v-if="session.messageCount !== undefined">
|
|
{{ formatMessageCount(session.messageCount) }}
|
|
{{ t('settings.api.dataSources.discovery.messages') }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex justify-end gap-2 pt-2">
|
|
<UButton variant="soft" @click="closeModal">{{ t('common.cancel') }}</UButton>
|
|
<UButton
|
|
v-if="isManageMode"
|
|
color="primary"
|
|
:disabled="selectedSessionIds.size === 0"
|
|
@click="handleSubmit"
|
|
>
|
|
{{ t('settings.api.dataSources.discovery.subscribe', { count: selectedSessionIds.size }) }}
|
|
</UButton>
|
|
<UButton v-else color="primary" :disabled="!formData.baseUrl" @click="handleSubmit">
|
|
{{ t('settings.api.dataSources.addBtn') }}
|
|
</UButton>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</UModal>
|
|
</template>
|