kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
refactor: rename props and update tracker output
rodzic
e7b3c9aa14
commit
c09c24ac66
|
@ -4,34 +4,43 @@ import readline from 'node:readline'
|
||||||
import { bgWhite, black, bold, cyan, gray, green, red, yellow } from 'colorette'
|
import { bgWhite, black, bold, cyan, gray, green, red, yellow } from 'colorette'
|
||||||
|
|
||||||
import { SPACE } from '@/constants'
|
import { SPACE } from '@/constants'
|
||||||
|
import { capitalize } from '@/utils'
|
||||||
|
|
||||||
import { TaskEvent, TaskStatus } from './event'
|
import { TaskEvent, TaskStatus } from './event'
|
||||||
import { SYMBOLS } from './symbols'
|
import { SYMBOLS } from './symbols'
|
||||||
|
|
||||||
const MAGIC_STRING = '__INSIDE_TRACKER__' // Define a unique "magic" string
|
export const MAGIC_STRING = '__INSIDE_TRACKER__' // a unique "magic" string that used to identify the output of the tracker
|
||||||
|
|
||||||
// eslint-disable-next-line no-control-regex
|
// eslint-disable-next-line no-control-regex
|
||||||
const RE_ANSI_ESCAPES = /\x1b\[[0-9;]*[A-Za-z]/ // cursor movement, screen clearing, etc.
|
const RE_ANSI_ESCAPES = /^(\x1b\[[0-9;]*[ABCDHJK]|[\r\n])+$/ // cursor movement, screen clearing, etc.
|
||||||
|
|
||||||
const originalStdoutWrite = process.stdout.write
|
const originalStdoutWrite = process.stdout.write
|
||||||
const originalStderrWrite = process.stderr.write
|
const originalStderrWrite = process.stderr.write
|
||||||
|
|
||||||
|
export interface TerminalTaskTrackerOptions {
|
||||||
|
spinnerInterval?: number
|
||||||
|
inactivityThreshold?: number
|
||||||
|
}
|
||||||
|
|
||||||
export class TerminalTaskTracker {
|
export class TerminalTaskTracker {
|
||||||
protected events: Record<string, any[]> = { root: [] }
|
protected _events: Record<string, any[]> = { root: [] }
|
||||||
protected interval: NodeJS.Timeout | null = null
|
protected _interval: NodeJS.Timeout | null = null
|
||||||
protected inactivityTimeout: NodeJS.Timeout | null = null
|
protected _inactivityTimeout: NodeJS.Timeout | null = null
|
||||||
protected truncateOutput = false
|
protected _truncateOutput = false
|
||||||
protected viewMode = 'tasks'
|
protected _viewMode = 'tasks'
|
||||||
protected outputs: Array<string | Uint8Array> = []
|
protected _outputs: Array<string | Uint8Array> = []
|
||||||
protected renderingPaused = false
|
protected _renderingPaused = false
|
||||||
|
|
||||||
protected _spinnerInterval: number
|
protected _spinnerInterval: number
|
||||||
protected _inactivityThreshold: number
|
protected _inactivityThreshold: number
|
||||||
|
|
||||||
private stdoutBuffer: string[] = []
|
private _stdoutBuffer: string[] = []
|
||||||
private stderrBuffer: string[] = []
|
private _stderrBuffer: string[] = []
|
||||||
|
|
||||||
constructor({ spinnerInterval = 100, inactivityThreshold = 2000 } = {}) {
|
constructor({
|
||||||
|
spinnerInterval = 100,
|
||||||
|
inactivityThreshold = 2_000
|
||||||
|
}: TerminalTaskTrackerOptions = {}) {
|
||||||
this._spinnerInterval = spinnerInterval
|
this._spinnerInterval = spinnerInterval
|
||||||
this._inactivityThreshold = inactivityThreshold
|
this._inactivityThreshold = inactivityThreshold
|
||||||
|
|
||||||
|
@ -45,7 +54,10 @@ export class TerminalTaskTracker {
|
||||||
buffer = Buffer.from(buffer).toString('utf-8')
|
buffer = Buffer.from(buffer).toString('utf-8')
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stdoutBuffer.push(buffer)
|
if (!this._renderingPaused) {
|
||||||
|
this._stdoutBuffer.push(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
return originalStdoutWrite.call(process.stdout, buffer)
|
return originalStdoutWrite.call(process.stdout, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,10 +73,10 @@ export class TerminalTaskTracker {
|
||||||
buffer.replace(MAGIC_STRING, '')
|
buffer.replace(MAGIC_STRING, '')
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
if (!RE_ANSI_ESCAPES.test(buffer)) {
|
if (!this._renderingPaused && !RE_ANSI_ESCAPES.test(buffer)) {
|
||||||
// If an ANSI escape sequence is written to stderr, it will mess up the output, so we need to write it to stdout instead:
|
// If an ANSI escape sequence is written to stderr, it will mess up the output, so we need to write it to stdout instead:
|
||||||
// This write is from outside the tracker, add it to stderrBuffer and write to stderr:
|
// This write is from outside the tracker, add it to stderrBuffer and write to stderr:
|
||||||
this.stderrBuffer.push(buffer)
|
this._stderrBuffer.push(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
return originalStderrWrite.call(process.stderr, buffer)
|
return originalStderrWrite.call(process.stderr, buffer)
|
||||||
|
@ -93,7 +105,7 @@ export class TerminalTaskTracker {
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
this.interval = setInterval(() => {
|
this._interval = setInterval(() => {
|
||||||
this.render()
|
this.render()
|
||||||
}, this._spinnerInterval)
|
}, this._spinnerInterval)
|
||||||
|
|
||||||
|
@ -107,12 +119,12 @@ export class TerminalTaskTracker {
|
||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
if (this.interval) {
|
if (this._interval) {
|
||||||
clearInterval(this.interval)
|
clearInterval(this._interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.inactivityTimeout) {
|
if (this._inactivityTimeout) {
|
||||||
clearTimeout(this.inactivityTimeout)
|
clearTimeout(this._inactivityTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
process.stdin.setRawMode(false)
|
process.stdin.setRawMode(false)
|
||||||
|
@ -129,15 +141,14 @@ export class TerminalTaskTracker {
|
||||||
'',
|
'',
|
||||||
bgWhite(black(' Completed all tasks. ')),
|
bgWhite(black(' Completed all tasks. ')),
|
||||||
'',
|
'',
|
||||||
bgWhite(black(' stdout: ')),
|
|
||||||
'',
|
|
||||||
this.stdoutBuffer.join(''),
|
|
||||||
'',
|
|
||||||
'',
|
'',
|
||||||
bgWhite(black(' stderr: ')),
|
bgWhite(black(' stderr: ')),
|
||||||
'',
|
'',
|
||||||
this.stderrBuffer.join(''),
|
this._stderrBuffer.join(''),
|
||||||
'',
|
'',
|
||||||
|
bgWhite(black(' stdout: ')),
|
||||||
|
'',
|
||||||
|
this._stdoutBuffer.join(''),
|
||||||
''
|
''
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -148,16 +159,17 @@ export class TerminalTaskTracker {
|
||||||
}
|
}
|
||||||
|
|
||||||
pause() {
|
pause() {
|
||||||
this.renderingPaused = true
|
this.clearAndSetCursorPosition()
|
||||||
|
this._renderingPaused = true
|
||||||
}
|
}
|
||||||
|
|
||||||
resume() {
|
resume() {
|
||||||
this.renderingPaused = false
|
this._renderingPaused = false
|
||||||
this.render()
|
this.render()
|
||||||
}
|
}
|
||||||
|
|
||||||
stringify(value: any) {
|
stringify(value: any): string {
|
||||||
if (this.truncateOutput) {
|
if (this._truncateOutput) {
|
||||||
const json = JSON.stringify(value)
|
const json = JSON.stringify(value)
|
||||||
if (json.length < 40) {
|
if (json.length < 40) {
|
||||||
return json
|
return json
|
||||||
|
@ -170,14 +182,13 @@ export class TerminalTaskTracker {
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleOutputTruncation() {
|
toggleOutputTruncation() {
|
||||||
this.truncateOutput = !this.truncateOutput
|
this._truncateOutput = !this._truncateOutput
|
||||||
}
|
}
|
||||||
|
|
||||||
startInactivityTimeout() {
|
startInactivityTimeout() {
|
||||||
this.inactivityTimeout = setTimeout(() => {
|
this._inactivityTimeout = setTimeout(() => {
|
||||||
// Check if all tasks are completed:
|
const allTasksCompleted = Object.values(this._events).every((events) =>
|
||||||
const allTasksCompleted = Object.values(this.events).every((events) =>
|
events.every((event) => event.status === TaskStatus.COMPLETED)
|
||||||
events.every((event) => event.status !== TaskStatus.RUNNING)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (allTasksCompleted) {
|
if (allTasksCompleted) {
|
||||||
|
@ -190,21 +201,21 @@ export class TerminalTaskTracker {
|
||||||
|
|
||||||
addEvent<TInput, TOutput>(event: TaskEvent<TInput, TOutput>) {
|
addEvent<TInput, TOutput>(event: TaskEvent<TInput, TOutput>) {
|
||||||
const { parent = 'root', taskId, name, status, inputs, output } = event
|
const { parent = 'root', taskId, name, status, inputs, output } = event
|
||||||
if (!this.events[parent]) {
|
if (!this._events[parent]) {
|
||||||
this.events[parent] = []
|
this._events[parent] = []
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingEventIndex = this.events[parent].findIndex(
|
const existingEventIndex = this._events[parent].findIndex(
|
||||||
(e) => e.taskId === taskId
|
(e) => e.taskId === taskId
|
||||||
)
|
)
|
||||||
|
|
||||||
if (existingEventIndex !== -1) {
|
if (existingEventIndex !== -1) {
|
||||||
// If the event already exists, update its status and output:
|
// If the event already exists, update its status and output:
|
||||||
this.events[parent][existingEventIndex].status = status
|
this._events[parent][existingEventIndex].status = status
|
||||||
this.events[parent][existingEventIndex].output = output
|
this._events[parent][existingEventIndex].output = output
|
||||||
} else {
|
} else {
|
||||||
// If the event does not exist, add it to the array:
|
// If the event does not exist, add it to the array:
|
||||||
this.events[parent].push({ taskId, name, status, inputs })
|
this._events[parent].push({ taskId, name, status, inputs })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,12 +235,12 @@ export class TerminalTaskTracker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTree(node: string, level = 0) {
|
renderTree(node: string, level = 0): string[] {
|
||||||
const indent = SPACE.repeat(level * 2)
|
const indent = SPACE.repeat(level * 2)
|
||||||
let lines: string[] = []
|
let lines: string[] = []
|
||||||
|
|
||||||
if (this.events[node]) {
|
if (this._events[node]) {
|
||||||
this.events[node].forEach(({ name, status, output, inputs }) => {
|
this._events[node].forEach(({ name, status, output, inputs }) => {
|
||||||
const [statusSymbol, color] = this.getStatusSymbolColor(status)
|
const [statusSymbol, color] = this.getStatusSymbolColor(status)
|
||||||
|
|
||||||
lines.push(
|
lines.push(
|
||||||
|
@ -240,7 +251,7 @@ export class TerminalTaskTracker {
|
||||||
gray('(' + this.stringify(inputs) + ')')
|
gray('(' + this.stringify(inputs) + ')')
|
||||||
)
|
)
|
||||||
|
|
||||||
if (this.events[name]) {
|
if (this._events[name]) {
|
||||||
lines = lines.concat(
|
lines = lines.concat(
|
||||||
this.renderTree(name, level + 1).map((line, index, arr) => {
|
this.renderTree(name, level + 1).map((line, index, arr) => {
|
||||||
if (index === arr.length - 1) {
|
if (index === arr.length - 1) {
|
||||||
|
@ -253,7 +264,7 @@ export class TerminalTaskTracker {
|
||||||
}
|
}
|
||||||
|
|
||||||
let line = ''
|
let line = ''
|
||||||
if (this.events[name]) {
|
if (this._events[name]) {
|
||||||
line = indent + gray(SYMBOLS.BAR_END)
|
line = indent + gray(SYMBOLS.BAR_END)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,57 +319,61 @@ export class TerminalTaskTracker {
|
||||||
process.stderr.write(MAGIC_STRING + output)
|
process.stderr.write(MAGIC_STRING + output)
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleView(direction) {
|
toggleView(direction: string) {
|
||||||
const viewModes = ['tasks', 'stdout', 'stderr']
|
const viewModes = ['tasks', 'stdout', 'stderr']
|
||||||
const currentIdx = viewModes.indexOf(this.viewMode)
|
const currentIdx = viewModes.indexOf(this._viewMode)
|
||||||
|
|
||||||
if (direction === 'next') {
|
if (direction === 'next') {
|
||||||
this.viewMode = viewModes[(currentIdx + 1) % viewModes.length]
|
this._viewMode = viewModes[(currentIdx + 1) % viewModes.length]
|
||||||
} else if (direction === 'prev') {
|
} else if (direction === 'prev') {
|
||||||
this.viewMode =
|
this._viewMode =
|
||||||
viewModes[(currentIdx - 1 + viewModes.length) % viewModes.length]
|
viewModes[(currentIdx - 1 + viewModes.length) % viewModes.length]
|
||||||
}
|
}
|
||||||
|
|
||||||
this.render()
|
this.render()
|
||||||
}
|
}
|
||||||
|
|
||||||
getSpinnerSymbol() {
|
getSpinnerSymbol(): string {
|
||||||
return SYMBOLS.SPINNER[
|
return SYMBOLS.SPINNER[
|
||||||
Math.floor(Date.now() / this._spinnerInterval) % SYMBOLS.SPINNER.length
|
Math.floor(Date.now() / this._spinnerInterval) % SYMBOLS.SPINNER.length
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
renderHeader() {
|
renderHeader() {
|
||||||
const legend = [
|
const commands = [
|
||||||
'commands',
|
'ctrl+c: exit',
|
||||||
'ctrl+c exit',
|
|
||||||
'ctrl+e: truncate output',
|
'ctrl+e: truncate output',
|
||||||
'ctrl+left/right: switch view'
|
'ctrl+left/right: switch view'
|
||||||
].join(' | ')
|
].join(' | ')
|
||||||
|
|
||||||
const header = [` Agentic: ${this.viewMode} `, ` ${legend} `, ''].join('\n')
|
const header = [
|
||||||
|
` Agentic - ${capitalize(this._viewMode)} View`,
|
||||||
|
' ' + commands + ' ',
|
||||||
|
'',
|
||||||
|
''
|
||||||
|
].join('\n')
|
||||||
this.writeWithMagicString(bgWhite(black(header)))
|
this.writeWithMagicString(bgWhite(black(header)))
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.renderingPaused) {
|
if (this._renderingPaused) {
|
||||||
return // Do not render if paused
|
return // Do not render if paused
|
||||||
}
|
}
|
||||||
|
|
||||||
this.clearAndSetCursorPosition()
|
this.clearAndSetCursorPosition()
|
||||||
if (this.viewMode === 'tasks') {
|
if (this._viewMode === 'tasks') {
|
||||||
const lines = this.renderTree('root')
|
const lines = this.renderTree('root')
|
||||||
this.clearPreviousRender(lines.length + 2)
|
this.clearPreviousRender(lines.length)
|
||||||
this.renderHeader()
|
this.renderHeader()
|
||||||
this.writeWithMagicString(lines)
|
this.writeWithMagicString(lines)
|
||||||
} else if (this.viewMode === 'stdout') {
|
} else if (this._viewMode === 'stdout') {
|
||||||
this.clearPreviousRender(this.stdoutBuffer.length + 2)
|
this.clearPreviousRender(this._stdoutBuffer.length)
|
||||||
this.renderHeader()
|
this.renderHeader()
|
||||||
this.writeWithMagicString(this.stdoutBuffer)
|
this.writeWithMagicString(this._stdoutBuffer)
|
||||||
} else if (this.viewMode === 'stderr') {
|
} else if (this._viewMode === 'stderr') {
|
||||||
this.clearPreviousRender(this.stderrBuffer.length + 2)
|
this.clearPreviousRender(this._stderrBuffer.length)
|
||||||
this.renderHeader()
|
this.renderHeader()
|
||||||
this.writeWithMagicString(this.stderrBuffer)
|
this.writeWithMagicString(this._stderrBuffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -254,3 +254,7 @@ export function isArray(value: any): value is any[] {
|
||||||
export function identity<T>(x: T): T {
|
export function identity<T>(x: T): T {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function capitalize(str: string): string {
|
||||||
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||||
|
}
|
||||||
|
|
Ładowanie…
Reference in New Issue