Skip to content

Commit

Permalink
Merge pull request #3 from wizardAEI/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
wizardAEI authored Dec 22, 2023
2 parents 1b8b29f + 6f47a56 commit cb39ff1
Show file tree
Hide file tree
Showing 63 changed files with 7,191 additions and 610 deletions.
18 changes: 15 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "gomoon",
"productName": "Gomoon",
"version": "1.0.2",
"version": "1.0.3",
"description": "A ai tools for everyone on Mac, Windows and Linux.",
"main": "./out/main/index.js",
"author": "aei",
Expand Down Expand Up @@ -30,15 +30,27 @@
"@zag-js/solid": "^0.31.1",
"@zag-js/switch": "^0.31.1",
"@zag-js/tooltip": "^0.31.1",
"axios": "^1.6.2",
"cheerio": "^1.0.0-rc.12",
"d3-dsv": "2",
"electron-updater": "^6.1.1",
"langchain": "^0.0.193",
"jszip": "^3.10.1",
"langchain": "0.0.209",
"libreoffice-convert": "^1.5.1",
"lodash": "^4.17.21",
"lowdb": "^6.1.1",
"mammoth": "^1.6.0",
"markdown-it": "^13.0.2",
"markdown-it-highlightjs": "^4.0.1",
"moment": "^2.29.4",
"officeparser": "^4.0.5",
"pdf-parse": "^1.1.1",
"puppeteer": "^21.6.1",
"shiki": "^0.14.5",
"solidjs-use": "^2.3.0",
"ulid": "^2.3.0"
"tesseract.js": "^5.0.3",
"ulid": "^2.3.0",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@electron-toolkit/eslint-config-prettier": "^1.0.1",
Expand Down
46 changes: 33 additions & 13 deletions src/main/eventHandler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BrowserWindow, app, ipcMain } from 'electron'
import { BrowserWindow, app, dialog, ipcMain, shell } from 'electron'
import {
addHistory,
createAssistant,
Expand All @@ -15,15 +15,14 @@ import {
setSendWithCmdOrCtrl,
updateAssistant,
updateUserData,
useAssistant
useAssistant,
getLines
} from './model/index'
import {
CreateAssistantModel,
HistoryModel,
UpdateAssistantModel,
UserDataModel
} from './model/model'
import { AssistantModel, CreateAssistantModel, HistoryModel, UserDataModel } from './model/model'
import { hideWindow, setQuicklyWakeUp } from './window'
import parseFile from './lib/ai/fileLoader'
import { writeFile } from 'fs'
import { parseURL2Str } from './lib/ai/parseURL'

export function initAppEventsHandler() {
/**
Expand All @@ -40,12 +39,10 @@ export function initAppEventsHandler() {
})
ipcMain.handle('set-models', (_, models: any) => setModels(models))
ipcMain.handle('set-can-multi-copy', (_, canMultiCopy: boolean) => {
console.log('set-can-multi-copy', canMultiCopy)
setCanMultiCopy(canMultiCopy)
})

ipcMain.handle('set-quickly-wake-up-keys', (_, keys: string) => {
console.log('set-quickly-wake-up-keys', keys)
setQuicklyWakeUpKeys(keys)
setQuicklyWakeUp(keys)
})
Expand All @@ -54,14 +51,14 @@ export function initAppEventsHandler() {
/**
* FEAT: 用户相关
*/
ipcMain.handle('set-user-data', (e, data: Partial<UserDataModel>) => updateUserData(data))
ipcMain.handle('set-user-data', (_, data: Partial<UserDataModel>) => updateUserData(data))
ipcMain.handle('get-user-data', () => getUserData())

/**
* FEAT: assistant 相关
*/
ipcMain.handle('get-assistants', () => getAssistants())
ipcMain.handle('update-assistant', (_, a: UpdateAssistantModel) => updateAssistant(a))
ipcMain.handle('update-assistant', (_, a: AssistantModel) => updateAssistant(a))
ipcMain.handle('delete-assistant', (_, id: string) => deleteAssistant(id))
ipcMain.handle('create-assistant', (_, a: CreateAssistantModel) => createAssistant(a))
ipcMain.handle('use-assistant', (_, id: string) => useAssistant(id))
Expand All @@ -73,8 +70,31 @@ export function initAppEventsHandler() {
ipcMain.handle('add-history', (_, history: HistoryModel) => addHistory(history))
ipcMain.handle('delete-history', (_, id: string) => deleteHistory(id))

// 文件相关
ipcMain.handle('parse-file', (_, files) => parseFile(files))
ipcMain.handle('open-path', (_, path: string) => {
shell.openPath(path)
})
ipcMain.handle('save-file', async (_, fileName: string, content: string) => {
const res = await dialog.showSaveDialog({
title: '保存文件',
buttonLabel: '保存',
defaultPath: fileName,
filters: [
{
name: 'All Files',
extensions: ['*']
}
]
})
if (res.filePath) {
writeFile(res.filePath, content, () => {})
}
})

// 其他
app.on('browser-window-created', () => {})
ipcMain.handle('hide-window', () => hideWindow())

ipcMain.handle('get-lines', () => getLines())
ipcMain.handle('parse-page-to-string', (_, url: string) => parseURL2Str(url))
}
164 changes: 164 additions & 0 deletions src/main/lib/ai/fileLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { TextLoader } from 'langchain/document_loaders/fs/text'
import { PDFLoader } from 'langchain/document_loaders/fs/pdf'
import { DocxLoader } from 'langchain/document_loaders/fs/docx'
import { PPTXLoader } from 'langchain/document_loaders/fs/pptx'
import { CSVLoader } from 'langchain/document_loaders/fs/csv'
import xlsx from 'xlsx'
import { Document } from 'langchain/document'
// import { OpenAIWhisperAudio } from 'langchain/document_loaders/fs/openai_whisper_audio'
import { readFile } from 'fs/promises'
import { app } from 'electron'
import { join } from 'path'
import { copyFileSync, mkdirSync } from 'fs'
import moment from 'moment'

export interface FileLoaderRes {
content: string
path: string
filename: string
}

const appDataPath = app.getPath('userData')
const filesPath = join(appDataPath, 'file')

function processText(text: string) {
// 多个换行变成一个换行
text = text.replace(/\n+/g, '\n')
// 去掉开头的换行
text = text.replace(/^\n/, '')
// 去掉结尾的换行
text = text.replace(/\n$/, '')
// 去掉开头的空格
text = text.replace(/^\s+/, '')
// 去掉结尾的空格
text = text.replace(/\s+$/, '')

return text
}

function processDocs(docs: Document<Record<string, any>>[]) {
return processText(docs.reduce((acc, doc) => acc + doc.pageContent, '\n'))
}

async function parseTextFile(b: Blob) {
const loader = new TextLoader(b)
const docs = await loader.load()
return processDocs(docs)
}
async function parsePDFFile(b: Blob) {
const loader = new PDFLoader(b)
const docs = await loader.load()
return processDocs(docs)
}

async function parseDocxFile(b: Blob) {
const loader = new DocxLoader(b)
const docs = await loader.load()
return processDocs(docs)
}

async function parsePPTXFile(b: Blob) {
const loader = new PPTXLoader(b)
const docs = await loader.load()
return processDocs(docs)
}

async function parseXLSXFile(b: Buffer) {
// 转化成arrayBuffer
const arrayBuffer = new Uint8Array(b).buffer
const workbook = xlsx.read(arrayBuffer, { type: 'array' })
let content = ''
workbook.SheetNames.forEach((sheetName) => {
const worksheet = workbook.Sheets[sheetName]
content += xlsx.utils.sheet_to_csv(worksheet)
})
return processText(content)
}

async function parseJSONFile(b: Buffer) {
const json = b.toString()
return processText(json)
}

async function parseCSVFile(b: Blob) {
const loader = new CSVLoader(b)
const docs = await loader.load()
return processDocs(docs)
}

// async function parseAudioFile(b: Blob) {
// const loader = new OpenAIWhisperAudio(b, {
// clientOptions: {
// apiKey: 'spi-key'
// }
// })
// const docs = await loader.load()
// console.log(docs)
// return docs.reduce((acc, doc) => acc + doc.pageContent, '\n')
// }

export default async function parseFile(
files: {
path: string
type: string
}[]
): Promise<FileLoaderRes> {
const today = moment().format('YYYY-MM-DD')
const targetPath = join(filesPath, `/${today}`)
const targetFile = join(targetPath, files[0].path.split('/').pop()!)
mkdirSync(targetPath, { recursive: true })
copyFileSync(files[0].path, targetFile)
const file = await readFile(targetFile)

const b = new Blob([file], { type: files[0].type })
let content = ''

if (b.type === 'text/plain' || b.type === 'application/msword') {
content = await parseTextFile(b)
}
if (b.type === 'application/pdf') {
content = await parsePDFFile(b)
}
if (b.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
content = await parseDocxFile(b)
}
if (b.type === 'application/vnd.openxmlformats-officedocument.presentationml.presentation') {
content = await parsePPTXFile(b)
}
if (b.type === 'application/json') {
content = await parseJSONFile(file)
}
if (b.type === 'text/csv') {
content = await parseCSVFile(b)
}
if (
b.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
b.type === 'application/vnd.ms-excel'
) {
content = await parseXLSXFile(file)
}

// .mp3,.mp4,.wav,.m4a,.webm,.mpga,.mpeg
// if (
// b.type === 'audio/wav' ||
// b.type === 'audio/mpeg' ||
// b.type === 'audio/mp4' ||
// b.type === 'audio/webm' ||
// b.type === 'audio/wave' ||
// b.type === 'audio/x-wav'
// ) {
// return parseAudioFile(b)
// }
return {
content,
path: targetPath,
filename: files[0].path.split('/').pop()!
}
}

parseFile([
{
path: '/Users/wangdejiang/Desktop/story.json',
type: 'application/json'
}
])
11 changes: 11 additions & 0 deletions src/main/lib/ai/parseURL.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import puppeteer from 'puppeteer'
export async function parseURL2Str(url: string) {
const page = await (await puppeteer.launch()).newPage()
await page.goto(url, { waitUntil: 'load' })
const mainContent = await page.evaluate(() => {
const content = document.querySelector('body')
return content ? content.innerText : ''
})

return mainContent
}
File renamed without changes.
9 changes: 9 additions & 0 deletions src/main/model/default/getDefaultLines.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { readFileSync } from 'fs'
import { getResourcesPath } from '../../lib'
import { Line } from '../model'

export default function (): Line[] {
const lines = readFileSync(getResourcesPath('lines.json'), 'utf-8')
const ls = JSON.parse(lines)
return ls
}
27 changes: 16 additions & 11 deletions src/main/model/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import { app } from 'electron'
import { JSONSyncPreset } from 'lowdb/node'
import {
AssistantModel,
CreateAssistantModel,
HistoryModel,
SettingModel,
UpdateAssistantModel
} from './model'
import { AssistantModel, CreateAssistantModel, HistoryModel, SettingModel } from './model'
import { getDefaultUserData } from './default/getDefaultUserData'
import { getDefaultConfig } from './default/getDefaultConfig'
import { join } from 'path'
import { ulid } from 'ulid'
import { merge } from 'lodash'
import getDefaultAssistants from './default/assistants'
import getDefaultAssistants from './default/getDefaultAssistants'
import getDefaultLines from './default/getDefaultLines'

const appDataPath = app.getPath('userData')
const configDB = JSONSyncPreset(join(appDataPath, 'config.json'), getDefaultConfig())
Expand Down Expand Up @@ -96,7 +91,7 @@ const assistantsDB = JSONSyncPreset(join(appDataPath, 'assistants.json'), getDef
export function getAssistants() {
return assistantsDB.data || []
}
export function updateAssistant(a: UpdateAssistantModel) {
export function updateAssistant(a: AssistantModel) {
const index = assistantsDB.data.findIndex((item) => item.id === a.id)
if (index === -1) {
assistantsDB.data = [
Expand All @@ -110,7 +105,10 @@ export function updateAssistant(a: UpdateAssistantModel) {
} else {
assistantsDB.data[index] = {
...a,
version: assistantsDB.data[index].version + 1
version:
assistantsDB.data[index].version < a.version
? a.version
: assistantsDB.data[index].version + 1
}
}
assistantsDB.write()
Expand Down Expand Up @@ -149,7 +147,7 @@ export function useAssistant(id: string) {
}

/**
* Histories 相关
* FEAT: Histories 相关
*/
const historiesDB = JSONSyncPreset<HistoryModel[]>(join(appDataPath, 'histories.json'), [])

Expand All @@ -170,3 +168,10 @@ export function deleteHistory(id: string) {
historiesDB.data.splice(index, 1)
historiesDB.write()
}

/**
* FEAT: 首页显示的文字 Lines
*/
export function getLines() {
return getDefaultLines()
}
7 changes: 6 additions & 1 deletion src/main/model/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,16 @@ export type AssistantModel = (
tools?: ToolEnum[]
}

export type UpdateAssistantModel = Omit<AssistantModel, 'version'>
export type CreateAssistantModel = Omit<AssistantModel, 'id' | 'version'>

export interface HistoryModel {
id: string
type: 'chat' | 'ans'
assistantId?: string
contents: { id?: string; role: 'human' | 'system' | 'ai' | 'ans' | 'question'; content: string }[]
}

export interface Line {
content: string
from: string
}
Loading

0 comments on commit cb39ff1

Please sign in to comment.