feat: improved TypeScript 7 support; weekly fresh install workflow

This commit is contained in:
louistiti
2026-07-19 07:41:22 +08:00
parent bc2854c33f
commit 4823bffb37
17 changed files with 1190 additions and 1510 deletions
+60
View File
@@ -0,0 +1,60 @@
name: Fresh installation
on:
schedule:
- cron: '17 4 * * 1'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: fresh-install-${{ github.ref }}
cancel-in-progress: false
jobs:
fresh-install:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
# Scheduled workflows are loaded from the default branch, but Leon's
# integration work lands on develop before it reaches a release branch.
- name: Check out Leon
uses: actions/checkout@v6
with:
ref: ${{ github.event_name == 'schedule' && 'develop' || github.ref }}
- name: Read runtime versions
id: versions
shell: bash
run: |
echo "node=$(jq -r '.node' bin/node/versions.json)" >> "$GITHUB_OUTPUT"
echo "pnpm=$(jq -r '.pnpm' bin/pnpm/versions.json)" >> "$GITHUB_OUTPUT"
- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ steps.versions.outputs.node }}
package-manager-cache: false
- name: Install fresh pnpm
shell: bash
run: |
corepack enable
corepack install --global "pnpm@${{ steps.versions.outputs.pnpm }}"
- name: Test the interactive installation
env:
LEON_FRESH_INSTALL_API_KEY: xxx
LEON_FRESH_INSTALL_LOG_PATH: /tmp/leon-fresh-install.log
run: node scripts/ci/test-fresh-install.js
- name: Upload installer log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: fresh-install-log
path: /tmp/leon-fresh-install.log
if-no-files-found: ignore
retention-days: 7
+3 -3
View File
@@ -10,14 +10,14 @@ export default function renderAuroraComponent(
supportedEvents
) {
if (component) {
// `import/namespace` cannot statically validate dynamic component lookups.
// eslint-disable-next-line import/namespace
// `import-x/namespace` cannot statically validate dynamic component lookups.
// eslint-disable-next-line import-x/namespace
let reactComponent = auroraComponents[component.component]
/**
* Find custom component if a former component is not found
*/
if (!reactComponent) {
// eslint-disable-next-line import/namespace
// eslint-disable-next-line import-x/namespace
reactComponent = customAuroraComponents[component.component]
}
-1
View File
@@ -3,7 +3,6 @@
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"rootDir": "./src",
"outDir": "./dist",
"baseUrl": ".",
"moduleResolution": "Bundler",
"module": "ESNext",
"target": "ESNext",
+7 -5
View File
@@ -172,11 +172,13 @@ class Leon {
onFetch: answerInput.widget.onFetch ?? null,
fallbackText,
historyMode: answerInput.widgetHistoryMode || 'persisted',
componentTree: new WidgetWrapper({
...answerInput.widget.wrapperProps,
children: [answerInput.widget.render()]
}),
supportedEvents: SUPPORTED_WIDGET_EVENTS as string[]
componentTree: {
...new WidgetWrapper({
...answerInput.widget.wrapperProps,
children: [answerInput.widget.render()]
})
},
supportedEvents: [...SUPPORTED_WIDGET_EVENTS]
}
}
-1
View File
@@ -3,7 +3,6 @@
"compilerOptions": {
"outDir": "./dist/bin",
"rootDir": "../../",
"baseUrl": ".",
"paths": {
"@@/*": ["../../*"],
"@/*": ["../../server/src/*"],
+24 -20
View File
@@ -1,7 +1,13 @@
import stylistic from '@stylistic/eslint-plugin'
import typescriptEslint from '@typescript-eslint/eslint-plugin'
import unicorn from 'eslint-plugin-unicorn'
import importPlugin from 'eslint-plugin-import'
import {
createNodeResolver,
importX
} from 'eslint-plugin-import-x'
import {
createTypeScriptImportResolver
} from 'eslint-import-resolver-typescript'
import globals from 'globals'
import tsParser from '@typescript-eslint/parser'
import js from '@eslint/js'
@@ -12,8 +18,8 @@ export default [
},
js.configs.recommended,
...typescriptEslint.configs['flat/recommended'],
importPlugin.flatConfigs.recommended,
importPlugin.flatConfigs.typescript,
importX.flatConfigs.recommended,
importX.flatConfigs.typescript,
{
plugins: {
'@stylistic': stylistic,
@@ -29,12 +35,18 @@ export default [
sourceType: 'module'
},
settings: {
'import/resolver': {
typescript: true,
node: true
}
'import-x/resolver-next': [
createTypeScriptImportResolver(),
createNodeResolver()
]
},
rules: {
// Preserve the existing cleanup patterns until they can be reviewed
// independently from the ESLint dependency upgrade.
'no-useless-assignment': 'off',
// Leon often converts infrastructure failures into domain-specific
// errors whose public messages intentionally omit the original cause.
'preserve-caught-error': 'off',
'@typescript-eslint/no-non-null-assertion': ['off'],
'no-async-promise-executor': ['off'],
'no-underscore-dangle': [
@@ -44,12 +56,10 @@ export default [
}
],
'prefer-destructuring': ['off'],
'comma-dangle': ['error', 'never'],
'@stylistic/comma-dangle': ['error', 'never'],
semi: ['error', 'never'],
quotes: ['error', 'single'],
'@stylistic/semi': ['error', 'never'],
'@stylistic/quotes': ['error', 'single'],
'object-curly-spacing': ['error', 'always'],
'@stylistic/object-curly-spacing': ['error', 'always'],
'unicorn/prefer-node-protocol': 'error',
'@stylistic/member-delimiter-style': [
'error',
@@ -66,15 +76,9 @@ export default [
],
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/consistent-type-definitions': 'error',
'import/no-named-as-default': 'off',
'import/no-named-as-default-member': 'off',
'import/order': 'off'
}
},
{
files: ['skills/**/*.ts'],
rules: {
'import/order': 'off'
'import-x/no-named-as-default': 'off',
'import-x/no-named-as-default-member': 'off',
'import-x/order': 'off'
}
},
{
+1 -1
View File
@@ -8,5 +8,5 @@
"server/src/tmp",
"server/dist"
],
"exec": "node scripts/run-with-managed-node.js node_modules/typescript/bin/tsc --noEmit -p tsconfig.json && node scripts/run-with-managed-node.js node_modules/tsx/dist/cli.mjs server/src/index.ts"
"exec": "pnpm exec tsc --noEmit -p tsconfig.json && node scripts/run-with-managed-node.js node_modules/tsx/dist/cli.mjs server/src/index.ts"
}
+11 -11
View File
@@ -27,6 +27,7 @@
},
"packageManager": {
"name": "pnpm",
"version": "11.1.1",
"onFail": "error"
}
},
@@ -118,9 +119,7 @@
"yaml": "2.8.3"
},
"devDependencies": {
"@eslint/compat": "1.2.3",
"@eslint/eslintrc": "3.3.5",
"@eslint/js": "9.15.0",
"@eslint/js": "10.0.1",
"@stylistic/eslint-plugin": "5.10.0",
"@tsconfig/node16": "16.1.8",
"@tsconfig/strictest": "2.0.8",
@@ -131,17 +130,18 @@
"@types/node": "25.6.0",
"@types/react": "19.2.15",
"@types/react-dom": "19.2.3",
"@typescript-eslint/eslint-plugin": "8.58.1",
"@typescript-eslint/parser": "8.58.1",
"@typescript-eslint/eslint-plugin": "8.64.0",
"@typescript-eslint/parser": "8.64.0",
"@typescript/native": "npm:typescript@7.0.2",
"@vitejs/plugin-react": "6.0.2",
"cli-spinner": "0.2.10",
"esbuild": "0.27.4",
"eslint": "10.2.0",
"eslint-import-resolver-typescript": "4.4.4",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-unicorn": "49.0.0",
"eslint": "10.7.0",
"eslint-import-resolver-typescript": "4.4.5",
"eslint-plugin-import-x": "4.17.1",
"eslint-plugin-unicorn": "72.0.0",
"git-changelog": "2.0.0",
"globals": "15.12.0",
"globals": "17.7.0",
"husky": "9.1.7",
"json": "11.0.0",
"lint-staged": "15.1.0",
@@ -152,7 +152,7 @@
"semver": "7.5.4",
"shx": "0.3.4",
"tsx": "4.20.5",
"typescript": "5.9.3",
"typescript": "npm:@typescript/typescript6@6.0.2",
"vite": "8.0.14",
"vitest": "4.1.4"
}
+609 -1424
View File
File diff suppressed because it is too large Load Diff
+362
View File
@@ -0,0 +1,362 @@
#!/usr/bin/env node
import { spawn, spawnSync } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
import { stripVTControlCharacters } from 'node:util'
const INSTALL_COMMAND =
'stty rows 40 cols 120 && pnpm install --frozen-lockfile'
const INSTALL_TIMEOUT = 45 * 60 * 1_000
const BUILD_TIMEOUT = 10 * 60 * 1_000
const START_TIMEOUT = 2 * 60 * 1_000
const PROCESS_STOP_TIMEOUT = 5_000
const OUTPUT_BUFFER_LIMIT = 32_000
const DEFAULT_API_KEY = 'xxx'
const DEFAULT_LOG_PATH = '/tmp/leon-fresh-install.log'
const SERVER_READY_MESSAGE = 'Server is available at '
const SCRIPT_ARGUMENTS = [
'--quiet',
'--return',
'--flush',
'--echo',
'never',
'--command',
INSTALL_COMMAND,
'/dev/null'
]
const PROMPTS = [
{
name: 'local AI',
text: 'Do you want me to set up local AI now?',
response: 'n\r',
required: false
},
{
name: 'remote provider',
text: 'Which online AI service should I use?',
response: '\r',
required: true
},
{
name: 'remote model',
text: 'Which model should I use with',
response: '\r',
required: true
},
{
name: 'API key',
text: 'I will save it in your local .env file.',
response: null,
required: true
},
{
name: 'voice',
text: 'Do you want to talk to me with your voice now?',
response: 'n\r',
required: true
},
{
name: 'finish',
text: 'What do you want to do next?',
// Select Finish explicitly so a changed default cannot start Leon here.
response: '\x1b[B\r',
required: true
}
]
let activeChild = null
function getCleanEnvironment() {
const environment = { ...process.env }
delete environment.GITHUB_ACTIONS
delete environment.IS_DOCKER
environment.CI = 'true'
environment.COLUMNS = '120'
environment.LINES = '40'
return environment
}
function stopProcessGroup(child, signal = 'SIGTERM') {
if (!child?.pid) {
return
}
try {
process.kill(-child.pid, signal)
} catch (error) {
if (error.code !== 'ESRCH') {
throw error
}
}
}
function stopActiveChildAndExit(signal) {
if (activeChild) {
stopProcessGroup(activeChild, signal)
}
process.exit(signal === 'SIGINT' ? 130 : 143)
}
process.once('SIGINT', () => stopActiveChildAndExit('SIGINT'))
process.once('SIGTERM', () => stopActiveChildAndExit('SIGTERM'))
function ensureEmptyPNPMStore(environment) {
const result = spawnSync('pnpm', ['store', 'path'], {
encoding: 'utf8',
env: environment
})
if (result.status !== 0) {
throw new Error(`Unable to resolve pnpm store path: ${result.stderr}`)
}
const storePath = result.stdout.trim()
if (fs.existsSync(storePath) && fs.readdirSync(storePath).length > 0) {
throw new Error(`pnpm store is not empty: ${storePath}`)
}
console.log(`Fresh pnpm store confirmed: ${storePath}`)
}
function normalizeOutput(output) {
return stripVTControlCharacters(output).replaceAll('\r', '')
}
function getPromptResponse(prompt, apiKey) {
return prompt.name === 'API key' ? `${apiKey}\r` : prompt.response
}
function runInteractiveInstall(environment, apiKey, logPath) {
return new Promise((resolve, reject) => {
fs.mkdirSync(path.dirname(logPath), { recursive: true })
const logStream = fs.createWriteStream(logPath)
const answeredPrompts = new Set()
let recentOutput = ''
let hasSettled = false
const child = spawn('script', SCRIPT_ARGUMENTS, {
detached: true,
env: environment,
stdio: ['pipe', 'pipe', 'pipe']
})
activeChild = child
const finish = (error) => {
if (hasSettled) {
return
}
hasSettled = true
clearTimeout(timeout)
logStream.end()
activeChild = null
if (error) {
reject(error)
} else {
resolve()
}
}
const handleOutput = (chunk, destination) => {
destination.write(chunk)
logStream.write(chunk)
recentOutput = `${recentOutput}${chunk}`.slice(-OUTPUT_BUFFER_LIMIT)
const normalizedOutput = normalizeOutput(recentOutput)
for (const prompt of PROMPTS) {
if (
!answeredPrompts.has(prompt.name) &&
normalizedOutput.includes(prompt.text)
) {
child.stdin.write(getPromptResponse(prompt, apiKey))
answeredPrompts.add(prompt.name)
console.log(`\n[clean-install] Answered: ${prompt.name}`)
}
}
}
child.stdout.on('data', (chunk) => handleOutput(chunk, process.stdout))
child.stderr.on('data', (chunk) => handleOutput(chunk, process.stderr))
child.once('error', finish)
child.once('close', (code, signal) => {
if (code !== 0) {
finish(
new Error(
`pnpm install exited with ${signal ? `signal ${signal}` : `code ${code}`}`
)
)
return
}
const missingPrompts = PROMPTS.filter(
(prompt) => prompt.required && !answeredPrompts.has(prompt.name)
).map((prompt) => prompt.name)
if (missingPrompts.length > 0) {
finish(
new Error(
`Installer did not present required prompts: ${missingPrompts.join(', ')}`
)
)
return
}
finish()
})
const timeout = setTimeout(() => {
stopProcessGroup(child)
finish(new Error(`Installation exceeded ${INSTALL_TIMEOUT} ms`))
}, INSTALL_TIMEOUT)
})
}
function runCommand(command, args, environment, timeoutMs) {
return new Promise((resolve, reject) => {
let hasSettled = false
const child = spawn(command, args, {
detached: true,
env: environment,
stdio: 'inherit'
})
activeChild = child
const finish = (error) => {
if (hasSettled) {
return
}
hasSettled = true
clearTimeout(timeout)
activeChild = null
if (error) {
reject(error)
} else {
resolve()
}
}
child.once('error', finish)
child.once('close', (code, signal) => {
if (code === 0) {
finish()
return
}
finish(
new Error(
`${command} ${args.join(' ')} exited with ${
signal ? `signal ${signal}` : `code ${code}`
}`
)
)
})
const timeout = setTimeout(() => {
stopProcessGroup(child)
finish(new Error(`${command} ${args.join(' ')} exceeded ${timeoutMs} ms`))
}, timeoutMs)
})
}
function smokeTestStart(environment) {
return new Promise((resolve, reject) => {
let hasSettled = false
let isReady = false
let output = ''
let forceStopTimeout = null
const child = spawn('pnpm', ['start'], {
detached: true,
env: {
...environment,
LEON_OPEN_BROWSER: 'false'
},
stdio: ['ignore', 'pipe', 'pipe']
})
activeChild = child
const finish = (error) => {
if (hasSettled) {
return
}
hasSettled = true
clearTimeout(startTimeout)
clearTimeout(forceStopTimeout)
activeChild = null
if (error) {
reject(error)
} else {
resolve()
}
}
const handleOutput = (chunk, destination) => {
destination.write(chunk)
output = `${output}${chunk}`.slice(-OUTPUT_BUFFER_LIMIT)
if (!isReady && normalizeOutput(output).includes(SERVER_READY_MESSAGE)) {
isReady = true
stopProcessGroup(child)
forceStopTimeout = setTimeout(() => {
stopProcessGroup(child, 'SIGKILL')
}, PROCESS_STOP_TIMEOUT)
}
}
child.stdout.on('data', (chunk) => handleOutput(chunk, process.stdout))
child.stderr.on('data', (chunk) => handleOutput(chunk, process.stderr))
child.once('error', finish)
child.once('close', (code, signal) => {
if (isReady) {
finish()
return
}
finish(
new Error(
`pnpm start exited before Leon was ready with ${
signal ? `signal ${signal}` : `code ${code}`
}`
)
)
})
const startTimeout = setTimeout(() => {
stopProcessGroup(child)
finish(new Error(`pnpm start exceeded ${START_TIMEOUT} ms`))
}, START_TIMEOUT)
})
}
async function main() {
const environment = getCleanEnvironment()
const apiKey = process.env.LEON_FRESH_INSTALL_API_KEY || DEFAULT_API_KEY
const logPath =
process.env.LEON_FRESH_INSTALL_LOG_PATH || DEFAULT_LOG_PATH
ensureEmptyPNPMStore(environment)
await runInteractiveInstall(environment, apiKey, logPath)
await runCommand('pnpm', ['build'], environment, BUILD_TIMEOUT)
await smokeTestStart(environment)
console.log('Fresh installation, build, and start verification passed.')
}
main().catch((error) => {
if (activeChild) {
stopProcessGroup(activeChild)
}
console.error(error)
process.exit(1)
})
+18 -1
View File
@@ -9,6 +9,18 @@ import { createSetupStatus } from './setup-status'
const MOVE_FALLBACK_ERROR_CODES = new Set(['EXDEV', 'EPERM', 'EBUSY', 'EACCES'])
const QMD_MODELS_DIR_PATH = path.join(homedir(), '.cache', 'qmd', 'models')
const QMD_DOWNLOAD_RETRY_OPTIONS = {
retries: 5,
factor: 1.5,
minTimeout: 1_000,
maxTimeout: 10_000
}
const QMD_DOWNLOAD_INFO_RETRY_OPTIONS = {
retries: 3,
factor: 1.5,
minTimeout: 1_000,
maxTimeout: 5_000
}
const QMD_MODELS = [
{
@@ -73,7 +85,12 @@ async function downloadModel(model) {
const resolvedURL = await NetworkHelper.setHuggingFaceURL(model.url)
await FileHelper.downloadFile(resolvedURL, destinationPath)
// Model files are large enough that transient CDN failures are common.
// Keep resumable downloads alive longer than the general file default.
await FileHelper.downloadFile(resolvedURL, destinationPath, {
retry: QMD_DOWNLOAD_RETRY_OPTIONS,
retryFetchDownloadInfo: QMD_DOWNLOAD_INFO_RETRY_OPTIONS
})
return 'downloaded'
}
+41 -10
View File
@@ -1,5 +1,6 @@
import fs from 'node:fs'
import path from 'node:path'
import { setTimeout as sleep } from 'node:timers/promises'
import execa from 'execa'
@@ -13,6 +14,8 @@ import { createSetupStatus } from './setup-status'
const NLTK_DATA_DIR_NAME = 'nltk_data'
const NLTK_VENV_DIR_NAME = '.venv'
const NLTK_DOWNLOAD_MAXIMUM_ATTEMPTS = 3
const NLTK_DOWNLOAD_RETRY_DELAY = 2_000
const PYTHON_TCP_SERVER_VENV_BIN_PATH = getProjectVenvPythonPath(
PYTHON_TCP_SERVER_SRC_PATH
)
@@ -45,6 +48,41 @@ async function isNLTKDatasetInstalled(resourcePath) {
}
}
// Retry in a new Python process because NLTK keeps its downloaded index in
// memory, including a truncated response from a transient network failure.
async function runNLTKDownloader(datasetIDs, status) {
let lastError = null
for (
let attempt = 1;
attempt <= NLTK_DOWNLOAD_MAXIMUM_ATTEMPTS;
attempt += 1
) {
try {
await execa(PYTHON_TCP_SERVER_VENV_BIN_PATH, [
'-m',
'nltk.downloader',
'-d',
NLTK_DATA_PATH,
...datasetIDs
])
return
} catch (error) {
lastError = error
if (attempt === NLTK_DOWNLOAD_MAXIMUM_ATTEMPTS) {
break
}
status.text = `NLTK data download failed, retrying (${attempt}/${NLTK_DOWNLOAD_MAXIMUM_ATTEMPTS})...`
await sleep(NLTK_DOWNLOAD_RETRY_DELAY)
}
}
throw lastError
}
/**
* NLTK data are used by g2p-en during TTS text normalization.
*
@@ -75,16 +113,9 @@ async function downloadNLTKData() {
.map(({ id }) => id)
.join(', ')}`
await execa(
PYTHON_TCP_SERVER_VENV_BIN_PATH,
[
'-m',
'nltk.downloader',
'-d',
NLTK_DATA_PATH,
...missingDatasets.map(({ id }) => id)
],
{ stdio: 'ignore' }
await runNLTKDownloader(
missingDatasets.map(({ id }) => id),
status
)
for (const dataset of missingDatasets) {
@@ -166,15 +166,22 @@ export class NLUProcessResultUpdater {
if (newResult.actionName && newResult.actionName !== '') {
const { skillName } = NLU.nluProcessResult
const skillConfig = await SkillDomainHelper.getNewSkillConfig(skillName)
const newActionConfig =
skillConfig?.actions?.[newResult.actionName] || null
// Narrow the generated schema to the records merged below instead of
// making the compiler expand the complete recursive TypeBox type.
const skillActions = skillConfig?.actions as
| Record<string, Record<string, unknown> | undefined>
| undefined
const newActionConfig = skillActions?.[newResult.actionName] || null
const newSkillLocaleConfig =
(await SkillDomainHelper.getSkillLocaleConfig(
BRAIN.lang,
skillName
)) as SkillLocaleConfigSchema
const newActionLocaleConfig =
newSkillLocaleConfig['actions'][newResult.actionName]
const localeActions = newSkillLocaleConfig['actions'] as Record<
string,
Record<string, unknown> | undefined
>
const newActionLocaleConfig = localeActions[newResult.actionName]
if (!newActionLocaleConfig) {
LogHelper.title('NLU')
@@ -186,12 +193,12 @@ export class NLUProcessResultUpdater {
NLU.nluProcessResult = {
...NLU.nluProcessResult,
actionName: newResult.actionName,
actionConfig: newActionConfig
actionConfig: (newActionConfig
? {
...newActionConfig,
...newActionLocaleConfig
}
: newActionConfig
: newActionConfig) as NLUProcessResult['actionConfig']
}
return
+24 -7
View File
@@ -405,12 +405,23 @@ export default class NLU {
/**
* Compute required parameters for an action by excluding optional_parameters
*/
private getRequiredParamsForAction(
actionConfig: NLUProcessResult['actionConfig']
): string[] {
const allParams = Object.keys(actionConfig?.parameters || {})
const optionalParams: string[] = (actionConfig?.optional_parameters ||
[]) as string[]
private getRequiredParamsForAction(actionConfig: unknown): string[] {
if (!actionConfig || typeof actionConfig !== 'object') {
return []
}
// Only expand the fields needed here; the complete generated schema is
// recursive and considerably more expensive for the compiler to infer.
const config = actionConfig as Record<string, unknown>
const parameters =
config['parameters'] && typeof config['parameters'] === 'object'
? config['parameters']
: {}
const optionalParams = Array.isArray(config['optional_parameters'])
? (config['optional_parameters'] as string[])
: []
const allParams = Object.keys(parameters)
return allParams.filter((p) => !optionalParams.includes(p))
}
@@ -1218,7 +1229,13 @@ export default class NLU {
if (hasPendingAction) {
this.workflowProgress.showResolvingParameters()
const [slotName] = this.conversation.activeState.missingParameters
const actionConfig = this._nluProcessResult.actionConfig
// Limit inference to the parameter fields consumed by slot filling.
const actionConfig = this._nluProcessResult.actionConfig as {
parameters?: Record<
string,
{ description?: string, type?: string } | undefined
>
} | null
const param = actionConfig?.parameters?.[slotName as string]
if (!slotName || !param) {
+14 -16
View File
@@ -1,4 +1,3 @@
import type { DefaultEventsMap } from 'socket.io/dist/typed-events'
import { Server as SocketIOServer, Socket } from 'socket.io'
import axios from 'axios'
@@ -125,7 +124,7 @@ interface ConnectedChatClient {
protocol: LeonClientInterfaceProtocol
profileName: string
sessionId: string
socket: Socket<DefaultEventsMap, DefaultEventsMap>
socket: Socket
}
export default class SocketServer {
@@ -136,8 +135,7 @@ export default class SocketServer {
{ profileName: string, deviceId: string }
>()
public socket: Socket<DefaultEventsMap, DefaultEventsMap> | undefined =
undefined
public socket: Socket | undefined = undefined
constructor() {
if (!SocketServer.instance) {
@@ -149,7 +147,7 @@ export default class SocketServer {
}
private setActiveSocket(
socket: Socket<DefaultEventsMap, DefaultEventsMap>
socket: Socket
): void {
this.socket = socket
}
@@ -198,7 +196,7 @@ export default class SocketServer {
}
private registerChatClient(
socket: Socket<DefaultEventsMap, DefaultEventsMap>,
socket: Socket,
initData: InitDataEvent,
profileName: string,
protocol: LeonClientInterfaceProtocol = 'legacy'
@@ -226,7 +224,7 @@ export default class SocketServer {
}
private registerLeonClient(
socket: Socket<DefaultEventsMap, DefaultEventsMap>,
socket: Socket,
initData: LeonClientInterfaceInitPayload,
profileName: string
): ConnectedChatClient {
@@ -294,7 +292,7 @@ export default class SocketServer {
}
private getSocketAuthToken(
socket: Socket<DefaultEventsMap, DefaultEventsMap>,
socket: Socket,
initData?: { token?: string }
): string {
const handshakeAuthToken = socket.handshake.auth?.['token']
@@ -311,7 +309,7 @@ export default class SocketServer {
}
private isLeonClientInterfaceAuthorized(
socket: Socket<DefaultEventsMap, DefaultEventsMap>,
socket: Socket,
initData?: { token?: string }
): string | null {
const initToken = this.getSocketAuthToken(socket, initData)
@@ -332,7 +330,7 @@ export default class SocketServer {
}
private emitSocketEvent(
socket: Socket<DefaultEventsMap, DefaultEventsMap>,
socket: Socket,
eventName: string,
payload?: unknown
): void {
@@ -625,7 +623,7 @@ export default class SocketServer {
}
private monitorLLMInitialization(
socket: Socket<DefaultEventsMap, DefaultEventsMap>,
socket: Socket,
options: {
profileName: string
usesLlamaCPP: boolean
@@ -666,7 +664,7 @@ export default class SocketServer {
}
private emitLeonClientReady(
socket: Socket<DefaultEventsMap, DefaultEventsMap>,
socket: Socket,
sessionId: string
): void {
socket.emit(LEON_CLIENT_INTERFACE_EVENTS.ready, {
@@ -676,7 +674,7 @@ export default class SocketServer {
}
private emitLeonClientError(
socket: Socket<DefaultEventsMap, DefaultEventsMap>,
socket: Socket,
payload: LeonClientInterfaceErrorPayload
): void {
socket.emit(LEON_CLIENT_INTERFACE_EVENTS.error, payload)
@@ -718,7 +716,7 @@ export default class SocketServer {
}
private async handleOwnerMessage(
socket: Socket<DefaultEventsMap, DefaultEventsMap>,
socket: Socket,
utteranceData: UtteranceDataEvent
): Promise<void> {
const profileName =
@@ -730,7 +728,7 @@ export default class SocketServer {
}
private async processOwnerMessage(
socket: Socket<DefaultEventsMap, DefaultEventsMap>,
socket: Socket,
utteranceData: UtteranceDataEvent
): Promise<void> {
this.setActiveSocket(socket)
@@ -834,7 +832,7 @@ export default class SocketServer {
}
private async handleWidgetEvent(
socket: Socket<DefaultEventsMap, DefaultEventsMap>,
socket: Socket,
event: WidgetDataEvent
): Promise<void> {
const profileName =
+1 -2
View File
@@ -3,9 +3,8 @@
"compilerOptions": {
"lib": ["ESNext"],
"target": "ES2022",
"moduleResolution": "Node",
"moduleResolution": "Bundler",
"module": "ESNext",
"baseUrl": ".",
"paths": {
"@@/*": ["../*"],
"@/*": ["../server/src/*"],
+2 -2
View File
@@ -4,8 +4,7 @@
"lib": ["ESNext"],
"rootDir": ".",
"outDir": "./server/dist",
"baseUrl": ".",
"moduleResolution": "Node",
"moduleResolution": "Bundler",
"module": "ESNext",
"jsx": "react",
"paths": {
@@ -20,6 +19,7 @@
},
"allowJs": true,
"checkJs": false,
"types": ["node"],
"resolveJsonModule": true,
"declaration": true
},