├── .gitignore ├── public ├── icon.png ├── favicon.ico └── index.html ├── extras ├── gpt-graph.png ├── gtp-graph-attrs.png └── README.md ├── babel.config.js ├── src ├── main.js ├── services │ ├── ColorService.js │ └── OpenAIDavinciService.js ├── config │ └── prompts.js ├── components │ ├── NodeAttributes.vue │ ├── ConfigDialog.vue │ ├── TextEntry.vue │ └── CyGraph.vue ├── background.js ├── assets │ └── logo.svg └── App.vue ├── jsconfig.json ├── vue.config.js ├── package.json ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist_electron 3 | dist_electron/index.js 4 | -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/achousa/gpt-graph/HEAD/public/icon.png -------------------------------------------------------------------------------- /extras/gpt-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/achousa/gpt-graph/HEAD/extras/gpt-graph.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/achousa/gpt-graph/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /extras/gtp-graph-attrs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/achousa/gpt-graph/HEAD/extras/gtp-graph-attrs.png -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | import PrimeVue from 'primevue/config'; 4 | import ToastService from 'primevue/toastservice'; 5 | import BadgeDirective from 'primevue/badgedirective'; 6 | const app = createApp(App); 7 | app.use(PrimeVue); 8 | app.use(ToastService); 9 | app.use(BadgeDirective); 10 | app.mount('#app'); -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "baseUrl": "./", 6 | "moduleResolution": "node", 7 | "paths": { 8 | "@/*": [ 9 | "src/*" 10 | ] 11 | }, 12 | "lib": [ 13 | "esnext", 14 | "dom", 15 | "dom.iterable", 16 | "scripthost" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /extras/README.md: -------------------------------------------------------------------------------- 1 | # gpt-graph 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('@vue/cli-service') 2 | module.exports = defineConfig({ 3 | transpileDependencies: true 4 | }) 5 | 6 | 7 | module.exports = { 8 | pluginOptions: { 9 | electronBuilder: { 10 | nodeIntegration: true, 11 | customFileProtocol: './', 12 | builderOptions: { 13 | appId: "gpt-graph", 14 | win: { 15 | icon: 'public/icon.png' 16 | } 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/services/ColorService.js: -------------------------------------------------------------------------------- 1 | 2 | const colorList = ['#ed3e19', '#b64128', '#e3c727', '#ffee8f', '#3fafeb', '#057ebe', '#875fc8', '#77d88f', '#2c8040', '#f557d4', '#987fdf', '#4aeed5', '#dec19e', '#dade9e', '#cf7483', '#21e7d5', '#8a2c56', '#c8e802','#5c3500','#69926e', '#905e8e', '#5462ea', '#136158', '#7b805c', '#7f3bd6', '#00325c', '#e839e3', '#cfaa2e', '#c6d7e5', '#008cff', '#5938cb', '#00ff23', '#614e7a', '#8bcf2e', '#941414', '#d4ffd9', '#8e8050', '#ff187e', '#bcbbbb', '#a3de1b', '#e6f3c5', '#d8ff76', '#a58ef4', '#7c9db8', '#bab0dc', '#b7ff00', '#4a3e73', '#795e9c']; 3 | 4 | export default class ColorService { 5 | 6 | static calculateHash(data) { 7 | if(!data) { 8 | return 1; 9 | } 10 | let hash = 5381; 11 | for (let i = 0; i < data.length; i++) { 12 | hash = (hash * 33) + data.charCodeAt(i); 13 | } 14 | return (hash % colorList.length) + 1; 15 | } 16 | 17 | static getColor(data) { 18 | return colorList[this.calculateHash(data)] 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/config/prompts.js: -------------------------------------------------------------------------------- 1 | export default { 2 | API_URL: 'https://api.openai.com/v1/completions', 3 | model: 'text-davinci-003', 4 | temperature: 0.6, 5 | task_prompt: 'I want you to behave like an entity extractor. Your job is to interpret text and convert it to a graph of entities and relationships. Entities are going to be nodes of the graph, and relationships are going to be edges between nodes. {{typesPrompt}}. I want you to give me the response formatted as a minimized json that contains a list called nodes and a list called relations. Each node must have this attributes: id (numeric identifier starting with {{initialId}}), label (description), type (type of entity), attributes (list of any attribute that can be attributed to the entity in {name: value} format). The relationships must have the following fields: source (id of the source entity), target (id of the target entity), label (description of the relationship). It is very important that you answer only with a well formed json, do not include text in the response that is not inside the json. The text that you must process is the following: ', 6 | listTypesPrompt: 'The entity types you have to look for, are: ', 7 | noTypesPrompt: 'Feel free to include any entity type you see fit to the text', 8 | maxModelTokens: 3900 9 | } 10 | -------------------------------------------------------------------------------- /src/components/NodeAttributes.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 36 | 37 | -------------------------------------------------------------------------------- /src/services/OpenAIDavinciService.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import config from '../config/prompts.js' 3 | 4 | const API_URL = 'https://api.openai.com/v1/completions'; 5 | 6 | export default class OpenAIDavinciService { 7 | 8 | apiKey; 9 | 10 | constructor(apiKey) { 11 | this.apiKey = apiKey; 12 | } 13 | 14 | calcMaxTokens(text) { 15 | // Roughly calculate number ok tokens in the prompt 16 | return parseInt(config.maxModelTokens - text.trim().length / 4, 10) 17 | } 18 | 19 | async generateText(text, initialId, entityTypes) { 20 | let typesprompt; 21 | if(entityTypes && entityTypes.length > 0) { 22 | typesprompt = config.listTypesPrompt + entityTypes.join(','); 23 | } else { 24 | typesprompt = config.noTypesPrompt; 25 | } 26 | const prompt = config.task_prompt.replace(/{{initialId}}/g, initialId).replace(/{{typesPrompt}}/g, typesprompt); 27 | console.log('GPT PROMPT: '+ prompt); 28 | try { 29 | const response = await axios.post(API_URL, { 30 | model: config.model, 31 | prompt: prompt + '\n\n' + text, 32 | max_tokens: this.calcMaxTokens(prompt + '\n\n' + text), 33 | n: 1, 34 | temperature: config.temperature 35 | }, { 36 | headers: { 37 | 'Authorization': `Bearer ${this.apiKey}`, 38 | 'Content-Type': 'application/json' 39 | } 40 | }); 41 | return response.data; 42 | } catch (error) { 43 | console.error(error.code); 44 | throw error; 45 | } 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gpt-graph", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint", 9 | "electron:build": "vue-cli-service electron:build", 10 | "electron:serve": "vue-cli-service electron:serve", 11 | "postinstall": "electron-builder install-app-deps", 12 | "postuninstall": "electron-builder install-app-deps" 13 | }, 14 | "main": "background.js", 15 | "dependencies": { 16 | "axios": "^1.3.2", 17 | "core-js": "^3.8.3", 18 | "cytoscape": "^3.23.0", 19 | "cytoscape-fcose": "^2.2.0", 20 | "node-sass": "^8.0.0", 21 | "primeicons": "^6.0.1", 22 | "primevue": "^3.23.0", 23 | "sass-loader": "^13.2.0", 24 | "vue": "^3.2.13", 25 | "vue-router": "^4.1.6" 26 | }, 27 | "devDependencies": { 28 | "@babel/core": "^7.12.16", 29 | "@babel/eslint-parser": "^7.12.16", 30 | "@vue/cli-plugin-babel": "~5.0.0", 31 | "@vue/cli-plugin-eslint": "~5.0.0", 32 | "@vue/cli-service": "~5.0.0", 33 | "electron": "^13.0.0", 34 | "electron-devtools-installer": "^3.1.0", 35 | "eslint": "^7.32.0", 36 | "eslint-plugin-vue": "^8.0.3", 37 | "vue-cli-plugin-electron-builder": "~2.1.1" 38 | }, 39 | "eslintConfig": { 40 | "root": true, 41 | "env": { 42 | "node": true 43 | }, 44 | "extends": [ 45 | "plugin:vue/vue3-essential", 46 | "eslint:recommended" 47 | ], 48 | "parserOptions": { 49 | "parser": "@babel/eslint-parser" 50 | }, 51 | "rules": {} 52 | }, 53 | "browserslist": [ 54 | "> 1%", 55 | "last 2 versions", 56 | "not dead", 57 | "not ie 11" 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /src/components/ConfigDialog.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 59 | 60 | -------------------------------------------------------------------------------- /src/background.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import { app, protocol, BrowserWindow } from 'electron' 4 | import { createProtocol } from 'vue-cli-plugin-electron-builder/lib' 5 | import installExtension, { VUEJS3_DEVTOOLS } from 'electron-devtools-installer' 6 | const isDevelopment = process.env.NODE_ENV !== 'production' 7 | 8 | // Scheme must be registered before the app is ready 9 | protocol.registerSchemesAsPrivileged([ 10 | { scheme: 'app', privileges: { secure: true, standard: true } } 11 | ]) 12 | 13 | async function createWindow() { 14 | // Create the browser window. 15 | const win = new BrowserWindow({ 16 | width: 1024, 17 | height: 800, 18 | autoHideMenuBar: true, 19 | webPreferences: { 20 | 21 | // Use pluginOptions.nodeIntegration, leave this alone 22 | // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info 23 | nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION, 24 | contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION, 25 | //devTools: false 26 | } 27 | }) 28 | 29 | if (process.env.WEBPACK_DEV_SERVER_URL) { 30 | // Load the url of the dev server if in development mode 31 | await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL) 32 | if (!process.env.IS_TEST) win.webContents.openDevTools() 33 | } else { 34 | createProtocol('app') 35 | // Load the index.html when not in development 36 | win.loadURL('app://./index.html') 37 | } 38 | } 39 | 40 | // Quit when all windows are closed. 41 | app.on('window-all-closed', () => { 42 | // On macOS it is common for applications and their menu bar 43 | // to stay active until the user quits explicitly with Cmd + Q 44 | if (process.platform !== 'darwin') { 45 | app.quit() 46 | } 47 | }) 48 | 49 | app.on('activate', () => { 50 | // On macOS it's common to re-create a window in the app when the 51 | // dock icon is clicked and there are no other windows open. 52 | if (BrowserWindow.getAllWindows().length === 0) createWindow() 53 | }) 54 | 55 | // This method will be called when Electron has finished 56 | // initialization and is ready to create browser windows. 57 | // Some APIs can only be used after this event occurs. 58 | app.on('ready', async () => { 59 | if (isDevelopment && !process.env.IS_TEST) { 60 | // Install Vue Devtools 61 | try { 62 | await installExtension(VUEJS3_DEVTOOLS) 63 | } catch (e) { 64 | console.error('Vue Devtools failed to install:', e.toString()) 65 | } 66 | } 67 | createWindow() 68 | 69 | }) 70 | 71 | // Exit cleanly on request from parent process in development mode. 72 | if (isDevelopment) { 73 | if (process.platform === 'win32') { 74 | process.on('message', (data) => { 75 | if (data === 'graceful-exit') { 76 | app.quit() 77 | } 78 | }) 79 | } else { 80 | process.on('SIGTERM', () => { 81 | app.quit() 82 | }) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gpt-graph 2 | 3 | ![gpt-graph](https://github.com/achousa/gpt-graph/blob/main/extras/gpt-graph.png?raw=true) 4 | 5 | gpt-graph is a text-to-graph application that uses GPT-3 to generate a graph of entities and their relationships. GPT-3 is a language model developed by OpenAI that can perform a variety of natural language tasks, including text generation, translation, and summarization. With gpt-graph, users can provide a text document to the application, and it will extract the relevant entities and their relationships to create a graph. It can also search for information in the text that cannot be represented as relations, and label the entities with those attributes. 6 | 7 | ![gpt-graph](https://github.com/achousa/gpt-graph/blob/main/extras/gtp-graph-attrs.png?raw=true) 8 | 9 | gpt-graph uses [cytoscape.js](https://js.cytoscape.org/) graph visualization library to display the graph in an interactive format. Users can explore the graph and its relationships by zooming in and out and moving around. 10 | 11 | ## How it works? 12 | 13 | gpt-graph works by providing a custom prompt that instructs the model on how to handle the incoming text and format the output. It can be given a list of entities to identify or left to the model's discretion. Once the graph is on the screen, subsequent pieces of text can be added, and the application will attempt to merge the new information with the existing graph. 14 | 15 | These subsequent calls to add new text are stateless, which means that existing graph information is not used as a starting point. This greatly reduces token consumption and enables the creation of larger graphs. However, the downside of this approach is that entities may be duplicated if they are written slightly differently across the text. 16 | 17 | ## You need an OpenAI API Key ! 18 | 19 | To make requests to the OpenAI API, you'll need to register on the [the OpenAI platform](https://platform.openai.com) and go to the 'User Settings' to generate an API Key. You can then copy and paste the API key into the gpr-graph configuration dialog. Please note that the OpenAI API is a paid service, although if you simply want to try it out, there are free credits available upon registration that will allow for plenty of experimentation. 20 | 21 | ## Technology 22 | 23 | The project makes use of [Electron](https://www.electronjs.org/), [Vue.js](https://vuejs.org/) and [cytoscape](https://js.cytoscape.org/) graph library. [Axios](https://github.com/axios/axios) is also used to make http requests to the [OpenAI Api](https://openai.com/product). 24 | 25 | ## Binaries 26 | 27 | If you dont want to build the project from source, here are some packaged binaries you can use: 28 | 29 | [https://github.com/achousa/gpt-graph/releases](https://github.com/achousa/gpt-graph/releases) 30 | 31 | ## Project setup 32 | ``` 33 | npm install 34 | ``` 35 | 36 | ### Compiles and hot-reloads for development 37 | ``` 38 | npm run electron:serve 39 | ``` 40 | 41 | ### Compiles and minifies for production 42 | ``` 43 | npm run electron:build 44 | ``` 45 | 46 | ### Lints and fixes files 47 | ``` 48 | npm run lint 49 | ``` 50 | 51 | ### Customize configuration 52 | See [Configuration Reference](https://cli.vuejs.org/config/). 53 | 54 | ### Attributions 55 | 56 | Vectors and icons by Nixx Design in CC Attribution License via SVG Repo 57 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/components/TextEntry.vue: -------------------------------------------------------------------------------- 1 |