168 行
5.7 KiB
TypeScript
168 行
5.7 KiB
TypeScript
import {Chat, ChatOptions, ChatRequest, ChatResponse, ModelType} from "../base";
|
|
import {Browser, Page} from "puppeteer";
|
|
import {BrowserPool, BrowserUser} from "../../pool/puppeteer";
|
|
import {DoneData, ErrorData, Event, EventStream, MessageData, sleep} from "../../utils";
|
|
import {v4} from "uuid";
|
|
|
|
type PageData = {
|
|
gpt4times: number;
|
|
}
|
|
|
|
const MaxGptTimes = 4;
|
|
|
|
const TimeFormat = "YYYY-MM-DD HH:mm:ss";
|
|
|
|
type Account = {
|
|
id: string;
|
|
}
|
|
const url = "https://www.phind.com";
|
|
|
|
export class Phind extends Chat implements BrowserUser<Account> {
|
|
private pagePool: BrowserPool<Account>;
|
|
|
|
constructor(options?: ChatOptions) {
|
|
super(options);
|
|
const maxSize = +(process.env.PHIND_POOL_SIZE || 2);
|
|
this.pagePool = new BrowserPool<Account>(maxSize, this);
|
|
}
|
|
|
|
support(model: ModelType): number {
|
|
switch (model) {
|
|
case ModelType.NetGpt3p5:
|
|
return 4000;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public async ask(req: ChatRequest): Promise<ChatResponse> {
|
|
const et = new EventStream();
|
|
const res = await this.askStream(req, et);
|
|
const result: ChatResponse = {
|
|
content: '',
|
|
};
|
|
return new Promise(resolve => {
|
|
et.read((event, data) => {
|
|
if (!data) {
|
|
return;
|
|
}
|
|
switch (event) {
|
|
case 'message':
|
|
result.content = (data as MessageData).content;
|
|
break;
|
|
case 'done':
|
|
result.content = (data as DoneData).content;
|
|
break;
|
|
case 'error':
|
|
result.error = (data as ErrorData).error;
|
|
break;
|
|
default:
|
|
console.error(data);
|
|
break;
|
|
}
|
|
}, () => {
|
|
resolve(result);
|
|
});
|
|
})
|
|
}
|
|
|
|
static async allowClipboard(browser: Browser, page: Page) {
|
|
const context = browser.defaultBrowserContext()
|
|
await context.overridePermissions(url, [
|
|
'clipboard-read',
|
|
'clipboard-write',
|
|
])
|
|
await page.evaluate(() => Object.defineProperty(navigator, 'clipboard', {
|
|
value: {
|
|
//@ts-ignore
|
|
writeText(text) {
|
|
this.text = text;
|
|
},
|
|
}
|
|
}));
|
|
}
|
|
|
|
deleteID(id: string): void {
|
|
}
|
|
|
|
newID(): string {
|
|
return v4();
|
|
}
|
|
|
|
async init(id: string, browser: Browser): Promise<[Page | undefined, Account]> {
|
|
const [page] = await browser.pages();
|
|
await Phind.allowClipboard(browser, page);
|
|
|
|
await page.goto(url);
|
|
await sleep(2000);
|
|
await page.waitForSelector('div > .mt-4 > div > .form-check > .form-check-label')
|
|
await page.click('div > .mt-4 > div > .form-check > .form-check-label')
|
|
await Phind.allowClipboard(browser, page);
|
|
return [page, {id}]
|
|
}
|
|
|
|
public static async copyContent(page: Page) {
|
|
await page.waitForSelector('.row > .col-lg-8 > .container-xl > .mb-4 > .btn:nth-child(3)', {timeout: 5 * 60 * 1000})
|
|
await page.click('.row > .col-lg-8 > .container-xl > .mb-4 > .btn:nth-child(3)')
|
|
}
|
|
|
|
public async askStream(req: ChatRequest, stream: EventStream) {
|
|
req.prompt = req.prompt.replace(/\n/g, '|');
|
|
const [page, account, done, destroy] = this.pagePool.get();
|
|
if (!account || !page) {
|
|
stream.write(Event.error, {error: 'please wait init.....about 1 min'})
|
|
stream.end();
|
|
return;
|
|
}
|
|
try {
|
|
await page.waitForSelector('.col-md-10 > .container-xl > .mb-3 > .input-group > .form-control')
|
|
await page.click('.col-md-10 > .container-xl > .mb-3 > .input-group > .form-control')
|
|
await page.focus('.col-md-10 > .container-xl > .mb-3 > .input-group > .form-control')
|
|
|
|
await page.keyboard.type(req.prompt);
|
|
await page.keyboard.press('Enter');
|
|
await page.waitForSelector('.col-lg-10 > .row > .col-lg-8:nth-child(4) > .container-xl > div:nth-child(1)');
|
|
const output = await page.$('.col-lg-10 > .row > .col-lg-8:nth-child(4) > .container-xl > div:nth-child(1)');
|
|
const itl = setInterval(async () => {
|
|
try {
|
|
const content: any = await output?.evaluate(el => {
|
|
return el.textContent;
|
|
});
|
|
stream.write(Event.message, {content});
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
}, 1000)
|
|
const wait = async () => {
|
|
await Phind.copyContent(page);
|
|
clearInterval(itl);
|
|
//@ts-ignore
|
|
const text: any = await page.evaluate(() => navigator.clipboard.text);
|
|
console.log('chat end: ', text);
|
|
stream.write(Event.done, {
|
|
content: text || await output?.evaluate((el: any) => {
|
|
return el.textContent;
|
|
})
|
|
})
|
|
stream.end();
|
|
done(account);
|
|
await page.goto(url);
|
|
}
|
|
wait().then().catch(async (e)=>{
|
|
console.error(e);
|
|
stream.write(Event.error, {error: e.message})
|
|
stream.end();
|
|
await page.goto(url);
|
|
});
|
|
} catch (e: any) {
|
|
console.error(e);
|
|
stream.write(Event.error, {error: e.message})
|
|
stream.end();
|
|
await page.goto(url);
|
|
return;
|
|
}
|
|
return
|
|
}
|
|
|
|
}
|