mirror of
https://github.com/hempyhemp/hh-auto-reply.git
synced 2026-06-08 18:04:57 +00:00
✨ feat(resume): добавлены логи для обработчика резюме
All checks were successful
Deploy / deploy (push) Successful in 48s
All checks were successful
Deploy / deploy (push) Successful in 48s
⭐️ Использован createLogger для управления логами в resume.ts. Логи рапперащают о загрузке списка резюме и ошибках.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import process from 'node:process'
|
import process from 'node:process'
|
||||||
|
|
||||||
import TelegramBot from 'node-telegram-bot-api'
|
import TelegramBot from 'node-telegram-bot-api'
|
||||||
|
const log = createLogger('telegram')
|
||||||
|
|
||||||
const token = process.env.TG_BOT_TOKEN!
|
const token = process.env.TG_BOT_TOKEN!
|
||||||
const bot = new TelegramBot(token, { polling: true })
|
const bot = new TelegramBot(token, { polling: true })
|
||||||
@@ -8,7 +9,7 @@ const bot = new TelegramBot(token, { polling: true })
|
|||||||
bot.on('polling_error', (err: any) => {
|
bot.on('polling_error', (err: any) => {
|
||||||
// EFATAL (socket hang up) — Telegram обрывает long-poll соединение, это нормально
|
// EFATAL (socket hang up) — Telegram обрывает long-poll соединение, это нормально
|
||||||
if (err?.code === 'EFATAL') return
|
if (err?.code === 'EFATAL') return
|
||||||
console.error('[polling_error]', err?.code, err?.message)
|
log.error('polling_error', err?.code, err?.message)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default bot
|
export default bot
|
||||||
|
|||||||
7
src/global.d.ts
vendored
Normal file
7
src/global.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { Logger } from './logger.js'
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
function createLogger(tag: string): Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
export {}
|
||||||
3
src/globals.ts
Normal file
3
src/globals.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { createLogger } from './logger.js'
|
||||||
|
|
||||||
|
;(globalThis as any).createLogger = createLogger
|
||||||
@@ -4,6 +4,8 @@ import { listResumes, NoResumeError, saveResume } from '../scraper.js'
|
|||||||
import { getState } from '../state.js'
|
import { getState } from '../state.js'
|
||||||
import { escapeHtml, NO_RESUME_MARKUP, safeEdit } from '../ui.js'
|
import { escapeHtml, NO_RESUME_MARKUP, safeEdit } from '../ui.js'
|
||||||
|
|
||||||
|
const log = createLogger('resume')
|
||||||
|
|
||||||
export async function handleResumeList(chatId: number): Promise<void> {
|
export async function handleResumeList(chatId: number): Promise<void> {
|
||||||
const state = getState(chatId)
|
const state = getState(chatId)
|
||||||
const loadingMsg = await bot.sendMessage(chatId, '🔄 Загружаю список резюме...')
|
const loadingMsg = await bot.sendMessage(chatId, '🔄 Загружаю список резюме...')
|
||||||
@@ -11,7 +13,7 @@ export async function handleResumeList(chatId: number): Promise<void> {
|
|||||||
let resumes
|
let resumes
|
||||||
try {
|
try {
|
||||||
resumes = await listResumes(chatId)
|
resumes = await listResumes(chatId)
|
||||||
console.log(`[handleResumeList ${chatId}]: ${resumes}`)
|
log.ok(`handleResumeList chatId=${chatId}:`, resumes)
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
await bot.deleteMessage(chatId, loadingMsg.message_id).catch(() => {})
|
await bot.deleteMessage(chatId, loadingMsg.message_id).catch(() => {})
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { createMessage } from '@/openai'
|
|||||||
import { loadSession, newStealthContext, randomDelay, randomScroll, withBrowser } from './browser.js'
|
import { loadSession, newStealthContext, randomDelay, randomScroll, withBrowser } from './browser.js'
|
||||||
import { createStatusReporter, escapeHtml } from './ui.js'
|
import { createStatusReporter, escapeHtml } from './ui.js'
|
||||||
|
|
||||||
|
const log = createLogger('scraper')
|
||||||
|
|
||||||
export class NoResumeError extends Error {
|
export class NoResumeError extends Error {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('no_resume')
|
super('no_resume')
|
||||||
@@ -51,7 +53,7 @@ async function skipIfQuestionnaire(
|
|||||||
return false
|
return false
|
||||||
const { keep } = createStatusReporter(chatId)
|
const { keep } = createStatusReporter(chatId)
|
||||||
await keep(`Пропущена вакансия: ${vacancy.title}`)
|
await keep(`Пропущена вакансия: ${vacancy.title}`)
|
||||||
console.log(`[x] ${vacancy.title} hasQuestionnaire`)
|
log.warn(`[x] ${vacancy.title} hasQuestionnaire`)
|
||||||
await prisma.skippedVacancy.upsert({
|
await prisma.skippedVacancy.upsert({
|
||||||
where: { telegramId_href: { telegramId: chatId, href: vacancy.href } },
|
where: { telegramId_href: { telegramId: chatId, href: vacancy.href } },
|
||||||
create: { telegramId: chatId, href: vacancy.href, title: vacancy.title },
|
create: { telegramId: chatId, href: vacancy.href, title: vacancy.title },
|
||||||
@@ -64,7 +66,7 @@ async function skipIfQuestionnaire(
|
|||||||
export async function login(email: string, chatId: number): Promise<void> {
|
export async function login(email: string, chatId: number): Promise<void> {
|
||||||
await withBrowser(async (browser) => {
|
await withBrowser(async (browser) => {
|
||||||
if (!browser.version()) {
|
if (!browser.version()) {
|
||||||
console.log('browser error')
|
log.error('browser error')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +159,7 @@ export async function listResumes(chatId: number): Promise<ResumeListItem[]> {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log(items.length)
|
log.info('resumes found:', items.length)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const href = await cardLinks[0].getAttribute('href') ?? ''
|
const href = await cardLinks[0].getAttribute('href') ?? ''
|
||||||
@@ -188,11 +190,11 @@ export async function listResumes(chatId: number): Promise<ResumeListItem[]> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.log(`Failed to fetch resume text for ${item.title}:`, e)
|
log.error(`Failed to fetch resume text for ${item.title}:`, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(items)
|
log.ok('listResumes:', items)
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
@@ -246,7 +248,7 @@ export async function applyToJobs(
|
|||||||
|
|
||||||
await loadSession(page, chatId)
|
await loadSession(page, chatId)
|
||||||
|
|
||||||
const url = `https://hh.ru/search/vacancy?text=${encodeURIComponent(query)}&items_on_page=50&page=0` // &area=${area}
|
const url = `https://hh.ru/search/vacancy?text=${encodeURIComponent(query)}&items_on_page=100&page=0` // &area=${area}
|
||||||
await page.goto(url, { waitUntil: 'domcontentloaded' })
|
await page.goto(url, { waitUntil: 'domcontentloaded' })
|
||||||
await page.waitForSelector('[data-qa="serp-item__title"]', { timeout: 10000 }).catch(() => null)
|
await page.waitForSelector('[data-qa="serp-item__title"]', { timeout: 10000 }).catch(() => null)
|
||||||
await page.pause()
|
await page.pause()
|
||||||
@@ -267,9 +269,9 @@ export async function applyToJobs(
|
|||||||
return Number(pageParam ?? 0)
|
return Number(pageParam ?? 0)
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
console.log('[applyToJobs] Max page:', maxPage)
|
log.info('Max page:', maxPage)
|
||||||
for (let p = 1; p <= maxPage; p++) {
|
for (let p = 1; p <= maxPage; p++) {
|
||||||
const pageUrl = `https://hh.ru/search/vacancy?text=${encodeURIComponent(query)}&items_on_page=50&page=${p}`
|
const pageUrl = `https://hh.ru/search/vacancy?text=${encodeURIComponent(query)}&items_on_page=100&page=${p}`
|
||||||
await page.goto(pageUrl, { waitUntil: 'domcontentloaded' })
|
await page.goto(pageUrl, { waitUntil: 'domcontentloaded' })
|
||||||
await page.waitForSelector('[data-qa="vacancy-serp__vacancy"]', { timeout: 10000 }).catch(() => null)
|
await page.waitForSelector('[data-qa="vacancy-serp__vacancy"]', { timeout: 10000 }).catch(() => null)
|
||||||
const more = await collectPageVacancies(page)
|
const more = await collectPageVacancies(page)
|
||||||
@@ -344,7 +346,7 @@ export async function applyToJobs(
|
|||||||
setTimeout(() => reject(new Error('Letter generation timeout (60s)')), 80000),
|
setTimeout(() => reject(new Error('Letter generation timeout (60s)')), 80000),
|
||||||
),
|
),
|
||||||
]).catch((err: Error) => {
|
]).catch((err: Error) => {
|
||||||
console.error('[Letter Error]:', err.message)
|
log.error('Letter Error:', err.message)
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -352,11 +354,11 @@ export async function applyToJobs(
|
|||||||
// Выбор резюме
|
// Выбор резюме
|
||||||
const currentResumeEl = await page.$('[data-qa="resume-title"]')
|
const currentResumeEl = await page.$('[data-qa="resume-title"]')
|
||||||
const currentResumeTitle = (await currentResumeEl?.innerText())?.trim() ?? ''
|
const currentResumeTitle = (await currentResumeEl?.innerText())?.trim() ?? ''
|
||||||
console.log('Текущее резюме на странице:', currentResumeTitle)
|
log.debug('Текущее резюме на странице:', currentResumeTitle)
|
||||||
console.log('Ожидаемое резюме из БД:', resume.title)
|
log.debug('Ожидаемое резюме из БД:', resume.title)
|
||||||
|
|
||||||
if (currentResumeTitle !== resume.title) {
|
if (currentResumeTitle !== resume.title) {
|
||||||
console.log('Резюме не совпадает, нужно сменить')
|
log.warn('Резюме не совпадает, нужно сменить')
|
||||||
await currentResumeEl?.click()
|
await currentResumeEl?.click()
|
||||||
await page.waitForSelector('[data-qa="magritte-select-option-list"]', { timeout: 5000 })
|
await page.waitForSelector('[data-qa="magritte-select-option-list"]', { timeout: 5000 })
|
||||||
// await page.pause()
|
// await page.pause()
|
||||||
@@ -401,14 +403,14 @@ export async function applyToJobs(
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const errMsg = 'Not found submit button'
|
const errMsg = 'Not found submit button'
|
||||||
console.log(errMsg)
|
log.warn(errMsg)
|
||||||
results.errors.push({ ...ref, message: errMsg })
|
results.errors.push({ ...ref, message: errMsg })
|
||||||
// results.skipped.push(vacancy)
|
// results.skipped.push(vacancy)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.log(`[Debug]: single flow: ${chatId}`)
|
log.debug(`single flow: ${chatId}`)
|
||||||
|
|
||||||
const letter = await letterPromise
|
const letter = await letterPromise
|
||||||
|
|
||||||
@@ -441,7 +443,7 @@ export async function applyToJobs(
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const errMsg = 'Not found submit button'
|
const errMsg = 'Not found submit button'
|
||||||
console.log(errMsg)
|
log.warn(errMsg)
|
||||||
results.errors.push({ ...ref, message: errMsg })
|
results.errors.push({ ...ref, message: errMsg })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
// import * as process from 'node:process'
|
// import * as process from 'node:process'
|
||||||
|
import './globals.js'
|
||||||
import bot from '@bot'
|
import bot from '@bot'
|
||||||
import prisma from '@prisma'
|
import prisma from '@prisma'
|
||||||
import { registerHHCommands, triggerHHStart } from './hh/bot-commands.js'
|
import { registerHHCommands, triggerHHStart } from './hh/bot-commands.js'
|
||||||
|
|
||||||
|
const log = createLogger('index')
|
||||||
|
|
||||||
process.on('unhandledRejection', (reason) => {
|
process.on('unhandledRejection', (reason) => {
|
||||||
console.error('[unhandledRejection]', reason)
|
log.error('[unhandledRejection]', reason)
|
||||||
})
|
})
|
||||||
// console.log('hi') //PWDEBUG=1
|
// console.log('hi') //PWDEBUG=1
|
||||||
registerHHCommands()
|
registerHHCommands()
|
||||||
@@ -36,4 +39,4 @@ bot.onText(/\/start/, async (msg) => {
|
|||||||
await triggerHHStart(chatId)
|
await triggerHHStart(chatId)
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('Bot started 🚀')
|
log.ok('Bot started 🚀')
|
||||||
|
|||||||
83
src/logger.ts
Normal file
83
src/logger.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
const RESET = '\x1b[0m'
|
||||||
|
const BOLD = '\x1b[1m'
|
||||||
|
const DIM = '\x1b[2m'
|
||||||
|
|
||||||
|
const colors = {
|
||||||
|
gray: '\x1b[90m',
|
||||||
|
green: '\x1b[32m',
|
||||||
|
yellow: '\x1b[33m',
|
||||||
|
red: '\x1b[31m',
|
||||||
|
cyan: '\x1b[36m',
|
||||||
|
magenta: '\x1b[35m',
|
||||||
|
blue: '\x1b[34m',
|
||||||
|
white: '\x1b[37m',
|
||||||
|
}
|
||||||
|
|
||||||
|
const LEVELS = {
|
||||||
|
info: { icon: '●', color: colors.cyan, label: 'INFO ' },
|
||||||
|
success: { icon: '✔', color: colors.green, label: 'OK ' },
|
||||||
|
warn: { icon: '▲', color: colors.yellow, label: 'WARN ' },
|
||||||
|
error: { icon: '✖', color: colors.red, label: 'ERROR' },
|
||||||
|
debug: { icon: '◆', color: colors.magenta, label: 'DEBUG' },
|
||||||
|
llm: { icon: '◈', color: colors.blue, label: 'LLM ' },
|
||||||
|
} as const
|
||||||
|
|
||||||
|
type Level = keyof typeof LEVELS
|
||||||
|
|
||||||
|
function timestamp(): string {
|
||||||
|
const now = new Date()
|
||||||
|
const hh = String(now.getHours()).padStart(2, '0')
|
||||||
|
const mm = String(now.getMinutes()).padStart(2, '0')
|
||||||
|
const ss = String(now.getSeconds()).padStart(2, '0')
|
||||||
|
const ms = String(now.getMilliseconds()).padStart(3, '0')
|
||||||
|
return `${DIM}${colors.gray}${hh}:${mm}:${ss}.${ms}${RESET}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTag(tag: string): string {
|
||||||
|
return `${DIM}${colors.gray}[${tag}]${RESET}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatArgs(args: unknown[]): string {
|
||||||
|
return args
|
||||||
|
.map((a) =>
|
||||||
|
typeof a === 'object' && a !== null
|
||||||
|
? JSON.stringify(a, null, 2)
|
||||||
|
: String(a),
|
||||||
|
)
|
||||||
|
.join(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
function print(level: Level, tag: string, args: unknown[]): void {
|
||||||
|
const { icon, color, label } = LEVELS[level]
|
||||||
|
const parts = [
|
||||||
|
timestamp(),
|
||||||
|
`${color}${BOLD}${icon} ${label}${RESET}`,
|
||||||
|
formatTag(tag),
|
||||||
|
`${color}${formatArgs(args)}${RESET}`,
|
||||||
|
]
|
||||||
|
if (level === 'error') {
|
||||||
|
process.stderr.write(parts.join(' ') + '\n')
|
||||||
|
} else {
|
||||||
|
process.stdout.write(parts.join(' ') + '\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createLogger(tag: string) {
|
||||||
|
return {
|
||||||
|
info: (...args: unknown[]) => print('info', tag, args),
|
||||||
|
ok: (...args: unknown[]) => print('success', tag, args),
|
||||||
|
warn: (...args: unknown[]) => print('warn', tag, args),
|
||||||
|
error: (...args: unknown[]) => print('error', tag, args),
|
||||||
|
debug: (...args: unknown[]) => print('debug', tag, args),
|
||||||
|
llm: (...args: unknown[]) => print('llm', tag, args),
|
||||||
|
divider: (label?: string) => {
|
||||||
|
const line = '─'.repeat(58)
|
||||||
|
const text = label
|
||||||
|
? `${DIM}${colors.gray}┌─ ${label} ${'─'.repeat(Math.max(0, 54 - label.length))}${RESET}`
|
||||||
|
: `${DIM}${colors.gray}${line}${RESET}`
|
||||||
|
process.stdout.write(text + '\n')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Logger = ReturnType<typeof createLogger>
|
||||||
@@ -2,6 +2,7 @@ import process from 'node:process'
|
|||||||
import { createOpencode, createOpencodeClient } from '@opencode-ai/sdk'
|
import { createOpencode, createOpencodeClient } from '@opencode-ai/sdk'
|
||||||
// import Anthropic from '@anthropic-ai/sdk'
|
// import Anthropic from '@anthropic-ai/sdk'
|
||||||
import OpenAI from 'openai'
|
import OpenAI from 'openai'
|
||||||
|
const log = createLogger('llm')
|
||||||
|
|
||||||
// export const claude = new Anthropic({
|
// export const claude = new Anthropic({
|
||||||
// apiKey: process.env.ANTHROPIC_API_KEY,
|
// apiKey: process.env.ANTHROPIC_API_KEY,
|
||||||
@@ -55,12 +56,12 @@ export async function test() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const test = await client.config.providers()
|
const test = await client.config.providers()
|
||||||
console.log(test.data)
|
log.debug('providers', test.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function askLLM(userMessage: string) {
|
export async function askLLM(userMessage: string) {
|
||||||
const client = await getClient()
|
const client = await getClient()
|
||||||
console.log('askLLM')
|
log.info('askLLM called')
|
||||||
// Создаём сессию
|
// Создаём сессию
|
||||||
const session = await client.session.create({
|
const session = await client.session.create({
|
||||||
body: { title: 'My request' },
|
body: { title: 'My request' },
|
||||||
@@ -85,20 +86,18 @@ export async function askLLM(userMessage: string) {
|
|||||||
export async function createMessage(resume: string, message: string, prompt?: string) {
|
export async function createMessage(resume: string, message: string, prompt?: string) {
|
||||||
const client = await getClient()
|
const client = await getClient()
|
||||||
|
|
||||||
console.log('[createMessage] client.instance: ', !!client.instance)
|
log.debug('client.instance:', !!client.instance)
|
||||||
const session = await client.session.create({ body: { title: 'Cover letter' } })
|
const session = await client.session.create({ body: { title: 'Cover letter' } })
|
||||||
const sessionId = session.data!.id
|
const sessionId = session.data!.id
|
||||||
|
|
||||||
console.log('[createMessage] sessionId: ', sessionId)
|
log.debug('sessionId:', sessionId)
|
||||||
|
|
||||||
const finalPromt = prompt || 'Ты — помощник по написанию сопроводительных писем. Отвечай только текстом самого письма, без вступлений, ремарок и пояснений. Опирайся на резюме и ничего не выдумывай, чего недостаточно в резюме лучше умолчать. Пиши по короче и простыми словами. В конце письма оставляй все контакты для связи.'
|
const finalPromt = prompt || 'Ты — помощник по написанию сопроводительных писем. Отвечай только текстом самого письма, без вступлений, ремарок и пояснений. Опирайся на резюме и ничего не выдумывай, чего недостаточно в резюме лучше умолчать. Пиши по короче и простыми словами. В конце письма оставляй все контакты для связи.'
|
||||||
|
|
||||||
const resumePreview = resume.slice(0, 200).replace(/\n/g, ' ')
|
const resumePreview = resume.slice(0, 200).replace(/\n/g, ' ')
|
||||||
console.log(`\n${'─'.repeat(60)}`)
|
log.divider('Prompt 1 — system + resume (noReply)')
|
||||||
console.log(`[LLM] 📋 Prompt 1 (system + resume, noReply)`)
|
log.llm(`system: ${finalPromt.slice(0, 80)}…`)
|
||||||
console.log(`[LLM] system: ${finalPromt.slice(0, 80)}…`)
|
log.llm(`resume: ${resumePreview}…`)
|
||||||
console.log(`[LLM] resume: ${resumePreview}…`)
|
|
||||||
console.log(`${'─'.repeat(60)}`)
|
|
||||||
|
|
||||||
await client.session.prompt({
|
await client.session.prompt({
|
||||||
path: { id: sessionId },
|
path: { id: sessionId },
|
||||||
@@ -109,10 +108,9 @@ export async function createMessage(resume: string, message: string, prompt?: st
|
|||||||
})
|
})
|
||||||
|
|
||||||
const vacancyPreview = message.slice(0, 300).replace(/\n/g, ' ')
|
const vacancyPreview = message.slice(0, 300).replace(/\n/g, ' ')
|
||||||
console.log(`\n${'─'.repeat(60)}`)
|
log.divider('Prompt 2 — vacancy')
|
||||||
console.log(`[LLM] 📝 Prompt 2 (vacancy) → ожидаю ответ…`)
|
log.llm(`📝 vacancy → ожидаю ответ…`)
|
||||||
console.log(`[LLM] vacancy: ${vacancyPreview}…`)
|
log.llm(`vacancy: ${vacancyPreview}…`)
|
||||||
console.log(`${'─'.repeat(60)}`)
|
|
||||||
|
|
||||||
// ${prompt}\n\n
|
// ${prompt}\n\n
|
||||||
const result = await client.session.prompt({
|
const result = await client.session.prompt({
|
||||||
@@ -124,16 +122,15 @@ export async function createMessage(resume: string, message: string, prompt?: st
|
|||||||
|
|
||||||
const parts = (result.data?.parts ?? []) as { type: string, text?: string }[]
|
const parts = (result.data?.parts ?? []) as { type: string, text?: string }[]
|
||||||
const textPart = parts.find(p => p.type === 'text')
|
const textPart = parts.find(p => p.type === 'text')
|
||||||
console.log(`\n${'─'.repeat(60)}`)
|
log.divider('Ответ получен')
|
||||||
console.log(`[LLM] ✅ Ответ получен (${textPart?.text?.length ?? 0} символов)`)
|
log.llm(`✅ ${textPart?.text?.length ?? 0} символов`)
|
||||||
console.log(`[LLM] ${textPart?.text?.slice(0, 150).replace(/\n/g, ' ') ?? 'null'}…`)
|
log.llm(`${textPart?.text?.slice(0, 150).replace(/\n/g, ' ') ?? 'null'}…`)
|
||||||
console.log(`${'─'.repeat(60)}\n`)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await client.session.delete({ path: { id: sessionId } })
|
await client.session.delete({ path: { id: sessionId } })
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.error('[Session cleanup error]:', (e as Error).message)
|
log.error('Session cleanup error:', (e as Error).message)
|
||||||
}
|
}
|
||||||
|
|
||||||
return textPart?.text ?? null
|
return textPart?.text ?? null
|
||||||
|
|||||||
Reference in New Issue
Block a user