refactor(stage-ui,stage-pages): improve structure of character & character-orchestrator
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { OnboardingDialog, ToasterRoot } from '@proj-airi/stage-ui/components'
|
||||
import { useSharedAnalyticsStore } from '@proj-airi/stage-ui/stores/analytics'
|
||||
import { useCharacterOrchestratorStore } from '@proj-airi/stage-ui/stores/character-orchestrator'
|
||||
import { useCharacterOrchestratorStore } from '@proj-airi/stage-ui/stores/character'
|
||||
import { useDisplayModelsStore } from '@proj-airi/stage-ui/stores/display-models'
|
||||
import { useModsServerChannelStore } from '@proj-airi/stage-ui/stores/mods/api/channel-server'
|
||||
import { useContextBridgeStore } from '@proj-airi/stage-ui/stores/mods/api/context-bridge'
|
||||
|
||||
@@ -3,7 +3,7 @@ import { defineInvoke, defineInvokeHandler } from '@moeru/eventa'
|
||||
import { themeColorFromValue, useThemeColor } from '@proj-airi/stage-layouts/composables/theme-color'
|
||||
import { ToasterRoot } from '@proj-airi/stage-ui/components'
|
||||
import { useSharedAnalyticsStore } from '@proj-airi/stage-ui/stores/analytics'
|
||||
import { useCharacterOrchestratorStore } from '@proj-airi/stage-ui/stores/character-orchestrator'
|
||||
import { useCharacterOrchestratorStore } from '@proj-airi/stage-ui/stores/character'
|
||||
import { useDisplayModelsStore } from '@proj-airi/stage-ui/stores/display-models'
|
||||
import { useModsServerChannelStore } from '@proj-airi/stage-ui/stores/mods/api/channel-server'
|
||||
import { useContextBridgeStore } from '@proj-airi/stage-ui/stores/mods/api/context-bridge'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { OnboardingDialog, ToasterRoot } from '@proj-airi/stage-ui/components'
|
||||
import { useSharedAnalyticsStore } from '@proj-airi/stage-ui/stores/analytics'
|
||||
import { useCharacterOrchestratorStore } from '@proj-airi/stage-ui/stores/character-orchestrator'
|
||||
import { useCharacterOrchestratorStore } from '@proj-airi/stage-ui/stores/character'
|
||||
import { useDisplayModelsStore } from '@proj-airi/stage-ui/stores/display-models'
|
||||
import { useModsServerChannelStore } from '@proj-airi/stage-ui/stores/mods/api/channel-server'
|
||||
import { useContextBridgeStore } from '@proj-airi/stage-ui/stores/mods/api/context-bridge'
|
||||
|
||||
+43
-6
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { FlowEntry, SparkNotifyEntryState } from '../context-flow-types'
|
||||
import type { FlowChannel, FlowDirection, FlowEntry, SparkNotifyEntryState } from '../context-flow-types'
|
||||
|
||||
import ContextFlowPreview from './context-flow-preview.vue'
|
||||
import ContextFlowSparkNotify from './context-flow-spark-notify.vue'
|
||||
@@ -9,14 +9,51 @@ import { useContextFlowFormatters } from '../composables/use-context-flow-format
|
||||
defineProps<{ entry: FlowEntry, sparkNotifyState?: SparkNotifyEntryState }>()
|
||||
|
||||
const {
|
||||
channelBadgeClasses,
|
||||
directionBadgeClasses,
|
||||
directionIconClass,
|
||||
formatPayload,
|
||||
formatTimestamp,
|
||||
getEventSource,
|
||||
sourceBadgeClasses,
|
||||
} = useContextFlowFormatters()
|
||||
|
||||
const directionBadgeClassMap: Record<FlowDirection, string[]> = {
|
||||
incoming: [
|
||||
'bg-complementary-500/15',
|
||||
'text-complementary-600',
|
||||
'dark:text-complementary-300',
|
||||
'border-complementary-500/30',
|
||||
],
|
||||
outgoing: [
|
||||
'bg-primary-500/15',
|
||||
'text-primary-600',
|
||||
'dark:text-primary-300',
|
||||
'border-primary-500/30',
|
||||
],
|
||||
}
|
||||
|
||||
const channelBadgeClassMap: Record<FlowChannel, string[]> = {
|
||||
server: ['bg-orange-500/15', 'text-orange-600', 'dark:text-orange-300', 'border-orange-500/30'],
|
||||
broadcast: ['bg-violet-500/15', 'text-violet-600', 'dark:text-violet-300', 'border-violet-500/30'],
|
||||
chat: ['bg-lime-500/15', 'text-lime-600', 'dark:text-lime-300', 'border-lime-500/30'],
|
||||
devtools: ['bg-neutral-400/15', 'text-neutral-600', 'dark:text-neutral-300', 'border-neutral-500/30'],
|
||||
}
|
||||
|
||||
const directionIconClassMap: Record<FlowDirection, string> = {
|
||||
incoming: 'i-solar:arrow-down-linear',
|
||||
outgoing: 'i-solar:arrow-up-linear',
|
||||
}
|
||||
|
||||
const sourceBadgeClasses = ['bg-neutral-400/15', 'text-neutral-600', 'dark:text-neutral-300', 'border-neutral-500/30']
|
||||
|
||||
function directionBadgeClasses(direction: FlowDirection) {
|
||||
return directionBadgeClassMap[direction]
|
||||
}
|
||||
|
||||
function channelBadgeClasses(channel: FlowChannel) {
|
||||
return channelBadgeClassMap[channel]
|
||||
}
|
||||
|
||||
function directionIconClass(direction: FlowDirection) {
|
||||
return directionIconClassMap[direction]
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -42,7 +79,7 @@ const {
|
||||
</span>
|
||||
<span
|
||||
v-if="getEventSource(entry)"
|
||||
:class="['rounded-full', 'border', 'px-2', 'py-0.5', ...sourceBadgeClasses()]"
|
||||
:class="['rounded-full', 'border', 'px-2', 'py-0.5', ...sourceBadgeClasses]"
|
||||
>
|
||||
{{ getEventSource(entry) }}
|
||||
</span>
|
||||
|
||||
-3
@@ -19,11 +19,8 @@ const { buildPreviewItems } = useContextFlowFormatters()
|
||||
:key="`${entry.id}-${item.label}`"
|
||||
:class="[
|
||||
'rounded-lg',
|
||||
'border',
|
||||
'border-neutral-200/70',
|
||||
'bg-white/80',
|
||||
'p-3',
|
||||
'dark:border-neutral-800/80',
|
||||
'dark:bg-neutral-900/70',
|
||||
]"
|
||||
>
|
||||
|
||||
+1
-4
@@ -13,12 +13,9 @@ const { buildSparkCommandPreview } = useContextFlowFormatters()
|
||||
:class="[
|
||||
'mt-3',
|
||||
'rounded-lg',
|
||||
'border',
|
||||
'border-neutral-200/70',
|
||||
'bg-white/80',
|
||||
'p-3',
|
||||
'text-xs',
|
||||
'dark:border-neutral-800/80',
|
||||
'dark:bg-neutral-900/70',
|
||||
'grid',
|
||||
'gap-3',
|
||||
@@ -29,7 +26,7 @@ const { buildSparkCommandPreview } = useContextFlowFormatters()
|
||||
<div :class="['flex', 'items-center', 'gap-2', 'text-neutral-700', 'dark:text-neutral-200']">
|
||||
<span
|
||||
v-if="state.handling"
|
||||
:class="['size-3.5', 'i-solar:spinner-line-duotone', 'animate-spin']"
|
||||
:class="['size-3.5', 'i-svg-spinners:ring-resize', 'animate-spin']"
|
||||
/>
|
||||
<span>
|
||||
{{ state.handling ? 'Handling spark:notify...' : 'spark:notify handled' }}
|
||||
|
||||
+1
-43
@@ -1,6 +1,6 @@
|
||||
import type { WebSocketEvents } from '@proj-airi/server-sdk'
|
||||
|
||||
import type { FlowChannel, FlowDirection, FlowEntry, PreviewItem } from '../context-flow-types'
|
||||
import type { FlowEntry, PreviewItem } from '../context-flow-types'
|
||||
|
||||
const previewMaxLength = 420
|
||||
|
||||
@@ -187,57 +187,15 @@ function formatPayload(payload: unknown) {
|
||||
}
|
||||
}
|
||||
|
||||
function directionBadgeClasses(direction: FlowDirection) {
|
||||
if (direction === 'incoming') {
|
||||
return [
|
||||
'bg-complementary-500/15',
|
||||
'text-complementary-600',
|
||||
'dark:text-complementary-300',
|
||||
'border-complementary-500/30',
|
||||
]
|
||||
}
|
||||
return [
|
||||
'bg-primary-500/15',
|
||||
'text-primary-600',
|
||||
'dark:text-primary-300',
|
||||
'border-primary-500/30',
|
||||
]
|
||||
}
|
||||
|
||||
function directionIconClass(direction: FlowDirection) {
|
||||
return direction === 'incoming' ? 'i-solar:arrow-down-linear' : 'i-solar:arrow-up-linear'
|
||||
}
|
||||
|
||||
function channelBadgeClasses(channel: FlowChannel) {
|
||||
switch (channel) {
|
||||
case 'server':
|
||||
return ['bg-orange-500/15', 'text-orange-600', 'dark:text-orange-300', 'border-orange-500/30']
|
||||
case 'broadcast':
|
||||
return ['bg-violet-500/15', 'text-violet-600', 'dark:text-violet-300', 'border-violet-500/30']
|
||||
case 'chat':
|
||||
return ['bg-lime-500/15', 'text-lime-600', 'dark:text-lime-300', 'border-lime-500/30']
|
||||
default:
|
||||
return ['bg-neutral-400/15', 'text-neutral-600', 'dark:text-neutral-300', 'border-neutral-500/30']
|
||||
}
|
||||
}
|
||||
|
||||
function sourceBadgeClasses() {
|
||||
return ['bg-neutral-400/15', 'text-neutral-600', 'dark:text-neutral-300', 'border-neutral-500/30']
|
||||
}
|
||||
|
||||
export function useContextFlowFormatters() {
|
||||
return {
|
||||
buildPreviewItems,
|
||||
buildSparkCommandPreview,
|
||||
channelBadgeClasses,
|
||||
directionBadgeClasses,
|
||||
directionIconClass,
|
||||
formatDestinations,
|
||||
formatPayload,
|
||||
formatTimestamp,
|
||||
getEventSource,
|
||||
getPayloadData,
|
||||
sourceBadgeClasses,
|
||||
summarizeContextUpdate,
|
||||
truncateText,
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@ import type { FlowDirection, FlowEntry, SparkNotifyEntryState } from './context-
|
||||
|
||||
import { errorMessageFrom } from '@moeru/std'
|
||||
import { ContextUpdateStrategy } from '@proj-airi/server-sdk'
|
||||
import { useCharacterStore } from '@proj-airi/stage-ui/stores/character'
|
||||
import { useCharacterOrchestratorStore } from '@proj-airi/stage-ui/stores/character-orchestrator'
|
||||
import { useCharacterOrchestratorStore, useCharacterStore } from '@proj-airi/stage-ui/stores/character'
|
||||
import { CHAT_STREAM_CHANNEL_NAME, CONTEXT_CHANNEL_NAME, useChatStore } from '@proj-airi/stage-ui/stores/chat'
|
||||
import { useModsServerChannelStore } from '@proj-airi/stage-ui/stores/mods/api/channel-server'
|
||||
import { Callout } from '@proj-airi/ui'
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"paths": {
|
||||
"@proj-airi/stage-ui/*": [
|
||||
"../../packages/stage-ui/src/*"
|
||||
]
|
||||
},
|
||||
"resolveJsonModule": true,
|
||||
"types": [
|
||||
"vitest",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"./libs": "./src/libs/index.ts",
|
||||
"./stores/providers/aliyun": "./src/stores/providers/aliyun/index.ts",
|
||||
"./stores/analytics": "./src/stores/analytics/index.ts",
|
||||
"./stores/character": "./src/stores/character/index.ts",
|
||||
"./stores/*": "./src/stores/*.ts",
|
||||
"./stores": "./src/stores/index.ts",
|
||||
"./workers/vad": "./src/workers/vad/index.ts",
|
||||
|
||||
+6
-4
@@ -1,12 +1,14 @@
|
||||
import type { TextSegmentationItem } from '../composables/queues'
|
||||
import type { TextSegmentationItem } from '../../composables/queues'
|
||||
|
||||
import { nanoid } from 'nanoid'
|
||||
import { defineStore, storeToRefs } from 'pinia'
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
|
||||
import { usePipelineWorkflowTextSegmentationStore } from '../composables/queues'
|
||||
import { TTS_FLUSH_INSTRUCTION } from '../utils/tts'
|
||||
import { useAiriCardStore } from './modules'
|
||||
import { usePipelineWorkflowTextSegmentationStore } from '../../composables/queues'
|
||||
import { TTS_FLUSH_INSTRUCTION } from '../../utils/tts'
|
||||
import { useAiriCardStore } from '../modules'
|
||||
|
||||
export * from './orchestrator'
|
||||
|
||||
export interface CharacterSparkNotifyReaction {
|
||||
id: string
|
||||
+63
-85
@@ -1,24 +1,16 @@
|
||||
import type { WebSocketBaseEvent, WebSocketEvents } from '@proj-airi/server-sdk'
|
||||
import type { ChatProvider } from '@xsai-ext/providers/utils'
|
||||
import type { ChatProvider, ChatProviderWithExtraOptions, EmbedProvider, EmbedProviderWithExtraOptions, SpeechProvider, SpeechProviderWithExtraOptions, TranscriptionProvider, TranscriptionProviderWithExtraOptions } from '@xsai-ext/providers/utils'
|
||||
import type { Message } from '@xsai/shared-chat'
|
||||
|
||||
import type { StreamEvent } from './llm'
|
||||
import type { StreamEvent } from '../../../../llm'
|
||||
|
||||
import { errorMessageFrom } from '@moeru/std'
|
||||
import { tool } from '@xsai/tool'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { defineStore, storeToRefs } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { validate } from 'xsschema'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { useCharacterStore } from './character'
|
||||
import { useLLM } from './llm'
|
||||
import { useModsServerChannelStore } from './mods/api/channel-server'
|
||||
import { useConsciousnessStore } from './modules/consciousness'
|
||||
import { useProvidersStore } from './providers'
|
||||
|
||||
interface SparkNotifyCommandDraft {
|
||||
export interface SparkNotifyCommandDraft {
|
||||
destinations: string[]
|
||||
interrupt?: 'force' | 'soft' | boolean
|
||||
priority?: 'critical' | 'high' | 'normal' | 'low'
|
||||
@@ -28,11 +20,45 @@ interface SparkNotifyCommandDraft {
|
||||
contexts?: WebSocketEvents['spark:command']['contexts']
|
||||
}
|
||||
|
||||
interface SparkNotifyResponse {
|
||||
export interface SparkNotifyResponse {
|
||||
reaction?: string
|
||||
commands?: SparkNotifyCommandDraft[]
|
||||
}
|
||||
|
||||
export interface SparkNotifyAgentDeps {
|
||||
stream: (
|
||||
model: string,
|
||||
provider: ChatProvider,
|
||||
messages: Message[],
|
||||
options: {
|
||||
tools?: any[]
|
||||
supportsTools?: boolean
|
||||
waitForTools?: boolean
|
||||
onStreamEvent?: (event: StreamEvent) => void | Promise<void>
|
||||
},
|
||||
) => Promise<void>
|
||||
getActiveProvider: () => string | undefined
|
||||
getActiveModel: () => string | undefined
|
||||
getProviderInstance: <R extends
|
||||
| ChatProvider
|
||||
| ChatProviderWithExtraOptions
|
||||
| EmbedProvider
|
||||
| EmbedProviderWithExtraOptions
|
||||
| SpeechProvider
|
||||
| SpeechProviderWithExtraOptions
|
||||
| TranscriptionProvider
|
||||
| TranscriptionProviderWithExtraOptions,
|
||||
>(name: string,
|
||||
) => Promise<R>
|
||||
onReactionDelta: (eventId: string, text: string) => void
|
||||
onReactionEnd: (eventId: string, text: string) => void
|
||||
getSystemPrompt: () => string
|
||||
getProcessing: () => boolean
|
||||
setProcessing: (next: boolean) => void
|
||||
getPending: () => Array<WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify']>>
|
||||
setPending: (next: Array<WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify']>>) => void
|
||||
}
|
||||
|
||||
function getSparkNotifyHandlingAgentInstruction(moduleName: string) {
|
||||
return [
|
||||
'This is AIRI system, the life pod hosting your consciousness. You don\'t need to respond to me or every spark:notify event directly.',
|
||||
@@ -72,33 +98,25 @@ export const sparkCommandSchema = z.object({
|
||||
|
||||
export type SparkCommandSchema = z.infer<typeof sparkCommandSchema>
|
||||
|
||||
export const useCharacterOrchestratorStore = defineStore('character-orchestrator', () => {
|
||||
const { stream } = useLLM()
|
||||
const { activeProvider, activeModel } = storeToRefs(useConsciousnessStore())
|
||||
const providersStore = useProvidersStore()
|
||||
const characterStore = useCharacterStore()
|
||||
const { systemPrompt } = storeToRefs(characterStore)
|
||||
const modsServerChannelStore = useModsServerChannelStore()
|
||||
|
||||
const processing = ref(false)
|
||||
const pendingNotifies = ref<Array<WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify']>>>([])
|
||||
|
||||
export function setupAgentSparkNotifyHandler(deps: SparkNotifyAgentDeps) {
|
||||
async function runNotifyAgent(event: WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify']>) {
|
||||
if (!activeProvider.value || !activeModel.value) {
|
||||
const activeProvider = deps.getActiveProvider()
|
||||
const activeModel = deps.getActiveModel()
|
||||
if (!activeProvider || !activeModel) {
|
||||
console.warn('Spark notify ignored: missing active provider or model')
|
||||
return undefined
|
||||
}
|
||||
|
||||
const chatProvider = await providersStore.getProviderInstance<ChatProvider>(activeProvider.value)
|
||||
const chatProvider = await deps.getProviderInstance<ChatProvider>(activeProvider)
|
||||
const commandDrafts: SparkNotifyCommandDraft[] = []
|
||||
|
||||
let noResponse = false
|
||||
|
||||
const sparkNoResponseTool = await tool({
|
||||
name: 'builtIn_sparkNoResponse',
|
||||
description: `Indicate that no response or action is needed for the current spark:notify event.`,
|
||||
description: 'Indicate that no response or action is needed for the current spark:notify event.',
|
||||
parameters: z.object({}).strict(),
|
||||
execute: async (_payload) => {
|
||||
execute: async () => {
|
||||
noResponse = true
|
||||
return 'AIRI System: Acknowledged, no response or action will be processed.'
|
||||
},
|
||||
@@ -106,7 +124,7 @@ export const useCharacterOrchestratorStore = defineStore('character-orchestrator
|
||||
|
||||
const sparkCommandTool = await tool({
|
||||
name: 'builtIn_sparkCommand',
|
||||
description: `Issue a spark:command to sub-agents. You can call this tool multiple times to issue matrices of commands to different sub-agents as needed.`,
|
||||
description: 'Issue a spark:command to sub-agents. You can call this tool multiple times to issue matrices of commands to different sub-agents as needed.',
|
||||
parameters: sparkCommandSchema,
|
||||
execute: async (payload) => {
|
||||
try {
|
||||
@@ -153,7 +171,7 @@ export const useCharacterOrchestratorStore = defineStore('character-orchestrator
|
||||
const systemMessage: Message = {
|
||||
role: 'system',
|
||||
content: [
|
||||
systemPrompt.value,
|
||||
deps.getSystemPrompt(),
|
||||
getSparkNotifyHandlingAgentInstruction(event.source),
|
||||
].filter(Boolean).join('\n\n'),
|
||||
}
|
||||
@@ -168,32 +186,32 @@ export const useCharacterOrchestratorStore = defineStore('character-orchestrator
|
||||
|
||||
let fullText = ''
|
||||
|
||||
await stream(activeModel.value, chatProvider, [systemMessage, userMessage], {
|
||||
await deps.stream(activeModel, chatProvider, [systemMessage, userMessage], {
|
||||
tools: [
|
||||
sparkNoResponseTool,
|
||||
sparkCommandTool,
|
||||
],
|
||||
supportsTools: true, // we expect tools to be supported
|
||||
waitForTools: true, // see https://github.com/moeru-ai/airi/issues/907
|
||||
supportsTools: true,
|
||||
waitForTools: true,
|
||||
onStreamEvent: async (streamEvent: StreamEvent) => {
|
||||
if (streamEvent.type === 'text-delta') {
|
||||
if (noResponse)
|
||||
return
|
||||
|
||||
characterStore.onSparkNotifyReactionStreamEvent(event.data.id, streamEvent.text)
|
||||
deps.onReactionDelta(event.data.id, streamEvent.text)
|
||||
|
||||
fullText += streamEvent.text
|
||||
}
|
||||
if (streamEvent.type === 'finish') {
|
||||
if (noResponse) {
|
||||
characterStore.onSparkNotifyReactionStreamEnd(event.data.id, '')
|
||||
deps.onReactionEnd(event.data.id, '')
|
||||
return
|
||||
}
|
||||
|
||||
characterStore.onSparkNotifyReactionStreamEnd(event.data.id, fullText)
|
||||
deps.onReactionEnd(event.data.id, fullText)
|
||||
}
|
||||
if (streamEvent.type === 'error') {
|
||||
characterStore.onSparkNotifyReactionStreamEnd(event.data.id, fullText)
|
||||
deps.onReactionEnd(event.data.id, fullText)
|
||||
throw streamEvent.error ?? new Error('Spark notify stream error')
|
||||
}
|
||||
},
|
||||
@@ -205,17 +223,17 @@ export const useCharacterOrchestratorStore = defineStore('character-orchestrator
|
||||
} satisfies SparkNotifyResponse
|
||||
}
|
||||
|
||||
async function handleSparkNotify(event: WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify']>) {
|
||||
if (event.data.urgency !== 'immediate' && pendingNotifies.value.length > 0) {
|
||||
pendingNotifies.value = [...pendingNotifies.value, event]
|
||||
async function handle(event: WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify']>) {
|
||||
if (event.data.urgency !== 'immediate' && deps.getPending().length > 0) {
|
||||
deps.setPending([...deps.getPending(), event])
|
||||
return undefined
|
||||
}
|
||||
if (processing.value) {
|
||||
pendingNotifies.value = [...pendingNotifies.value, event]
|
||||
if (deps.getProcessing()) {
|
||||
deps.setPending([...deps.getPending(), event])
|
||||
return undefined
|
||||
}
|
||||
|
||||
processing.value = true
|
||||
deps.setProcessing(true)
|
||||
|
||||
try {
|
||||
const response = await runNotifyAgent(event)
|
||||
@@ -243,51 +261,11 @@ export const useCharacterOrchestratorStore = defineStore('character-orchestrator
|
||||
}
|
||||
}
|
||||
finally {
|
||||
processing.value = false
|
||||
deps.setProcessing(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSparkEmit(_: WebSocketBaseEvent<'spark:emit', WebSocketEvents['spark:emit']>) {
|
||||
// Currently no-op
|
||||
return undefined
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
modsServerChannelStore.onEvent('spark:notify', async (event) => {
|
||||
try {
|
||||
const result = await handleSparkNotify(event)
|
||||
if (!result?.commands?.length)
|
||||
return
|
||||
|
||||
for (const command of result.commands) {
|
||||
modsServerChannelStore.send({
|
||||
type: 'spark:command',
|
||||
data: command,
|
||||
})
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.warn('Failed to handle spark:notify event:', error)
|
||||
}
|
||||
})
|
||||
|
||||
modsServerChannelStore.onEvent('spark:emit', async (event) => {
|
||||
try {
|
||||
await handleSparkEmit(event)
|
||||
}
|
||||
catch (error) {
|
||||
console.warn('Failed to handle spark:emit event:', error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
processing,
|
||||
pendingNotifies,
|
||||
|
||||
initialize,
|
||||
|
||||
handleSparkNotify,
|
||||
handleSparkEmit,
|
||||
handle,
|
||||
}
|
||||
})
|
||||
}
|
||||
+7
-7
@@ -7,8 +7,8 @@ import type { Mock } from 'vitest'
|
||||
import type { UnwrapRef } from 'vue'
|
||||
import type z from 'zod'
|
||||
|
||||
import type { StreamEvent } from './llm'
|
||||
import type { AiriCard } from './modules'
|
||||
import type { StreamEvent } from '../../llm'
|
||||
import type { AiriCard } from '../../modules'
|
||||
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { tool } from '@xsai/tool'
|
||||
@@ -16,11 +16,11 @@ import { nanoid } from 'nanoid'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useCharacterStore } from './character'
|
||||
import { sparkCommandSchema, useCharacterOrchestratorStore } from './character-orchestrator'
|
||||
import { useLLM } from './llm'
|
||||
import { useAiriCardStore, useConsciousnessStore } from './modules'
|
||||
import { useProvidersStore } from './providers'
|
||||
import { sparkCommandSchema, useCharacterOrchestratorStore } from '.'
|
||||
import { useCharacterStore } from '..'
|
||||
import { useLLM } from '../../llm'
|
||||
import { useAiriCardStore, useConsciousnessStore } from '../../modules'
|
||||
import { useProvidersStore } from '../../providers'
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
@@ -0,0 +1 @@
|
||||
export * from './store'
|
||||
@@ -0,0 +1,82 @@
|
||||
import type { WebSocketBaseEvent, WebSocketEvents } from '@proj-airi/server-sdk'
|
||||
|
||||
import { defineStore, storeToRefs } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { useCharacterStore } from '../'
|
||||
import { useLLM } from '../../llm'
|
||||
import { useModsServerChannelStore } from '../../mods/api/channel-server'
|
||||
import { useConsciousnessStore } from '../../modules/consciousness'
|
||||
import { useProvidersStore } from '../../providers'
|
||||
import { setupAgentSparkNotifyHandler } from './agents/event-handler-spark-notify'
|
||||
|
||||
export { sparkCommandSchema } from './agents/event-handler-spark-notify'
|
||||
|
||||
export const useCharacterOrchestratorStore = defineStore('character-orchestrator', () => {
|
||||
const { stream } = useLLM()
|
||||
const { activeProvider, activeModel } = storeToRefs(useConsciousnessStore())
|
||||
const providersStore = useProvidersStore()
|
||||
const characterStore = useCharacterStore()
|
||||
const { systemPrompt } = storeToRefs(characterStore)
|
||||
const modsServerChannelStore = useModsServerChannelStore()
|
||||
|
||||
const processing = ref(false)
|
||||
const pendingNotifies = ref<Array<WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify']>>>([])
|
||||
const sparkNotifyAgent = setupAgentSparkNotifyHandler({
|
||||
stream,
|
||||
getActiveProvider: () => activeProvider.value,
|
||||
getActiveModel: () => activeModel.value,
|
||||
getProviderInstance: name => providersStore.getProviderInstance(name),
|
||||
onReactionDelta: (eventId, text) => characterStore.onSparkNotifyReactionStreamEvent(eventId, text),
|
||||
onReactionEnd: (eventId, text) => characterStore.onSparkNotifyReactionStreamEnd(eventId, text),
|
||||
getSystemPrompt: () => systemPrompt.value,
|
||||
getProcessing: () => processing.value,
|
||||
setProcessing: next => processing.value = next,
|
||||
getPending: () => pendingNotifies.value,
|
||||
setPending: next => pendingNotifies.value = next,
|
||||
})
|
||||
|
||||
async function handleSparkEmit(_: WebSocketBaseEvent<'spark:emit', WebSocketEvents['spark:emit']>) {
|
||||
// Currently no-op
|
||||
return undefined
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
modsServerChannelStore.onEvent('spark:notify', async (event) => {
|
||||
try {
|
||||
const result = await sparkNotifyAgent.handle(event)
|
||||
if (!result?.commands?.length)
|
||||
return
|
||||
|
||||
for (const command of result.commands) {
|
||||
modsServerChannelStore.send({
|
||||
type: 'spark:command',
|
||||
data: command,
|
||||
})
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.warn('Failed to handle spark:notify event:', error)
|
||||
}
|
||||
})
|
||||
|
||||
modsServerChannelStore.onEvent('spark:emit', async (event) => {
|
||||
try {
|
||||
await handleSparkEmit(event)
|
||||
}
|
||||
catch (error) {
|
||||
console.warn('Failed to handle spark:emit event:', error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
processing,
|
||||
pendingNotifies,
|
||||
|
||||
initialize,
|
||||
|
||||
handleSparkNotify: sparkNotifyAgent.handle,
|
||||
handleSparkEmit,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user