import { jsonrepair } from 'jsonrepair'
import * as jsonpatch from 'fast-json-patch'
import { utils } from '@opentiny/tiny-engine-utils'
import { useCanvas, useHistory, useMessage } from '@opentiny/tiny-engine-meta-register'
import { useThrottleFn } from '@vueuse/core'
import useModelConfig from './useConfig'
import { ChatMode } from '../../types/mode.types'
import {
fixMethods,
schemaAutoFix,
getJsonObjectString,
isValidFastJsonPatch,
isValidSchemaChildren,
jsonPatchAutoFix
} from '../../utils'
const { deepClone } = utils
const logger = console
let schemaUpdateVersion = 0
let lastSuccessfulSchema: object | null = null
const setSchema = async (schema: object, addHistory = false) => {
const { importSchema, pageState, setSaved } = useCanvas()
if (addHistory) {
importSchema(schema)
useHistory().addHistory()
} else {
Object.assign(pageState.pageSchema, schema)
useMessage().publish({ topic: 'schemaChange', data: {} })
}
setSaved(false)
lastSuccessfulSchema = schema
}
type UpdateResult =
| undefined
| { isError: false; schema: object; error?: undefined }
| { isError: true; schema?: undefined; error: unknown }
const _updatePageSchema = async (
streamContent: string,
currentPageSchema: object,
isFinal: boolean = false,
version = schemaUpdateVersion
): Promise<UpdateResult> => {
if (version !== schemaUpdateVersion) {
return
}
const { getSelectedModelInfo } = useModelConfig()
if (getSelectedModelInfo().config?.chatMode !== ChatMode.Agent) {
return
}
let content = getJsonObjectString(streamContent)
let jsonPatches = []
try {
if (!isFinal) {
content = jsonrepair(content)
}
jsonPatches = JSON.parse(content)
} catch (error) {
if (isFinal) {
logger.error('parse json patch error:', error)
}
return { isError: true, error }
}
if (!isFinal && !isValidFastJsonPatch(jsonPatches)) {
return { isError: true, error: 'format error: not a valid json patch.' }
}
const validJsonPatches = jsonPatchAutoFix(jsonPatches, isFinal)
const originSchema = deepClone(currentPageSchema)
const newSchema = validJsonPatches.reduce((acc: object, patch: any) => {
try {
return jsonpatch.applyPatch(acc, [patch], false, false).newDocument
} catch (error) {
if (isFinal) {
logger.error('apply patch error:', error, patch)
}
return acc
}
}, originSchema)
fixMethods(newSchema.methods)
if (!isValidSchemaChildren(newSchema.children)) {
return { isError: true, error: 'format error: schema children contains invalid nodes.' }
}
schemaAutoFix(newSchema.children)
try {
await setSchema(newSchema, isFinal)
} catch (error) {
if (isFinal) {
logger.error('set schema error:', error)
}
return { isError: true, error }
}
return { schema: newSchema, isError: false }
}
const updatePageSchemaThrottled = useThrottleFn(_updatePageSchema, 200, true)
const invalidatePendingStreamUpdates = () => {
schemaUpdateVersion++
}
export const resetPageSchemaUpdateState = () => {
invalidatePendingStreamUpdates()
lastSuccessfulSchema = null
}
export const getLastSuccessfulPageSchema = () => lastSuccessfulSchema
export const updatePageSchema = (
streamContent: string,
currentPageSchema: object,
isFinal: boolean = false
): UpdateResult | Promise<UpdateResult> => {
if (isFinal) {
invalidatePendingStreamUpdates()
return _updatePageSchema(streamContent, currentPageSchema, isFinal, schemaUpdateVersion)
}
return updatePageSchemaThrottled(streamContent, currentPageSchema, isFinal, schemaUpdateVersion)
}