({ url: '/platform/', body: data, throwOnError: true }),
onSettled: () => queryCache.invalidateQueries({ key: ['platform'] }),
})
}
diff --git a/packages/web/src/pages/bots/components/bot-heartbeat.vue b/packages/web/src/pages/bots/components/bot-heartbeat.vue
index 2c12e2cf..30fae096 100644
--- a/packages/web/src/pages/bots/components/bot-heartbeat.vue
+++ b/packages/web/src/pages/bots/components/bot-heartbeat.vue
@@ -191,7 +191,7 @@
@@ -243,8 +243,12 @@ import {
} from '@memoh/ui'
import ConfirmPopover from '@/components/confirm-popover/index.vue'
import ModelSelect from './model-select.vue'
-import { client } from '@memoh/sdk/client'
-import { getBotsByBotIdSettings, putBotsByBotIdSettings, getModels, getProviders } from '@memoh/sdk'
+import {
+ getBotsByBotIdSettings, putBotsByBotIdSettings,
+ getBotsByBotIdHeartbeatLogs, deleteBotsByBotIdHeartbeatLogs,
+ getModels, getProviders,
+} from '@memoh/sdk'
+import type { SettingsSettings, SettingsUpsertRequest, HeartbeatLog } from '@memoh/sdk'
import { useQuery, useMutation, useQueryCache } from '@pinia/colada'
import { resolveApiErrorMessage } from '@/utils/api-error'
import { formatDateTime } from '@/utils/date-time'
@@ -294,24 +298,24 @@ const settingsForm = reactive({
heartbeat_model_id: '',
})
-watch(settings, (val) => {
+watch(settings, (val: SettingsSettings | undefined) => {
if (val) {
- settingsForm.heartbeat_enabled = (val as any).heartbeat_enabled ?? false
- settingsForm.heartbeat_interval = (val as any).heartbeat_interval ?? 30
- settingsForm.heartbeat_model_id = (val as any).heartbeat_model_id ?? ''
+ settingsForm.heartbeat_enabled = val.heartbeat_enabled ?? false
+ settingsForm.heartbeat_interval = val.heartbeat_interval ?? 30
+ settingsForm.heartbeat_model_id = val.heartbeat_model_id ?? ''
}
}, { immediate: true })
const settingsChanged = computed(() => {
if (!settings.value) return false
- const s = settings.value as any
+ const s: SettingsSettings = settings.value
return settingsForm.heartbeat_enabled !== (s.heartbeat_enabled ?? false)
|| settingsForm.heartbeat_interval !== (s.heartbeat_interval ?? 30)
|| settingsForm.heartbeat_model_id !== (s.heartbeat_model_id ?? '')
})
const { mutateAsync: updateSettings, isLoading: isSaving } = useMutation({
- mutation: async (body: Record) => {
+ mutation: async (body: SettingsUpsertRequest) => {
const { data } = await putBotsByBotIdSettings({
path: { bot_id: botIdRef.value },
body,
@@ -331,17 +335,6 @@ async function handleSaveSettings() {
}
}
-interface HeartbeatLog {
- id: string
- bot_id: string
- status: 'ok' | 'alert' | 'error'
- result_text: string
- error_message: string
- usage: any
- started_at: string
- completed_at: string | null
-}
-
const isLoading = ref(false)
const isClearing = ref(false)
const logs = ref([])
@@ -356,32 +349,33 @@ const filteredLogs = computed(() => {
return logs.value.filter(l => l.status === statusFilter.value)
})
-function statusVariant(status: string) {
+function statusVariant(status: string | undefined) {
if (status === 'ok') return 'secondary' as const
if (status === 'alert') return 'default' as const
return 'destructive' as const
}
-function statusLabel(status: string) {
+function statusLabel(status: string | undefined) {
if (status === 'ok') return t('bots.heartbeat.statusOk')
if (status === 'alert') return t('bots.heartbeat.statusAlert')
return t('bots.heartbeat.statusError')
}
-function formatDuration(startedAt: string, completedAt: string | null) {
- if (!completedAt) return '—'
+function formatDuration(startedAt: string | undefined, completedAt: string | null | undefined) {
+ if (!startedAt || !completedAt) return '—'
const ms = new Date(completedAt).getTime() - new Date(startedAt).getTime()
if (ms < 1000) return `${ms}ms`
return `${(ms / 1000).toFixed(1)}s`
}
-function truncateText(text: string, maxLen = 80) {
+function truncateText(text: string | undefined, maxLen = 80) {
if (!text) return ''
if (text === 'HEARTBEAT_OK') return 'HEARTBEAT_OK'
return text.length > maxLen ? text.slice(0, maxLen) + '…' : text
}
-function toggleExpand(id: string) {
+function toggleExpand(id: string | undefined) {
+ if (!id) return
if (expandedIds.value.has(id)) {
expandedIds.value.delete(id)
} else {
@@ -393,14 +387,12 @@ async function fetchLogs(before?: string) {
if (!props.botId) return
isLoading.value = true
try {
- const params = new URLSearchParams({ limit: String(PAGE_SIZE) })
- if (before) params.set('before', before)
- const { data, error } = await client.get({
- url: `/bots/${props.botId}/heartbeat/logs`,
+ const { data } = await getBotsByBotIdHeartbeatLogs({
+ path: { bot_id: props.botId },
query: { limit: PAGE_SIZE, ...(before ? { before } : {}) },
+ throwOnError: true,
})
- if (error) throw error
- const items = (data as any)?.items ?? []
+ const items = data?.items ?? []
if (!before) {
logs.value = items
} else {
@@ -417,7 +409,7 @@ async function fetchLogs(before?: string) {
async function loadMore() {
if (logs.value.length === 0) return
const lastLog = logs.value[logs.value.length - 1]
- await fetchLogs(lastLog.started_at)
+ await fetchLogs(lastLog?.started_at)
}
async function handleRefresh() {
@@ -428,10 +420,10 @@ async function handleRefresh() {
async function handleClear() {
isClearing.value = true
try {
- const { error } = await client.delete({
- url: `/bots/${props.botId}/heartbeat/logs`,
+ await deleteBotsByBotIdHeartbeatLogs({
+ path: { bot_id: props.botId },
+ throwOnError: true,
})
- if (error) throw error
logs.value = []
expandedIds.value.clear()
toast.success(t('bots.heartbeat.clearSuccess'))
diff --git a/packages/web/src/pages/bots/components/bot-mcp.vue b/packages/web/src/pages/bots/components/bot-mcp.vue
index b2e5ccd8..a6dac14b 100644
--- a/packages/web/src/pages/bots/components/bot-mcp.vue
+++ b/packages/web/src/pages/bots/components/bot-mcp.vue
@@ -93,7 +93,7 @@
-
+
= {
+function buildRequestBody(): McpUpsertRequest {
+ const body: McpUpsertRequest = {
name: formData.value.name.trim(),
is_active: formData.value.active,
}
@@ -982,13 +983,13 @@ async function handleSubmit() {
if (editingItem.value) {
await putBotsByBotIdMcpById({
path: { bot_id: props.botId, id: editingItem.value.id },
- body: body as any,
+ body,
throwOnError: true,
})
} else {
await postBotsByBotIdMcp({
path: { bot_id: props.botId },
- body: body as any,
+ body,
throwOnError: true,
})
}
@@ -1046,12 +1047,13 @@ function handleBatchExport() {
async function handleImport() {
importSubmitting.value = true
try {
- let parsed = JSON.parse(importJson.value)
+ let parsed: McpImportRequest = JSON.parse(importJson.value)
if (!parsed.mcpServers && typeof parsed === 'object') {
- parsed = { mcpServers: parsed }
+ parsed = { mcpServers: parsed as McpImportRequest['mcpServers'] }
}
await client.put({
- url: `/bots/${props.botId}/mcp-ops/import`,
+ url: '/bots/{bot_id}/mcp-ops/import',
+ path: { bot_id: props.botId },
body: parsed,
throwOnError: true,
})
diff --git a/packages/web/src/pages/bots/components/bot-memory.vue b/packages/web/src/pages/bots/components/bot-memory.vue
index 1a200918..b8527c1c 100644
--- a/packages/web/src/pages/bots/components/bot-memory.vue
+++ b/packages/web/src/pages/bots/components/bot-memory.vue
@@ -414,8 +414,8 @@
>
{{ msg.role }}
-
- {{ msg.content?.text || (typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)) }}
+
+ {{ extractMessageText(msg.content) }}
@@ -578,6 +578,7 @@ import {
postBotsByBotIdMemoryCompact,
getBotsByBotIdMessages,
} from '@memoh/sdk'
+import type { MemoryCdfPoint, MemoryTopKBucket } from '@memoh/sdk'
import { toast } from 'vue-sonner'
import { useI18n } from 'vue-i18n'
import ConfirmPopover from '@/components/confirm-popover/index.vue'
@@ -593,12 +594,26 @@ interface MemoryItem {
score?: number
}
+type MessageContentBlock = { type: string; text?: string }
+type MessageContent = string | MessageContentBlock[] | unknown
+
interface Message {
role: string
- content: any
+ content: MessageContent
created_at?: string
}
+function extractMessageText(content: MessageContent): string {
+ if (typeof content === 'string') return content
+ if (Array.isArray(content)) {
+ return content
+ .filter((b): b is MessageContentBlock => typeof b === 'object' && b !== null)
+ .map(b => b.text ?? '')
+ .join('')
+ }
+ return JSON.stringify(content)
+}
+
const props = defineProps<{
botId: string
}>()
@@ -627,7 +642,7 @@ const compactRatio = ref('0.5')
const compactDecayDate = ref('')
// Hover state for CDF chart
-const hoveredCdfPoint = ref(null)
+const hoveredCdfPoint = ref(null)
const hoveredCdfIdx = ref(-1)
const hoveredCdfX = computed(() => {
if (!hoveredCdfPoint.value || !selectedMemory.value) return 0
@@ -640,13 +655,13 @@ const hoveredCdfY = computed(() => {
})
const selectedTopKBuckets = computed(() => selectedMemory.value?.top_k_buckets ?? [])
-const topKBucketValues = computed(() => selectedTopKBuckets.value.map((bucket: any) => bucket.value))
+const topKBucketValues = computed(() => selectedTopKBuckets.value.map((bucket: MemoryTopKBucket) => bucket.value ?? 0))
const topKMinValue = computed(() => Math.min(...topKBucketValues.value))
const topKMaxValue = computed(() => Math.max(...topKBucketValues.value))
const topKRange = computed(() => (topKMaxValue.value - topKMinValue.value) || 1)
const topKBarHeights = computed(() =>
selectedTopKBuckets.value.map(
- (bucket: any) => (((bucket.value - topKMinValue.value) / topKRange.value) * 80) + 20,
+ (bucket: MemoryTopKBucket) => ((((bucket.value ?? 0) - topKMinValue.value) / topKRange.value) * 80) + 20,
),
)
@@ -846,7 +861,7 @@ async function handleCompact() {
body: {
ratio: parseFloat(compactRatio.value),
decay_days: compactDecayDays.value || undefined,
- } as any,
+ },
throwOnError: true,
})
toast.success(t('bots.memory.compactSuccess'))
@@ -887,7 +902,7 @@ watch(() => props.botId, () => {
})
// Chart Helper: Generate smooth SVG path
-function generateSmoothPath(data: any[], closePath: boolean = false) {
+function generateSmoothPath(data: MemoryCdfPoint[], closePath: boolean = false) {
if (!data || data.length < 2) return ''
// Use a small margin (2%) to prevent clipping at boundaries
@@ -895,7 +910,7 @@ function generateSmoothPath(data: any[], closePath: boolean = false) {
const height = 100 - (margin * 2)
const points = data.map((p, idx) => ({
x: (idx / (data.length - 1)) * 100,
- y: (100 - margin) - (p.cumulative * height)
+ y: (100 - margin) - ((p.cumulative ?? 0) * height)
}))
let d = `M ${points[0].x},${points[0].y}`
diff --git a/packages/web/src/pages/bots/components/channel-settings-panel.vue b/packages/web/src/pages/bots/components/channel-settings-panel.vue
index 3876f33b..e97021a1 100644
--- a/packages/web/src/pages/bots/components/channel-settings-panel.vue
+++ b/packages/web/src/pages/bots/components/channel-settings-panel.vue
@@ -240,9 +240,8 @@ import { reactive, watch, computed, ref } from 'vue'
import { toast } from 'vue-sonner'
import { useI18n } from 'vue-i18n'
import { useMutation, useQueryCache } from '@pinia/colada'
-import { putBotsByIdChannelByPlatform } from '@memoh/sdk'
-import { client } from '@memoh/sdk/client'
-import type { HandlersChannelMeta, ChannelChannelConfig, ChannelFieldSchema } from '@memoh/sdk'
+import { putBotsByIdChannelByPlatform, deleteBotsByIdChannelByPlatform, patchBotsByIdChannelByPlatformStatus } from '@memoh/sdk'
+import type { HandlersChannelMeta, ChannelChannelConfig, ChannelFieldSchema, ChannelUpsertConfigRequest } from '@memoh/sdk'
import ConfirmPopover from '@/components/confirm-popover/index.vue'
interface BotChannelItem {
@@ -264,10 +263,10 @@ const { t } = useI18n()
const botIdRef = computed(() => props.botId)
const queryCache = useQueryCache()
const { mutateAsync: upsertChannel, isLoading } = useMutation({
- mutation: async ({ platform, data }: { platform: string; data: Record }) => {
+ mutation: async ({ platform, data }: { platform: string; data: ChannelUpsertConfigRequest }) => {
const { data: result } = await putBotsByIdChannelByPlatform({
path: { id: botIdRef.value, platform },
- body: data as any,
+ body: data,
throwOnError: true,
})
return result
@@ -276,12 +275,12 @@ const { mutateAsync: upsertChannel, isLoading } = useMutation({
})
const { mutateAsync: updateChannelStatus, isLoading: isStatusLoading } = useMutation({
mutation: async ({ platform, disabled }: { platform: string; disabled: boolean }) => {
- const { data } = await client.patch({
- url: `/bots/${botIdRef.value}/channel/${platform}/status`,
+ const { data } = await patchBotsByIdChannelByPlatformStatus({
+ path: { id: botIdRef.value, platform },
body: { disabled },
throwOnError: true,
})
- return data as ChannelChannelConfig
+ return data
},
onSettled: () => queryCache.invalidateQueries({ key: ['bot-channels', botIdRef.value] }),
})
@@ -434,8 +433,8 @@ async function handleToggleDisabled() {
async function handleDelete() {
action.value = 'delete'
try {
- await client.delete({
- url: `/bots/${botIdRef.value}/channel/${props.channelItem.meta.type}`,
+ await deleteBotsByIdChannelByPlatform({
+ path: { id: botIdRef.value, platform: props.channelItem.meta.type },
throwOnError: true,
})
lastSavedConfigId.value = ''
diff --git a/packages/web/src/pages/bots/detail.vue b/packages/web/src/pages/bots/detail.vue
index 6b1484e2..ed466aa9 100644
--- a/packages/web/src/pages/bots/detail.vue
+++ b/packages/web/src/pages/bots/detail.vue
@@ -40,17 +40,14 @@
@keydown.enter.prevent="handleConfirmBotName"
@keydown.esc.prevent="handleCancelBotName"
/>
-
+