├── README.md ├── admin └── src │ ├── api │ ├── completions │ │ └── index.js │ ├── models │ │ └── index.js │ └── settings │ │ └── index.js │ ├── components │ ├── CMEditView │ │ └── RightLinksCompo │ │ │ └── index.js │ └── Initializer │ │ └── index.js │ ├── index.js │ ├── pluginId.js │ ├── translations │ ├── en.json │ └── fr.json │ └── utils │ ├── axiosInstance.js │ └── getTrad.js ├── package.json ├── server ├── bootstrap.js ├── config │ └── index.js ├── content-types │ └── index.js ├── controllers │ ├── completions.js │ ├── index.js │ ├── models.js │ └── settings.js ├── destroy.js ├── index.js ├── middlewares │ └── index.js ├── policies │ └── index.js ├── register.js ├── routes │ ├── completion.js │ ├── index.js │ ├── models.js │ └── settings.js └── services │ ├── completions.js │ ├── index.js │ ├── models.js │ └── settings.js ├── strapi-admin.js └── strapi-server.js /README.md: -------------------------------------------------------------------------------- 1 | # Strapi plugin Open AI completion 2 | 3 | The official plugin that allows you to create Open AI completion from a prompt 4 | 5 | ## Features 6 | 7 | - Create completion from multiple [Open AI models](https://beta.openai.com/docs/models) 8 | - Adjust the [temperature](https://beta.openai.com/docs/api-reference/completions/create#completions/create-temperature) and [max_tokens](https://beta.openai.com/docs/api-reference/completions/create#completions/create-max_tokens) of the completion 9 | 10 | ## Installation 11 | 12 | To install this plugin, you need to add an NPM dependency to your Strapi application. 13 | 14 | ```sh 15 | # Using Yarn 16 | yarn add @strapi/plugin-open-ai 17 | 18 | # Or using NPM 19 | npm install @strapi/plugin-open-ai 20 | ``` 21 | 22 | ## Configuration 23 | 24 | `./config/plugins.js` 25 | 26 | ```js 27 | module.exports = ({ env }) => ({ 28 | // ... 29 | 'open-ai': { 30 | enabled: true, 31 | config: { 32 | API_TOKEN: '', 33 | }, 34 | }, 35 | // ... 36 | }); 37 | ``` 38 | 39 | Then, you'll need to build your admin panel: 40 | 41 | ```sh 42 | # Using Yarn 43 | yarn build 44 | 45 | # Or using NPM 46 | npm run build 47 | ``` 48 | 49 | ## How it works 50 | 51 | You can create AI completions in the content-manager view, when creating or updating an entry. It is mandatory to have a working Open AI API token in your `./config/plugins.js` for the plugin to work. 52 | 53 | You have two tabs when you open the plugin modal: 54 | 55 | - Prompt: This is where you can write your prompt and generate the completion. Once generated, you will be able to either clear the completion textarea or to copy it in your clipboard. Copying the completion in your clipboard will automatically close the modal. 56 | 57 | - Settings: You can play with some completion API configuration such as selecting your favorite model, having a certain temperature and max_tokens for your completions. It is possible to save your settings! 58 | 59 | *Note that the plugin will automatically put the temperature value between 0 and 1 and the max_tokens value between 0 and 4096* 60 | 61 | The plugin has a static list of Open AI models downloaded on late January 2023 (`src/server/services/settings.js`). It is however possible to download actual models in the settings tab. The select input will automatically be updated with the new models. 62 | 63 | Default settings are the following: 64 | 65 | - model: '' 66 | - temperature: 1 67 | - max_tokens: 16 68 | - models: [...] 69 | 70 | Please find the Open AI doc for each one of them: 71 | 72 | - [Models](https://beta.openai.com/docs/models) 73 | - [Temperature](https://beta.openai.com/docs/api-reference/completions/create#completions/create-temperature) 74 | - [max_tokens](https://beta.openai.com/docs/api-reference/completions/create#completions/create-max_tokens) 75 | 76 | Enjoy! -------------------------------------------------------------------------------- /admin/src/api/completions/index.js: -------------------------------------------------------------------------------- 1 | import { request } from '@strapi/helper-plugin'; 2 | 3 | const completions = { 4 | create: async ({ model, messages, temperature, maxTokens }) => { 5 | const data = await request(`/open-ai/completions`, { 6 | method: 'POST', 7 | body: { model, messages, temperature, maxTokens }, 8 | }); 9 | return data; 10 | }, 11 | }; 12 | export default completions; 13 | -------------------------------------------------------------------------------- /admin/src/api/models/index.js: -------------------------------------------------------------------------------- 1 | import { request } from '@strapi/helper-plugin'; 2 | 3 | const models = { 4 | get: async () => { 5 | const data = await request(`/open-ai/models`, { 6 | method: 'GET', 7 | }); 8 | return data; 9 | }, 10 | }; 11 | export default models; 12 | -------------------------------------------------------------------------------- /admin/src/api/settings/index.js: -------------------------------------------------------------------------------- 1 | import { request } from '@strapi/helper-plugin'; 2 | 3 | const settings = { 4 | get: async () => { 5 | const data = await request(`/open-ai/settings`, { 6 | method: 'GET', 7 | }); 8 | return data; 9 | }, 10 | set: async (data) => { 11 | return await request(`/open-ai/settings`, { 12 | method: 'POST', 13 | body: data, 14 | }); 15 | } 16 | }; 17 | export default settings; 18 | -------------------------------------------------------------------------------- /admin/src/components/CMEditView/RightLinksCompo/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | 3 | import { useIntl } from 'react-intl'; 4 | import getTrad from '../../../utils/getTrad'; 5 | 6 | import { Box } from '@strapi/design-system/Box'; 7 | import { Textarea } from '@strapi/design-system'; 8 | import { TextInput } from '@strapi/design-system'; 9 | import { Button } from '@strapi/design-system/Button'; 10 | import { Select, Option } from '@strapi/design-system'; 11 | import { Divider } from '@strapi/design-system/Divider'; 12 | import { Typography } from '@strapi/design-system/Typography'; 13 | import { NumberInput } from '@strapi/design-system'; 14 | 15 | import { Grid, GridItem } from '@strapi/design-system'; 16 | import { 17 | ModalLayout, 18 | ModalBody, 19 | ModalHeader, 20 | ModalFooter, 21 | } from '@strapi/design-system'; 22 | 23 | import { 24 | Tabs, 25 | Tab, 26 | TabGroup, 27 | TabPanels, 28 | TabPanel, 29 | } from '@strapi/design-system'; 30 | 31 | import Lock from '@strapi/icons/Lock'; 32 | import Download from '@strapi/icons/Download'; 33 | import Trash from '@strapi/icons/Trash'; 34 | import Duplicate from '@strapi/icons/Duplicate'; 35 | 36 | const modelsAPI = require('../../../api/models').default; 37 | const settingsAPI = require('../../../api/settings').default; 38 | const completionAPI = require('../../../api/completions').default; 39 | 40 | const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); 41 | 42 | const RightLinksCompo = () => { 43 | const { formatMessage } = useIntl(); 44 | 45 | const [isVisible, setIsVisible] = useState(false); 46 | 47 | const [prompt, setPrompt] = useState(undefined); 48 | const [completion, setCompletion] = useState(undefined); 49 | const [finishReason, setFinishReason] = useState(null); 50 | 51 | const [model, setModel] = useState(); 52 | const [temperature, setTemperature] = useState(); 53 | const [maxTokens, setMaxTokens] = useState(); 54 | 55 | const [saveModelText, setSaveModelText] = useState( 56 | formatMessage({ 57 | id: getTrad('Modal.save-model.button.text.default'), 58 | defaultMessage: 'Save model settings', 59 | }) 60 | ); 61 | const [downloadedModelsText, setDownloadedModelsText] = useState( 62 | formatMessage({ 63 | id: getTrad('Modal.tabs.settings.download.button.text.default'), 64 | defaultMessage: 'Download models', 65 | }) 66 | ); 67 | const [generateCompletionText, setGenerateCompletionText] = useState( 68 | formatMessage({ 69 | id: getTrad('Modal.tabs.prompt.generate.button.text.default'), 70 | defaultMessage: 'Generate', 71 | }) 72 | ); 73 | const [defaultSettings, setDefaultSettings] = useState(null); 74 | 75 | useEffect(() => { 76 | const fetchDefaultSettings = async () => { 77 | const tmpSettings = await settingsAPI.get(); 78 | setDefaultSettings(tmpSettings); 79 | 80 | setModel(tmpSettings?.model); 81 | setTemperature(tmpSettings?.temperature); 82 | setMaxTokens(tmpSettings?.maxTokens); 83 | }; 84 | fetchDefaultSettings(); 85 | }, []); 86 | 87 | const handleSaveDefaultSettings = () => { 88 | settingsAPI 89 | .set({ model, temperature, maxTokens, models: defaultSettings.models }) 90 | .then(async () => { 91 | setSaveModelText( 92 | formatMessage({ 93 | id: getTrad('Modal.save-model.button.text.after'), 94 | defaultMessage: 'Saved!', 95 | }) 96 | ); 97 | await delay(2000); 98 | setSaveModelText( 99 | formatMessage({ 100 | id: getTrad('Modal.save-model.button.text.default'), 101 | defaultMessage: 'Save model settings', 102 | }) 103 | ); 104 | }); 105 | }; 106 | 107 | const handleDownloadOpenAIModels = () => { 108 | setDownloadedModelsText( 109 | formatMessage({ 110 | id: getTrad('Modal.tabs.settings.download.button.text.pending'), 111 | defaultMessage: 'Downloading...', 112 | }) 113 | ); 114 | modelsAPI.get().then((data) => { 115 | setDefaultSettings({ ...defaultSettings, models: data }); 116 | settingsAPI.set({ ...defaultSettings, models: data }).then(async () => { 117 | setDownloadedModelsText( 118 | formatMessage({ 119 | id: getTrad('Modal.tabs.settings.download.button.text.default'), 120 | defaultMessage: 'Download models', 121 | }) 122 | ); 123 | }); 124 | }); 125 | }; 126 | 127 | const handlePromptSubmit = () => { 128 | if (model && prompt && temperature && maxTokens) { 129 | const messages = [{ role: 'user', content: prompt }]; 130 | setGenerateCompletionText('Generating completion...'); 131 | completionAPI 132 | .create({ model, messages, temperature, maxTokens }) 133 | .then((data) => { 134 | setCompletion(data?.choices[0]?.text.trim()); 135 | setFinishReason(data?.choices[0]?.finish_reason); 136 | }) 137 | .finally(() => { 138 | setGenerateCompletionText('Generate'); 139 | }); 140 | } 141 | }; 142 | 143 | const handleCopyToClipboard = () => { 144 | setIsVisible((prev) => !prev); 145 | navigator.clipboard.writeText(completion); 146 | }; 147 | 148 | return ( 149 | 161 | 162 | 163 | {formatMessage({ 164 | id: getTrad('Plugin.name'), 165 | defaultMessage: 'Open AI Completion', 166 | })} 167 | 168 | 169 | 170 | 171 | 172 | 182 | {isVisible && ( 183 | setIsVisible((prev) => !prev)} 185 | labelledBy="title" 186 | > 187 | 188 | 194 | {formatMessage({ 195 | id: getTrad('Plugin.name'), 196 | defaultMessage: 'Open AI Completion', 197 | })} 198 | 199 | 200 | 201 | 202 | 207 | 208 | 209 | {formatMessage({ 210 | id: getTrad('Modal.tabs.prompt'), 211 | defaultMessage: 'Prompt', 212 | })} 213 | 214 | 215 | {formatMessage({ 216 | id: getTrad('Modal.tabs.settings'), 217 | defaultMessage: 'Settings', 218 | })} 219 | 220 | 221 | 222 | 223 | 229 | setPrompt(e.target.value)} 238 | value={prompt} 239 | /> 240 | 241 | 247 | 250 | 251 | 252 | 259 | 276 | 277 | 278 | 279 | 285 | 292 | 293 | 294 | 307 | 308 | 309 | 310 | 311 | 319 | 320 | 321 | 322 | 323 | 330 | 337 | 338 | 339 | 350 | setTemperature( 351 | value >= 0 && value <= 1 ? value : 1 352 | ) 353 | } 354 | value={temperature} 355 | /> 356 | 357 | 358 | 359 | 360 | 371 | setMaxTokens( 372 | value > 0 && value <= 4096 ? value : 16 373 | ) 374 | } 375 | value={maxTokens} 376 | /> 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | setIsVisible((prev) => !prev)} 390 | variant="tertiary" 391 | > 392 | {formatMessage({ 393 | id: getTrad('Modal.cancel.button.text'), 394 | defaultMessage: 'Cancel', 395 | })} 396 | 397 | } 398 | endActions={ 399 | <> 400 | {JSON.stringify({ 401 | model: defaultSettings.model, 402 | temperature: defaultSettings.temperature, 403 | maxTokens: defaultSettings.maxTokens, 404 | }) !== 405 | JSON.stringify({ model, temperature, maxTokens }) && ( 406 | 413 | )} 414 | 415 | {completion && ( 416 | 426 | )} 427 | 428 | {completion && ( 429 | 438 | )} 439 | 440 | } 441 | /> 442 | 443 | )} 444 | 445 | 446 | 447 | ); 448 | }; 449 | 450 | export default RightLinksCompo; 451 | -------------------------------------------------------------------------------- /admin/src/components/Initializer/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Initializer 4 | * 5 | */ 6 | 7 | import { useEffect, useRef } from 'react'; 8 | import PropTypes from 'prop-types'; 9 | import pluginId from '../../pluginId'; 10 | 11 | const Initializer = ({ setPlugin }) => { 12 | const ref = useRef(); 13 | ref.current = setPlugin; 14 | 15 | useEffect(() => { 16 | ref.current(pluginId); 17 | }, []); 18 | 19 | return null; 20 | }; 21 | 22 | Initializer.propTypes = { 23 | setPlugin: PropTypes.func.isRequired, 24 | }; 25 | 26 | export default Initializer; 27 | -------------------------------------------------------------------------------- /admin/src/index.js: -------------------------------------------------------------------------------- 1 | import { prefixPluginTranslations } from '@strapi/helper-plugin'; 2 | import pluginPkg from '../../package.json'; 3 | import pluginId from './pluginId'; 4 | import Initializer from './components/Initializer'; 5 | import RightLinksCompo from './components/CMEditView/RightLinksCompo'; 6 | 7 | const name = pluginPkg.strapi.name; 8 | 9 | export default { 10 | register(app) { 11 | app.registerPlugin({ 12 | id: pluginId, 13 | initializer: Initializer, 14 | isReady: false, 15 | name, 16 | }); 17 | }, 18 | 19 | bootstrap(app) { 20 | app.injectContentManagerComponent('editView', 'right-links', { 21 | name: 'open-ai-plugin', 22 | Component: RightLinksCompo, 23 | }); 24 | }, 25 | async registerTrads({ locales }) { 26 | const importedTrads = await Promise.all( 27 | locales.map((locale) => { 28 | return import( 29 | /* webpackChunkName: "translation-[request]" */ `./translations/${locale}.json` 30 | ) 31 | .then(({ default: data }) => { 32 | return { 33 | data: prefixPluginTranslations(data, pluginId), 34 | locale, 35 | }; 36 | }) 37 | .catch(() => { 38 | return { 39 | data: {}, 40 | locale, 41 | }; 42 | }); 43 | }) 44 | ); 45 | 46 | return Promise.resolve(importedTrads); 47 | }, 48 | }; 49 | -------------------------------------------------------------------------------- /admin/src/pluginId.js: -------------------------------------------------------------------------------- 1 | import pluginPkg from '../../package.json'; 2 | 3 | const pluginId = pluginPkg.name.replace(/^(@[^-,.][\w,-]+\/|strapi-)plugin-/i, ''); 4 | 5 | export default pluginId; 6 | -------------------------------------------------------------------------------- /admin/src/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "Plugin.name": "Open AI Completion", 3 | "RightLinks.button": "Completion", 4 | "Modal.tabs.prompt": "Prompt", 5 | "Modal.tabs.settings": "Settings", 6 | "Modal.tabs.prompt.generate.button.text.default": "Generate", 7 | "Modal.tabs.prompt.generate.button.text.pending": "Generating...", 8 | "Modal.tabs.settings.download.button.text.default": "Download models", 9 | "Modal.tabs.settings.download.button.text.pending": "Downloading...", 10 | "Modal.tabs.prompt.finish-reason.text": "Finish reason:", 11 | "Modal.tabs.settings.temperature.hint.text": "Between 0 and 1 (default). Higher values means the model will take more risks. Try 0,9 for more creative applications, and 0 for ones with a well-defined answer.", 12 | "Modal.tabs.settings.maxTokens.hint.text": "The token count of your prompt plus max_tokens cannot exceed the model's context length. Most models have a context length of 2048 tokens (except for the newest models, which support 4096).", 13 | "Modal.cancel.button.text": "Cancel", 14 | "Modal.save-model.button.text.default": "Save model settings", 15 | "Modal.save-model.button.text.after": "Saved!", 16 | "Modal.clear.button.text": "Clear completion", 17 | "Modal.copy-to-clipboard.button.text": "Copy to clipboard", 18 | "Modal.tabs.prompt.placeholder": "Explain what is Strapi to a 5 years old" 19 | } -------------------------------------------------------------------------------- /admin/src/translations/fr.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/utils/axiosInstance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * axios with a custom config. 3 | */ 4 | 5 | import axios from 'axios'; 6 | import { auth } from '@strapi/helper-plugin'; 7 | 8 | const instance = axios.create({ 9 | baseURL: process.env.STRAPI_ADMIN_BACKEND_URL, 10 | }); 11 | 12 | instance.interceptors.request.use( 13 | async (config) => { 14 | config.headers = { 15 | Authorization: `Bearer ${auth.getToken()}`, 16 | Accept: 'application/json', 17 | 'Content-Type': 'application/json', 18 | }; 19 | 20 | return config; 21 | }, 22 | (error) => { 23 | Promise.reject(error); 24 | } 25 | ); 26 | 27 | instance.interceptors.response.use( 28 | (response) => response, 29 | (error) => { 30 | // whatever you want to do with the error 31 | if (error.response?.status === 401) { 32 | auth.clearAppStorage(); 33 | window.location.reload(); 34 | } 35 | 36 | throw error; 37 | } 38 | ); 39 | 40 | export default instance; 41 | -------------------------------------------------------------------------------- /admin/src/utils/getTrad.js: -------------------------------------------------------------------------------- 1 | import pluginId from '../pluginId'; 2 | 3 | const getTrad = (id) => `${pluginId}.${id}`; 4 | 5 | export default getTrad; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@strapi/plugin-open-ai", 3 | "version": "1.0.3", 4 | "description": "The official plugin that allows you to create Open AI completion from a prompt", 5 | "strapi": { 6 | "name": "open-ai", 7 | "description": "The official plugin that allows you to create Open AI completion from a prompt", 8 | "kind": "plugin", 9 | "displayName": "Open AI" 10 | }, 11 | "author": "Strapi Solutions SAS (https://strapi.io)", 12 | "maintainers": [ 13 | "Strapi Solutions SAS (https://strapi.io)" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/strapi/strapi-plugin-open-ai.git" 18 | }, 19 | "peerDependencies": { 20 | "@strapi/strapi": "^4.0.0" 21 | }, 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/strapi/strapi-plugin-open-ai/issues" 25 | }, 26 | "homepage": "https://github.com/strapi/strapi-plugin-open-ai#readme", 27 | "main": "strapi-admin.js", 28 | "scripts": { 29 | "test": "echo \"Error: no test specified\" && exit 1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /server/bootstrap.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = ({ strapi }) => { 4 | // bootstrap phase 5 | }; 6 | -------------------------------------------------------------------------------- /server/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | default: {}, 5 | validator() {}, 6 | }; 7 | -------------------------------------------------------------------------------- /server/content-types/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = {}; 4 | -------------------------------------------------------------------------------- /server/controllers/completions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = ({ strapi }) => { 4 | const completionService = strapi.plugins['open-ai'].services.completions; 5 | 6 | const createCompletion = async (ctx) => { 7 | const { model, messages, temperature, maxTokens } = ctx.request.body; 8 | 9 | if (model && messages && temperature && maxTokens) { 10 | try { 11 | return completionService.createCompletion({ 12 | model, 13 | messages, 14 | temperature, 15 | maxTokens, 16 | }); 17 | } catch (err) { 18 | console.log(err); 19 | ctx.throw(500, err); 20 | } 21 | } 22 | return ctx.throw(400, 'Either the messages, temperature, model or maxToken parameter is missing'); 23 | }; 24 | 25 | return { 26 | createCompletion, 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /server/controllers/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const models = require('./models'); 4 | const settings = require('./settings'); 5 | const completions = require('./completions'); 6 | 7 | module.exports = { 8 | models, 9 | settings, 10 | completions, 11 | }; 12 | -------------------------------------------------------------------------------- /server/controllers/models.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = ({ strapi }) => { 4 | const modelService = strapi.plugins['open-ai'].services.models; 5 | 6 | const getModels = async (ctx) => { 7 | try { 8 | return modelService.getModels(); 9 | } catch (err) { 10 | ctx.throw(500, err); 11 | } 12 | }; 13 | 14 | return { 15 | getModels, 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /server/controllers/settings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = ({ strapi }) => { 4 | const settingsService = strapi.plugins['open-ai'].services.settings; 5 | 6 | const getSettings = async (ctx) => { 7 | try { 8 | return settingsService.getSettings(); 9 | } catch (err) { 10 | ctx.throw(500, err); 11 | } 12 | }; 13 | const setSettings = async (ctx) => { 14 | const { body } = ctx.request; 15 | try { 16 | await settingsService.setSettings(body); 17 | return settingsService.getSettings(); 18 | } catch (err) { 19 | ctx.throw(500, err); 20 | } 21 | }; 22 | return { 23 | getSettings, 24 | setSettings, 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /server/destroy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = ({ strapi }) => { 4 | // destroy phase 5 | }; 6 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const register = require('./register'); 4 | const bootstrap = require('./bootstrap'); 5 | const destroy = require('./destroy'); 6 | const config = require('./config'); 7 | const contentTypes = require('./content-types'); 8 | const controllers = require('./controllers'); 9 | const routes = require('./routes'); 10 | const middlewares = require('./middlewares'); 11 | const policies = require('./policies'); 12 | const services = require('./services'); 13 | 14 | module.exports = { 15 | register, 16 | bootstrap, 17 | destroy, 18 | config, 19 | controllers, 20 | routes, 21 | services, 22 | contentTypes, 23 | policies, 24 | middlewares, 25 | }; 26 | -------------------------------------------------------------------------------- /server/middlewares/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = {}; 4 | -------------------------------------------------------------------------------- /server/policies/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = {}; 4 | -------------------------------------------------------------------------------- /server/register.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = ({ strapi }) => { 4 | // registeration phase 5 | }; 6 | -------------------------------------------------------------------------------- /server/routes/completion.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // accessible only from admin UI 3 | type: 'admin', 4 | routes: [ 5 | { 6 | method: 'POST', 7 | path: '/completions', 8 | handler: 'completions.createCompletion', 9 | config: { policies: [] }, 10 | }, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | models: require('./models'), 3 | settings: require('./settings'), 4 | completion: require('./completion'), 5 | }; 6 | -------------------------------------------------------------------------------- /server/routes/models.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // accessible only from admin UI 3 | type: 'admin', 4 | routes: [ 5 | { 6 | method: 'GET', 7 | path: '/models', 8 | handler: 'models.getModels', 9 | config: { policies: [] }, 10 | }, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /server/routes/settings.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // accessible only from admin UI 3 | type: 'admin', 4 | routes: [ 5 | { 6 | method: 'GET', 7 | path: '/settings', 8 | handler: 'settings.getSettings', 9 | config: { policies: [] }, 10 | }, 11 | { 12 | method: 'POST', 13 | path: '/settings', 14 | handler: 'settings.setSettings', 15 | config: { policies: [] }, 16 | }, 17 | ], 18 | }; 19 | -------------------------------------------------------------------------------- /server/services/completions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fetch = require('node-fetch'); 4 | 5 | module.exports = ({ strapi }) => { 6 | const createCompletion = async ({ 7 | model, 8 | messages, 9 | temperature, 10 | maxTokens, 11 | }) => { 12 | try { 13 | const response = await fetch( 14 | `https://api.openai.com/v1/chat/completions`, 15 | { 16 | method: 'POST', 17 | headers: { 18 | 'Content-Type': 'application/json', 19 | Authorization: `Bearer ${strapi 20 | .plugin('open-ai') 21 | .config('API_TOKEN')}`, 22 | }, 23 | body: JSON.stringify({ 24 | model, 25 | messages, 26 | temperature, 27 | max_tokens: maxTokens, 28 | }), 29 | } 30 | ); 31 | 32 | const res = await response.json(); 33 | return res; 34 | } catch (error) { 35 | console.log(error); 36 | } 37 | }; 38 | 39 | return { 40 | createCompletion, 41 | }; 42 | }; 43 | -------------------------------------------------------------------------------- /server/services/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const models = require('./models'); 4 | const settings = require('./settings'); 5 | const completions = require('./completions'); 6 | 7 | module.exports = { 8 | models, 9 | settings, 10 | completions, 11 | }; 12 | -------------------------------------------------------------------------------- /server/services/models.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fetch = require('node-fetch'); 4 | 5 | module.exports = ({ strapi }) => { 6 | const getModels = async () => { 7 | try { 8 | const models = await fetch(`https://api.openai.com/v1/models`, { 9 | method: 'GET', 10 | headers: { 11 | 'Content-Type': 'application/json', 12 | Authorization: `Bearer ${strapi 13 | .plugin('open-ai') 14 | .config('API_TOKEN')}`, 15 | }, 16 | }); 17 | 18 | const res = await models.json(); 19 | return res?.data?.map((model) => model.id); 20 | } catch (error) { 21 | console.log(error, 'returning default models'); 22 | return strapi 23 | .store({ 24 | environment: '', 25 | type: 'plugin', 26 | name: 'open-ai', 27 | }) 28 | .get({ key: 'settings' }).models; 29 | } 30 | }; 31 | 32 | return { 33 | getModels, 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /server/services/settings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = ({ strapi }) => { 4 | const getPluginStore = () => { 5 | return strapi.store({ 6 | environment: '', 7 | type: 'plugin', 8 | name: 'open-ai', 9 | }); 10 | }; 11 | 12 | // Create default settings 13 | const createDefaultConfig = async () => { 14 | const pluginStore = getPluginStore(); 15 | const value = { 16 | model: '', 17 | temperature: 1, 18 | maxTokens: 16, 19 | models: [ 20 | 'dall-e-3', 21 | 'whisper-1', 22 | 'davinci-002', 23 | 'babbage-002', 24 | 'dall-e-2', 25 | 'gpt-3.5-turbo-16k', 26 | 'tts-1-hd-1106', 27 | 'tts-1-hd', 28 | 'gpt-3.5-turbo-1106', 29 | 'gpt-3.5-turbo-instruct-0914', 30 | 'gpt-3.5-turbo-instruct', 31 | 'tts-1', 32 | 'gpt-3.5-turbo-0301', 33 | 'tts-1-1106', 34 | 'gpt-3.5-turbo-0125', 35 | 'text-embedding-3-large', 36 | 'gpt-3.5-turbo', 37 | 'text-embedding-3-small', 38 | 'gpt-3.5-turbo-0613', 39 | 'text-embedding-ada-002', 40 | 'gpt-3.5-turbo-16k-0613', 41 | ], 42 | }; 43 | await pluginStore.set({ key: 'settings', value }); 44 | return pluginStore.get({ key: 'settings' }); 45 | }; 46 | const createConfigFromData = async (settings) => { 47 | const value = settings; 48 | const pluginStore = getPluginStore(); 49 | await pluginStore.set({ key: 'settings', value }); 50 | return pluginStore.get({ key: 'settings' }); 51 | }; 52 | const getSettings = async () => { 53 | const pluginStore = getPluginStore(); 54 | let config = await pluginStore.get({ key: 'settings' }); 55 | if (!config) { 56 | config = await createDefaultConfig(); 57 | } 58 | return config; 59 | }; 60 | const setSettings = async (data) => { 61 | return createConfigFromData(data); 62 | }; 63 | return { 64 | getSettings, 65 | setSettings, 66 | }; 67 | }; 68 | -------------------------------------------------------------------------------- /strapi-admin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./admin/src').default; 4 | -------------------------------------------------------------------------------- /strapi-server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./server'); 4 | --------------------------------------------------------------------------------