fix: package of llama-node

This commit is contained in:
linonetwo 2023-07-17 16:44:40 +08:00 committed by lin onetwo
parent d60ae9d132
commit 66ea5a2c34
14 changed files with 104 additions and 56 deletions

View file

@ -5,22 +5,22 @@
"version": "0.8.1-prerelease1",
"license": "MPL 2.0",
"scripts": {
"start": "pnpm run clean && pnpm run start:without-clean",
"start": "pnpm run clean && pnpm run init:git-submodule && pnpm run start:without-clean",
"start:without-clean": "pnpm run build:plugin && cross-env NODE_ENV=development electron-forge start",
"start:without-clean:debug-worker": "pnpm run build:plugin && cross-env NODE_ENV=development DEBUG_WORKER=true electron-forge start",
"start:without-clean:debug-main": "pnpm run build:plugin && cross-env NODE_ENV=development DEBUG_MAIN=true electron-forge start",
"build:plugin": "zx scripts/compilePlugins.mjs",
"test": "pnpm run clean && cross-env NODE_ENV=test pnpm run package && pnpm run test:without-package",
"test:without-package": "mkdir -p logs && cross-env NODE_ENV=test cucumber-js",
"package": "pnpm run init:git-submodule && pnpm run build:plugin && electron-forge package",
"make:mac-x64": "pnpm run init:git-submodule && pnpm run build:plugin && cross-env NODE_ENV=production electron-forge make --platform=darwin --arch=x64",
"make:mac-arm": "pnpm run init:git-submodule && pnpm run build:plugin && cross-env NODE_ENV=production electron-forge make --platform=darwin --arch=arm64",
"make:win-x64": "pnpm run init:git-submodule && pnpm run build:plugin && cross-env NODE_ENV=production electron-forge make --platform=win32 --arch=x64",
"make:win-ia32": "pnpm run init:git-submodule && pnpm run build:plugin && cross-env NODE_ENV=production electron-forge make --platform=win32 --arch=ia32",
"make:win-arm": "pnpm run init:git-submodule && pnpm run build:plugin && cross-env NODE_ENV=production electron-forge make --platform=win32 --arch=arm64",
"make:linux-x64": "pnpm run init:git-submodule && pnpm run build:plugin && cross-env NODE_ENV=production electron-forge make --platform=linux --arch=x64",
"make:linux-arm": "pnpm run init:git-submodule && pnpm run build:plugin && cross-env NODE_ENV=production electron-forge make --platform=linux --arch=arm64",
"clean": "rimraf ./out ./settings-dev ./node_modules/@tiddlygit/tiddlywiki/plugins/linonetwo ./cache-database-dev ./logs ./.webpack ./node_modules/.cache && cross-env NODE_ENV=development npx ts-node scripts/developmentMkdir.ts && pnpm run init:git-submodule",
"package": "pnpm run clean && pnpm run build:plugin && electron-forge package",
"make:mac-x64": "pnpm run clean && pnpm run build:plugin && cross-env NODE_ENV=production electron-forge make --platform=darwin --arch=x64",
"make:mac-arm": "pnpm run clean && pnpm run build:plugin && cross-env NODE_ENV=production electron-forge make --platform=darwin --arch=arm64",
"make:win-x64": "pnpm run clean && pnpm run build:plugin && cross-env NODE_ENV=production electron-forge make --platform=win32 --arch=x64",
"make:win-ia32": "pnpm run clean && pnpm run build:plugin && cross-env NODE_ENV=production electron-forge make --platform=win32 --arch=ia32",
"make:win-arm": "pnpm run clean && pnpm run build:plugin && cross-env NODE_ENV=production electron-forge make --platform=win32 --arch=arm64",
"make:linux-x64": "pnpm run clean && pnpm run build:plugin && cross-env NODE_ENV=production electron-forge make --platform=linux --arch=x64",
"make:linux-arm": "pnpm run clean && pnpm run build:plugin && cross-env NODE_ENV=production electron-forge make --platform=linux --arch=arm64",
"clean": "rimraf -- ./out ./settings-dev ./node_modules/@tiddlygit/tiddlywiki/plugins/linonetwo ./cache-database-dev ./logs ./.webpack ./node_modules/.cache && cross-env NODE_ENV=development npx ts-node scripts/developmentMkdir.ts && rimraf -- ./out",
"init:git-submodule": "git submodule update --recursive && git submodule update --remote",
"lint": "eslint ./src --ext js,ts,tsx,json",
"lint:fix": "eslint ./src --ext js,ts,tsx,json --fix",
@ -39,7 +39,7 @@
"better-sqlite3": "^8.4.0",
"bluebird": "3.7.2",
"default-gateway": "6.0.3",
"dugite": "^2.5.1",
"dugite": "2.5.1",
"electron-ipc-cat": "2.0.1",
"electron-settings": "5.0.0",
"electron-squirrel-startup": "1.0.0",

2
pnpm-lock.yaml generated
View file

@ -40,7 +40,7 @@ dependencies:
specifier: 6.0.3
version: 6.0.3
dugite:
specifier: ^2.5.1
specifier: 2.5.1
version: 2.5.1
electron-ipc-cat:
specifier: 2.0.1

View file

@ -7,6 +7,7 @@ const path = require('path');
const glob = require('glob');
const fs = require('fs-extra');
const util = require('util');
const packageJSON = require('../package.json')
const exec = util.promisify(require('child_process').exec);
/**
@ -38,14 +39,6 @@ exports.default = async (buildPath, electronVersion, platform, arch, callback) =
const tasks = [];
if (['production', 'test'].includes(process.env.NODE_ENV)) {
console.log('Copying tiddlywiki dependency to dist');
tasks.push(fs.copy(path.join(projectRoot, 'node_modules', '@tiddlygit', 'tiddlywiki'), path.join(cwd, 'node_modules', '@tiddlygit', 'tiddlywiki'), { dereference: true }));
// it has things like `git/bin/libexec/git-core/git-add` link to `git/bin/libexec/git-core/git`, to reduce size, so can't use `dereference: true` here.
tasks.push(fs.copy(path.join(projectRoot, 'node_modules', '.pnpm', 'dugite@2.5.1', 'node_modules', 'dugite'), path.join(cwd, 'node_modules', 'dugite'), { dereference: false }));
// we only need its `main` binary, no need its dependency and code, because we already copy it to src/services/native/externalApp
tasks.push(fs.mkdirp(path.join(cwd, 'node_modules', 'app-path')).then(async () => {
await fs.copy(path.join(projectRoot, 'node_modules', 'app-path', 'main'), path.join(cwd, 'node_modules', 'app-path', 'main'), { dereference: true })
}));
tasks.push(fs.copy(path.resolve(projectRoot, 'node_modules/better-sqlite3/build/Release/better_sqlite3.node'), path.resolve(cwd, 'node_modules/better-sqlite3/build/Release/better_sqlite3.node'), { dereference: true }));
tasks.push(fs.copy(path.join(projectRoot, 'node_modules', 'zx'), path.join(cwd, 'node_modules', 'zx'), { dereference: true }).then(async () => {
// not using pnpm, because after using it, it always causing problem here, causing `Error: spawn /bin/sh ENOENT` in github actions
// it can probably being "working directory didn't exist" in https://github.com/nodejs/node/issues/9644#issuecomment-282060923
@ -54,11 +47,30 @@ exports.default = async (buildPath, electronVersion, platform, arch, callback) =
await exec(`npm i --legacy-building`, { cwd: path.join(cwd, 'node_modules', 'zx', 'node_modules', 'globby'), shell });
await exec(`npm i --legacy-building --ignore-scripts`, { cwd: path.join(cwd, 'node_modules', 'zx', 'node_modules', 'node-fetch'), shell });
}));
const packagePathsToCopyDereferenced = [
['@tiddlygit', 'tiddlywiki'],
['llama-node'],
['@llama-node','llama-cpp'],
['@llama-node','core'],
['@llama-node','rwkv-cpp'],
['better-sqlite3','build','Release','better_sqlite3.node'],
]
for (const packagePathInNodeModules of packagePathsToCopyDereferenced) {
// some binary may not exist in other platforms, so allow failing here.
tasks.push(fs.copy(path.resolve(projectRoot, 'node_modules', ...packagePathInNodeModules), path.resolve(cwd, 'node_modules', ...packagePathInNodeModules), { dereference: true }));
}
const sqliteVssPackages = ['sqlite-vss-linux-x64', 'sqlite-vss-darwin-x64', 'sqlite-vss-darwin-arm64']
for (const sqliteVssPackage of sqliteVssPackages) {
// some binary may not exist in other platforms, so allow failing here.
tasks.push(fs.copy(path.resolve(projectRoot, `node_modules/${sqliteVssPackage}`), path.resolve(cwd, `node_modules/${sqliteVssPackage}`), { dereference: true }).catch(() => {}));
tasks.push(fs.copy(path.resolve(projectRoot, 'node_modules', sqliteVssPackage), path.resolve(cwd, 'node_modules', sqliteVssPackage), { dereference: true }).catch(() => {}));
}
// it has things like `git/bin/libexec/git-core/git-add` link to `git/bin/libexec/git-core/git`, to reduce size, so can't use `dereference: true` here.
// And pnpm will have node_modules/dugite to be a shortcut, can't just copy it with `dereference: false`, have to copy from .pnpm folder
tasks.push(fs.copy(path.join(projectRoot, 'node_modules', '.pnpm', `dugite@${packageJSON.dependencies.dugite}`, 'node_modules', 'dugite'), path.join(cwd, 'node_modules', 'dugite'), { dereference: false }));
// we only need its `main` binary, no need its dependency and code, because we already copy it to src/services/native/externalApp
tasks.push(fs.mkdirp(path.join(cwd, 'node_modules', 'app-path')).then(async () => {
await fs.copy(path.join(projectRoot, 'node_modules', 'app-path', 'main'), path.join(cwd, 'node_modules', 'app-path', 'main'), { dereference: true })
}));
}
/** sign it for mac m1 https://www.zhihu.com/question/431722091/answer/1592339574 (only work if user run this.)
* And have error

View file

@ -143,8 +143,11 @@ export interface IWorkflowListItem {
graphJSONString: string;
id: string;
image?: string;
/**
* Things that only exist on runtime, and won't be persisted.
*/
metadata?: {
tiddler: IWorkflowTiddler;
tiddler?: IWorkflowTiddler;
workspace?: IWorkspaceWithMetadata;
};
tags: string[];

View file

@ -5,10 +5,10 @@ import React, { ChangeEvent, useCallback, useState } from 'react';
import SimpleBar from 'simplebar-react';
import styled from 'styled-components';
import { useTranslation } from 'react-i18next';
import { AddItemDialog } from './AddItemDialog';
import { useAvailableFilterTags, useWorkflows } from './useWorkflowDataSource';
import { IWorkflowListItem, WorkflowList } from './WorkflowList';
import { useTranslation } from 'react-i18next';
const WorkflowManageContainer = styled(Box)`
display: flex;

View file

@ -1,10 +1,17 @@
import { dialog, net } from 'electron';
import { getRemoteName, getRemoteUrl, GitStep, ModifiedFileList } from 'git-sync-js';
import { inject, injectable } from 'inversify';
import { Observer } from 'rxjs';
import { ModuleThread, spawn, Worker } from 'threads';
// @ts-expect-error it don't want .ts
// eslint-disable-next-line import/no-webpack-loader-syntax
import workerURL from 'threads-plugin/dist/loader?name=gitWorker!./gitWorker.ts';
import { LOCAL_GIT_DIRECTORY } from '@/constants/appPaths';
import { WikiChannel } from '@/constants/channels';
import type { IAuthenticationService, ServiceBranchTypes } from '@services/auth/interface';
import { lazyInject } from '@services/container';
import { i18n } from '@services/libs/i18n';
import { logger } from '@services/libs/log';
import type { INativeService } from '@services/native/interface';
@ -13,17 +20,10 @@ import serviceIdentifier from '@services/serviceIdentifier';
import type { IViewService } from '@services/view/interface';
import type { IWikiService } from '@services/wiki/interface';
import type { IWindowService } from '@services/windows/interface';
import { Observer } from 'rxjs';
import { GitWorker } from './gitWorker';
import { ICommitAndSyncConfigs, IGitLogMessage, IGitService, IGitUserInfos } from './interface';
import { LOCAL_GIT_DIRECTORY } from '@/constants/appPaths';
import { lazyInject } from '@services/container';
import { WindowNames } from '@services/windows/WindowProperties';
import { IWorkspace } from '@services/workspaces/interface';
// @ts-expect-error it don't want .ts
// eslint-disable-next-line import/no-webpack-loader-syntax
import workerURL from 'threads-plugin/dist/loader?name=gitWorker!./gitWorker.ts';
import { GitWorker } from './gitWorker';
import { ICommitAndSyncConfigs, IGitLogMessage, IGitService, IGitUserInfos } from './interface';
import { stepWithChanges } from './stepWithChanges';
@injectable()
@ -51,8 +51,9 @@ export class Git implements IGitService {
private async initWorker(): Promise<void> {
process.env.LOCAL_GIT_DIRECTORY = LOCAL_GIT_DIRECTORY;
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
this.gitWorker = await spawn<GitWorker>(new Worker(workerURL), { timeout: 1000 * 60 });
logger.debug(`initial gitWorker with ${workerURL as string}`, { function: 'Git.initWorker', LOCAL_GIT_DIRECTORY });
this.gitWorker = await spawn<GitWorker>(new Worker(workerURL as string), { timeout: 1000 * 60 });
logger.debug(`initial gitWorker done`, { function: 'Git.initWorker' });
}
public async getModifiedFileList(wikiFolderPath: string): Promise<ModifiedFileList[]> {

View file

@ -36,8 +36,21 @@ export class LanguageModel implements ILanguageModelService {
private llmWorker?: ModuleThread<LLMWorker>;
private async initWorker(): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
this.llmWorker = await spawn<LLMWorker>(new Worker(workerURL), { timeout: 1000 * 60 });
logger.debug(`initial llmWorker with ${workerURL as string}`, { function: 'LanguageModel.initWorker' });
try {
this.llmWorker = await spawn<LLMWorker>(new Worker(workerURL as string), { timeout: 1000 * 30 });
logger.debug(`initial llmWorker done`, { function: 'LanguageModel.initWorker' });
} catch (error) {
if ((error as Error).message.includes('Did not receive an init message from worker after')) {
// https://github.com/andywer/threads.js/issues/426
// wait some time and restart the wiki will solve this
logger.warn(`initWorker() handle "${(error as Error)?.message}", will try recreate worker.`, { function: 'LanguageModel.initWorker' });
await this.initWorker();
} else {
logger.warn('initWorker() unexpected error, throw it', { function: 'LanguageModel.initWorker' });
throw error;
}
}
}
/**
@ -99,18 +112,13 @@ export class LanguageModel implements ILanguageModelService {
return new Observable<ILLMResultPart>((subscriber) => {
const getWikiChangeObserverIIFE = async () => {
const worker = await this.getWorker();
// const template = `Write a short helloworld in JavaScript.`;
// const prompt = `A chat between a user and a useful assistant.
// USER: ${template}
// ASSISTANT:`;
const { defaultModel } = await this.preferenceService.get('languageModel');
const modelPath = path.join(LANGUAGE_MODEL_FOLDER, modelName ?? defaultModel['llama.cpp']);
if (!(await this.checkModelExistsAndWarnUser(modelPath))) {
subscriber.error(new Error(`${i18n.t('LanguageModel.ModelNotExist')} ${modelPath}`));
return;
}
const observable = worker.runLLama$({ prompt, modelPath, conversationID });
const observable = worker.runLLama({ prompt, modelPath, conversationID, openDebugger: false });
observable.subscribe({
next: (result) => {
const loggerCommonMeta = { id: result.id, function: 'LanguageModel.runLLama$' };

View file

@ -55,7 +55,7 @@ export interface IRunLLAmaOptions extends ILLMResultBase {
/**
* Test language model on renderer by:
* ```js
* window.observables.languageModel.runLLama$({ id: '1' }).subscribe({ next: console.log, error: console.error, complete: () => console.warn('completed') })
* window.observables.languageModel.runLLama$({ prompt: 'A chat between a user and an assistant.\nUSER: You are a helpful assistant. Write a simple hello world in JS.\nASSISTANT:\n', id: '1' }).subscribe({ next: console.log, error: console.error, complete: () => console.warn('completed') })
* ```
*/

View file

@ -1,21 +1,30 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
import 'source-map-support/register';
import { LLM } from 'llama-node';
import { LLamaCpp, LoadConfig as LLamaLoadConfig } from 'llama-node/dist/llm/llama-cpp.js';
import type { LoadConfig as LLamaLoadConfig } from 'llama-node/dist/llm/llama-cpp';
import inspector from 'node:inspector';
import { Observable } from 'rxjs';
import { expose } from 'threads/worker';
import { ILanguageModelWorkerResponse } from './interface';
const DEFAULT_TIMEOUT_DURATION = 1000 * 30;
function runLLama$(
options: { conversationID: string; modelPath: string; prompt: string },
function runLLama(
options: { conversationID: string; modelPath: string; openDebugger?: boolean; prompt: string },
): Observable<ILanguageModelWorkerResponse> {
const { conversationID, modelPath, prompt } = options;
const loggerCommonMeta = { level: 'info' as const, meta: { function: 'llmWorker.runLLama$' }, id: conversationID };
const { conversationID, modelPath, prompt, openDebugger } = options;
if (openDebugger === true) {
inspector.open();
inspector.waitForDebugger();
// eslint-disable-next-line no-debugger
debugger;
}
const loggerCommonMeta = { level: 'info' as const, meta: { function: 'llmWorker.runLLama' }, id: conversationID };
return new Observable<ILanguageModelWorkerResponse>((subscriber) => {
void (async function runLLamaObservableIIFE() {
try {
subscriber.next({ message: 'preparing instance and config', ...loggerCommonMeta });
const { LLM } = await import('llama-node');
// use dynamic import cjs version to fix https://github.com/andywer/threads.js/issues/478
const { LLamaCpp } = await import('llama-node/dist/llm/llama-cpp.cjs');
const llama = new LLM(LLamaCpp);
const config: LLamaLoadConfig = {
modelPath,
@ -76,6 +85,6 @@ function runLLama$(
});
}
const llmWorker = { runLLama$ };
const llmWorker = { runLLama };
export type LLMWorker = typeof llmWorker;
expose(llmWorker);

View file

@ -20,7 +20,7 @@ export const defaultPreferences: IPreferences = {
'llama.cpp': 'llama.bin',
'rwkv.cpp': 'rwkv.bin',
},
timeoutDuration: 1000 * 30,
timeoutDuration: 1000 * 60,
},
menuBarAlwaysOnTop: false,
pauseNotifications: '',

View file

@ -32,15 +32,16 @@ import { getSubWikiPluginContent, ISubWikiPluginContent, updateSubWikiPluginCont
import type { IStartNodeJSWikiConfigs, WikiWorker } from './wikiWorker';
import type { IpcServerRouteMethods, IpcServerRouteNames } from './wikiWorker/ipcServerRoutes';
// @ts-expect-error it don't want .ts
// eslint-disable-next-line import/no-webpack-loader-syntax
import workerURL from 'threads-plugin/dist/loader?name=wikiWorker!./wikiWorker/index.ts';
import { LOG_FOLDER } from '@/constants/appPaths';
import { isDevelopmentOrTest } from '@/constants/environment';
import { defaultServerIP } from '@/constants/urls';
import { IDatabaseService } from '@services/database/interface';
import { IPreferenceService } from '@services/preferences/interface';
import { mapValues } from 'lodash';
// @ts-expect-error it don't want .ts
// eslint-disable-next-line import/no-webpack-loader-syntax
import workerURL from 'threads-plugin/dist/loader?name=wikiWorker!./wikiWorker/index.ts';
import { wikiWorkerStartedEventName } from './constants';
import { IWikiOperations, wikiOperations } from './wikiOperations';
@ -143,7 +144,9 @@ export class Wiki implements IWikiService {
tokenAuth,
userName,
};
logger.debug(`initial wikiWorker with ${workerURL as string} for workspaceID ${workspaceID}`, { function: 'Wiki.startWiki' });
const worker = await spawn<WikiWorker>(new Worker(workerURL as string), { timeout: 1000 * 60 });
logger.debug(`initial wikiWorker done`, { function: 'Wiki.startWiki' });
this.wikiWorkers[workspaceID] = worker;
this.wikiWorkerStartedEventTarget.dispatchEvent(new Event(wikiWorkerStartedEventName(workspaceID)));
const wikiLogger = startWikiLogger(workspaceID, name);

4
src/type.d.ts vendored
View file

@ -9,6 +9,10 @@ declare module '@tiddlygit/tiddlywiki' {
export * from 'tiddlywiki';
}
declare module 'llama-node/dist/llm/llama-cpp.cjs' {
export { LLamaCpp } from 'llama-node/dist/llm/llama-cpp';
}
declare module 'the-graph' {
import { Graph, GraphEdge, GraphNode } from 'fbp-graph';
import { MutableRefObject } from 'react';

View file

@ -48,11 +48,18 @@ module.exports = {
},
},
externals: [
// TODO: simply external things will make require can't find things. May need some other way.
// simply external all things will make require can't find things. Only exclude what we copied in scripts/afterPack.js
// nodeExternals({
// additionalModuleDirs: ['@tiddlygit/tiddlywiki'],
// allowlist: [/(threads-plugin)/],
// }),
'@tiddlygit/tiddlywiki',
'llama-node',
'@llama-node/llama-cpp',
'@llama-node/core',
'@llama-node/rwkv-cpp',
'dugite',
'zx',
],
// externalsType: 'commonjs',
// externalsPresets: { electronMain: true },

View file

@ -41,7 +41,8 @@ exports.main = _.compact([
}),
new ExternalsPlugin({
type: 'commonjs',
include: /@tiddlygit\+tiddlywiki@(.+)/,
// use regex works.
include: /@tiddlygit\+tiddlywiki@(.+)|llama-node(.+)|@llama-node(.+)/,
// when using npm, we can use this. But with pnpm, this won't work ↓
// include: path.join(__dirname, 'node_modules', '.pnpm', '@tiddlygit', 'tiddlywiki'),
}),