├── .gitignore ├── LICENSE ├── README.md ├── demo ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── index.html │ └── manifest.json ├── src │ ├── App.tsx │ ├── components │ │ ├── Api.tsx │ │ ├── Copywriting.tsx │ │ ├── CopywritingInput.tsx │ │ ├── CopywritingOutput.tsx │ │ ├── Storywriting.tsx │ │ ├── StorywritingInput.tsx │ │ └── StorywritingOutput.tsx │ ├── index.css │ ├── index.tsx │ ├── react-app-env.d.ts │ ├── reportWebVitals.ts │ └── setupTests.ts └── tsconfig.json ├── jest.config.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── components │ ├── Cell │ │ ├── Cell.stories.tsx │ │ ├── Cell.test.tsx │ │ ├── Cell.tsx │ │ ├── Cell.types.ts │ │ ├── CellBoard.stories.tsx │ │ ├── CellBoard.tsx │ │ ├── CellEditor.stories.tsx │ │ ├── CellEditor.tsx │ │ ├── CellTree.stories.tsx │ │ └── CellTree.tsx │ ├── Generator │ │ ├── Generator.stories.tsx │ │ ├── Generator.test.tsx │ │ ├── Generator.tsx │ │ └── Generator.types.ts │ └── Lens │ │ ├── Lens.stories.tsx │ │ ├── Lens.test.tsx │ │ ├── Lens.tsx │ │ ├── Lens.types.ts │ │ ├── ListLens.tsx │ │ ├── PlotLens.tsx │ │ ├── RatingLens.tsx │ │ └── SpaceLens.tsx ├── context │ ├── ObjectsContextProvider.stories.tsx │ ├── ObjectsContextProvider.test.tsx │ ├── ObjectsContextProvider.tsx │ └── Storywriting.stories.tsx ├── index.ts └── utils │ └── getLoremIpsum.tsx └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2023, KIXLAB 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LLM-UI-Objects Library 2 | 3 | ## Overview 4 | 5 | The LLM-UI-Objects library provides a set of object components that enable the creation of Large Language Model (LLM)-powered writing interfaces that support object-oriented interaction. Object-oriented interaction can enable users to more flexibly experiment and iterate with LLMs. The objects supported in the library are: **Cells** (input text fragments), **Generators** (sets of model parameters), and **Lenses** (output containers). This library is an instantiation of the [Cells, Generators, and Lenses](https://llm-objects.kixlab.org/) design framework that was presenteed at UIST 2023. 6 | 7 | ## Getting Started 8 | 9 | Install by executing `npm install llm-ui-objects`. 10 | Refer to directory `demo` for examples of how to import and use the components. 11 | 12 | ## Components 13 | 14 | Below is an overview of the different components available in the library. 15 | 16 | | Name | Description | Properties | 17 | | --------- | ----------- | ----------- | 18 | | `ObjectContextProvider` | Contains necessary context and information on the existing objects, their contents, and properties. Supports management of the objects by providing various helper functions that allow controlling, manipulating, and transferring information between these objects. | | 19 | | `Cell` | Represents a text fragment as an object. Cells can be connected to each other to represent a complete text input. | | 20 | | `Generator` | Represents a set of model parameter configurations and can be clicked on to generate outputs. Generators can be connected to cells to take its text (and text of all cells connected from that cell) as input. Generators can also be connected to lenses where its generation outputs will be contained and represented. | | 21 | | `Lens` | Represents the containers of generated outputs and can represent the outputs in various ways to support exploration of these outputs. Lenses can be connected together by assigning to the same group: lenses in the same group will show the same generated outputs. Examples of these representations are shown in the table below (e.g., `ListLens`, `SpaceLens`) | | 22 | 23 | For the **Cell** and **Lens** objects, we also provide a couple of examples of the different forms that these objects can take. 24 | 25 | | Name | Description | Properties | 26 | | --------- | ----------- | ----------- | 27 | | `CellBoard` | A board of cells where the user can create multiple rows of cells and multiple cells within these rows. The user selects individual cells in each row to compose an input. | | 28 | | `CellTree` | A tree representation of cells where parent-child relationships represent sentences that are continuations to each other. Each cells is minimized: they are represented by a rectangle containing the minimized text for the cell. | | 29 | | `CellEditor` | A QuillJS-based text editor that shows the contatenation of multiple selected cells and allows editing of individual cells directly on the editor. | | 30 | | `ListLens` | A lens that shows different outputs as a hierarchical list, where outputs are first grouped based on the input that was used to generate them and then grouped based on the model parameter settings used to generate them. | | 31 | | `SpaceLens` | A lens that shows different outputs as dots in a 2D space where their position of each output is specified by the `getPosition` function passed to the component. | | 32 | | `PlotLens` | A lens that shows different outputs as dots in a scatter plot where the user can select the axis for the plot, and the value of the dots for each axis are determined by the `getRatings` function passed to the component. | | 33 | | `RatingLens` | A lens that shows the ratings given to each output on multiple dimensions where the ratings of these outputs are determined by the `getRatings` function passed to the component | | 34 | 35 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # LLM UI Objects Demo 2 | 3 | This demo shows how the LLM UI Objects library can be used to create a copywriting and storywriting interface. 4 | 5 | ## Getting Started 6 | 7 | Install required libraries by using `npm install`. 8 | 9 | Then, run the app in development mode by ussing `npm run start`.\ 10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | In the interface, click on the key icon at the top right and input your OpenAI API key.\ 13 | Then, you can switch between the Copywriting and Storywriting interface to test the LLM UI objects. 14 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.17.0", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "@types/jest": "^27.5.2", 10 | "@types/node": "^16.18.59", 11 | "@types/react": "^18.2.31", 12 | "@types/react-dom": "^18.2.14", 13 | "llm-ui-objects": "^1.3.0", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0", 16 | "react-scripts": "5.0.1", 17 | "typescript": "^4.9.5", 18 | "web-vitals": "^2.1.4" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 16 | 17 | 26 | LLM UI Objects 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /demo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /demo/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | import { key } from './components/Api'; 5 | 6 | import Storywriting from './components/Storywriting'; 7 | import Copywriting from './components/Copywriting'; 8 | 9 | const KeySvg = 10 | 11 | 12 | 13 | ; 14 | 15 | const Container = styled.div` 16 | width: calc(100% - 64px); 17 | height: calc(100% - 48px); 18 | background-color: #eee; 19 | position: relative; 20 | z-index: 0; 21 | padding: 16px 32px 32px 32px; 22 | display: flex; 23 | flex-direction: column; 24 | `; 25 | 26 | const Header = styled.div` 27 | display: flex; 28 | flex-direction: row; 29 | justify-content: space-between; 30 | padding-bottom: 16px; 31 | `; 32 | 33 | const KeyContainer = styled.div` 34 | display: flex; 35 | flex-direction: row; 36 | gap: 4px; 37 | justify-content: flex-end; 38 | flex: 1; 39 | `; 40 | 41 | const KeyInput = styled.input` 42 | width: 200px; 43 | height: 32px; 44 | padding: 4px 8px; 45 | border: none; 46 | border-radius: 4px; 47 | font-size: 12px; 48 | box-sizing: border-box; 49 | `; 50 | 51 | const KeyButton = styled.div` 52 | width: 32px; 53 | height: 32px; 54 | margin-left: 4px; 55 | border-radius: 4px; 56 | cursor: pointer; 57 | background-color: #ccc; 58 | fill: #fff; 59 | box-sizing: border-box; 60 | display: flex; 61 | justify-content: center; 62 | align-items: center; 63 | `; 64 | 65 | const AppSwitcher = styled.div` 66 | display: flex; 67 | flex-direction: row; 68 | gap: 8px; 69 | flex: 1; 70 | `; 71 | 72 | const AppButton = styled.div<{selected?: boolean}>` 73 | width: 140px; 74 | height: 32px; 75 | border-radius: 4px; 76 | cursor: pointer; 77 | background-color: ${(props) => props.selected ? '#0088ff' : '#ccc'}; 78 | color: #fff; 79 | box-sizing: border-box; 80 | display: flex; 81 | justify-content: center; 82 | align-items: center; 83 | font-weight: bold; 84 | `; 85 | 86 | function App() { 87 | const [keyOpen, setKeyOpen] = React.useState(false); 88 | const [keyInput, setKeyInput] = React.useState(key.value); 89 | 90 | const [app, setApp] = React.useState('storywriting'); 91 | 92 | return ( 93 | setKeyOpen(false)}> 94 |
95 | 96 | setApp("storywriting")} 99 | > 100 | Storywriting 101 | 102 | setApp("copywriting")} 105 | > 106 | Copywriting 107 | 108 | 109 | 110 | { 113 | key.value = e.target.value; 114 | setKeyInput(e.target.value); 115 | }} 116 | onClick={(e: any) => e.stopPropagation()} 117 | style={{display: keyOpen ? 'block' : 'none'}} 118 | value={keyInput} 119 | type="password" 120 | /> 121 | { 123 | e.stopPropagation(); 124 | setKeyOpen(!keyOpen); 125 | }} 126 | > 127 | {KeySvg} 128 | 129 | 130 |
131 | 132 | 133 |
134 | ); 135 | } 136 | 137 | export default App; 138 | -------------------------------------------------------------------------------- /demo/src/components/Api.tsx: -------------------------------------------------------------------------------- 1 | import { GenerationProps } from "llm-ui-objects"; 2 | 3 | var key: { value: string } = { value: "" }; 4 | 5 | const generate = (system: string, input: string | string[], parameters: any) => { 6 | var url = "https://api.openai.com/v1/chat/completions"; 7 | var bearer = 'Bearer ' + key.value; 8 | 9 | if(typeof input === 'string') input = [input]; 10 | 11 | const promises: Promise[] = []; 12 | 13 | input.forEach((item) => { 14 | promises.push(new Promise((resolve, reject) => { 15 | fetch(url, { 16 | method: 'POST', 17 | headers: { 18 | 'Authorization': bearer, 19 | 'Content-Type': 'application/json' 20 | }, 21 | body: JSON.stringify({ 22 | ...parameters, 23 | "messages": [ 24 | { 25 | "role": "system", 26 | "content": system 27 | }, 28 | { 29 | "role": "user", 30 | "content": item 31 | } 32 | ] 33 | }) 34 | }) 35 | .then(response => response.json()) 36 | .then(data => { 37 | resolve(data.choices[0].message.content); 38 | }) 39 | .catch((error) => { 40 | reject(error); 41 | }); 42 | })); 43 | }); 44 | 45 | return Promise.all(promises); 46 | }; 47 | 48 | const getPositions = async (generations: GenerationProps[]) => { 49 | var url = "https://api.openai.com/v1/embeddings"; 50 | var bearer = 'Bearer ' + key.value; 51 | 52 | if(generations.length === 0) return Promise.resolve([]); 53 | 54 | const result = await fetch(url, { 55 | method: 'POST', 56 | headers: { 57 | 'Authorization': bearer, 58 | 'Content-Type': 'application/json' 59 | }, 60 | body: JSON.stringify({ 61 | "input": generations.map((generation) => generation.content), 62 | "model": "text-embedding-ada-002", 63 | "encoding_format": "float" 64 | }) 65 | }) 66 | .then(response => response.json()) 67 | .then(response => { 68 | var embeddingsList = response.data.map((item: any) => item.embedding); 69 | var positions = embeddingsList.map((item: any) => { 70 | var averageFirstHalf = item.slice(0, item.length / 2).reduce((a: any, b: any) => a + b) / (item.length / 2); 71 | var averageSecondHalf = item.slice(item.length / 2, item.length).reduce((a: any, b: any) => a + b) / (item.length / 2); 72 | return {x: averageFirstHalf, y: averageSecondHalf}; 73 | }); 74 | return positions; 75 | }) 76 | .catch((error) => { 77 | console.error(error); 78 | return []; 79 | }); 80 | 81 | return result; 82 | } 83 | 84 | const getRatings = (generations: GenerationProps[]) => { 85 | const prompt = `You are a helpful and precise assistant that can check the quality of writing by another AI assistant. You should evaluate how well a piece of writing satisfies a set of quality criteria. For each criterion, provide a one sentence explanation on how the writing satisfies the criterion and then provide a score out of 20 for that criterion. You should return your final answer as a valid JSON object. 86 | 87 | [The Start of Criteira] 88 | Creative: The writing is creative and original. It should include novel ideas and concepts. 89 | Simple: The writing is simple and easy to understand. It should be able to be understood by a wide audience. 90 | Positive: The writing is positive and uplifting. It should be able to make the reader feel good. 91 | Concise: The writing is concise and to the point. It should be able to convey its message in a short amount of time. 92 | Implicit: The writing is implicit and leaves room for interpretation. It should be able to be interpreted in multiple ways. 93 | [The End of Criteria] 94 | 95 | When returning your response, first output the token "$$$ANSWER$$$" and then output your answer as a valid JSON object of the following format: 96 | {"Creative": {"explanation": , "score": }, "Simple": {"explanation": , "score": }, "Positive": {"explanation": , "score": }, "Concise": {"explanation": , "score": }, "Implicit": {"explanation": , "score": }}`; 97 | 98 | return generate(prompt, generations.map((item) => item.content), { 99 | "model": "gpt-3.5-turbo", 100 | "temperature": 0.3, 101 | "max_tokens": 256 102 | }).then((response) => { 103 | var answers = response.map((item) => { 104 | var answer = JSON.parse(item.split("$$$ANSWER$$$")[1]); 105 | var ratings: {[key: string]: number} = {}; 106 | Object.keys(answer).forEach((key) => { 107 | ratings[key] = answer[key].score / 20; 108 | }); 109 | return ratings; 110 | }); 111 | return answers; 112 | }); 113 | } 114 | 115 | export { key, generate, getPositions, getRatings }; 116 | -------------------------------------------------------------------------------- /demo/src/components/Copywriting.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { ObjectsContextProvider, CellProps, GeneratorProps, ParameterProps, LensProps } from "llm-ui-objects"; 4 | 5 | import { generate } from "./Api" 6 | import CopywritingInput from "./CopywritingInput"; 7 | import CopywritingOutput from "./CopywritingOutput"; 8 | 9 | const defaultParameters: ParameterProps[] = [ 10 | { 11 | id: "model", 12 | name: "Model", 13 | value: "gpt-3.5-turbo", 14 | type: "nominal", 15 | allowedValues: ["gpt-3.5-turbo", "gpt-4", "gpt-3.5-turbo-0301", "gpt-4-0314"], 16 | valueNicknames: {"gpt-3.5-turbo": "3.5", "gpt-4": "4", "gpt-3.5-turbo-0301": "3.5M", "gpt-4-0314": "4M"}, 17 | defaultValue: "gpt-3.5-turbo" 18 | }, 19 | { 20 | id: "temperature", 21 | name: "Temperature", 22 | nickname: "temp", 23 | value: 0.7, 24 | type: "continuous", 25 | allowedValues: [0.0, 2.0], 26 | defaultValue: 1.0 27 | }, 28 | { 29 | id: "presence_penalty", 30 | name: "Presence Penalty", 31 | nickname: "present", 32 | value: 0.0, 33 | type: "continuous", 34 | allowedValues: [0.0, 2.0], 35 | defaultValue: 0.0 36 | }, 37 | { 38 | id: "frequency_penalty", 39 | name: "Frequency Penalty", 40 | nickname: "frequent", 41 | value: 0.0, 42 | type: "continuous", 43 | allowedValues: [0.0, 2.0], 44 | defaultValue: 0.0 45 | } 46 | ]; 47 | 48 | const defaultGenerators: GeneratorProps[] = [ 49 | { 50 | id: "generator-0", 51 | parameters: JSON.parse(JSON.stringify(defaultParameters)), 52 | color: "#0088ff", 53 | size: "medium", 54 | isGenerating: false, 55 | isSelected: false, 56 | cellId: null, 57 | lensId: "lens-0" 58 | } 59 | ]; 60 | 61 | const defaultLens: LensProps[] = [ 62 | { 63 | id: "lens-0", 64 | type: "list", 65 | group: 0 66 | }, 67 | { 68 | id: "lens-1", 69 | type: "rating", 70 | group: 0 71 | } 72 | ]; 73 | 74 | const generateAd = (input: string | string[], parameters: any) => { 75 | const systemPrompt = "You are a creative writing assistant. You will be given a couple of requirements for a creative advertisement, and you should create and advertisement that is at most 6 sentences long."; 76 | return generate(systemPrompt, input, parameters); 77 | } 78 | 79 | const Container = styled.div` 80 | height: 100%; 81 | display: flex; 82 | flex-direction: row; 83 | gap: 32px; 84 | `; 85 | 86 | const Copywriting = ({display}: {display: boolean}) => { 87 | return ( 88 | Promise} 92 | > 93 | 94 | 95 | 96 | 97 | 98 | ); 99 | } 100 | 101 | export default Copywriting; -------------------------------------------------------------------------------- /demo/src/components/CopywritingInput.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | 4 | import { ObjectsContext, CellBoard, Generator } from "llm-ui-objects"; 5 | 6 | const PlusSvg = ; 7 | 8 | const Container = styled.div` 9 | display: flex; 10 | flex-direction: column; 11 | gap: 8px; 12 | height: 100%; 13 | width: 40%; 14 | `; 15 | 16 | const GeneratorTray = styled.div` 17 | width: 100%; 18 | display: flex; 19 | flex-direction: column; 20 | gap: 8px; 21 | 22 | & > div:first-child { 23 | width: 100%; 24 | display: flex; 25 | flex-direction: row; 26 | gap: 8px; 27 | justify-content: center; 28 | align-items: center; 29 | } 30 | 31 | & > div:last-child { 32 | width: 100%; 33 | display: flex; 34 | justify-content: center; 35 | align-items: center; 36 | } 37 | `; 38 | 39 | const AddButton = styled.div` 40 | align-items: center; 41 | background-color: #0066ff; 42 | fill: #fff; 43 | border-radius: 4px; 44 | cursor: pointer; 45 | display: flex; 46 | justify-content: center; 47 | height: 40px; 48 | width: 40px; 49 | box-shadow: 0 2px 2px 0 rgba(0,0,0,0.2); 50 | `; 51 | 52 | const CopywritingInput = ({parameters}: {parameters: any}) => { 53 | const [entryCell, setEntryCell] = React.useState(null); 54 | const { generators, addGenerator, linkCellToGenerator } = React.useContext(ObjectsContext); 55 | 56 | React.useEffect(() => { 57 | if(!entryCell) return; 58 | generators.forEach((generator) => { 59 | if(generator.cellId === entryCell) return; 60 | linkCellToGenerator(entryCell, generator.id); 61 | }); 62 | }, [entryCell]); 63 | 64 | const handleAddGenerator = () => { 65 | addGenerator({ 66 | id: "placeholder", 67 | color: "#0088ff", 68 | size: "medium", 69 | parameters: JSON.parse(JSON.stringify(parameters)), 70 | cellId: entryCell, 71 | lensId: "lens-0" 72 | }); 73 | } 74 | 75 | return ( 76 | 77 | setEntryCell(cellId || null)} 87 | style={{ 88 | width: "100%", 89 | flex: "1", 90 | backgroundColor: "#fff", 91 | padding: "32px 24px", 92 | borderRadius: "8px", 93 | boxSizing: "border-box", 94 | boxShadow: "0px 4px 4px 0px rgba(0, 0, 0, 0.25)" 95 | }} 96 | /> 97 | 98 |
99 | {generators.map((generator) => ( 100 | 104 | ))} 105 |
106 |
107 | 108 | {PlusSvg} 109 | 110 |
111 |
112 |
113 | ) 114 | } 115 | 116 | export default CopywritingInput; -------------------------------------------------------------------------------- /demo/src/components/CopywritingOutput.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | 4 | import { ObjectsContext, Lens } from "llm-ui-objects"; 5 | 6 | import { getPositions, getRatings } from "./Api"; 7 | 8 | const ListSvg = ; 9 | const SpaceSvg = ; 10 | const PlotSvg = ; 11 | const TrashSvg = ; 12 | 13 | const Container = styled.div` 14 | display: flex; 15 | flex-direction: column; 16 | gap: 16px; 17 | height: 100%; 18 | width: 58%; 19 | `; 20 | 21 | const TextareaContainer = styled.div` 22 | flex: 1; 23 | width: 100%; 24 | 25 | & > textarea { 26 | width: 100%; 27 | height: 100%; 28 | border: none; 29 | border-radius: 8px; 30 | font-size: 18px; 31 | padding: 16px; 32 | resize: none; 33 | color: ${props => props.color}; 34 | background-color: #fff; 35 | overflow-y: auto; 36 | box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.1); 37 | outline: none; 38 | font-family: "Roboto", sans-serif; 39 | box-sizing: border-box; 40 | transition: color 0.5s ease; 41 | 42 | &::-webkit-scrollbar { 43 | width: 4px; 44 | } 45 | &::-webkit-scrollbar-track { 46 | background: #f1f1f1; 47 | border-radius: 4px; 48 | } 49 | &::-webkit-scrollbar-thumb { 50 | background: #ddd; 51 | border-radius: 4px; 52 | } 53 | } 54 | `; 55 | 56 | const LensesContainer = styled.div` 57 | width: 100%; 58 | display: flex; 59 | flex-direction: row; 60 | gap: 8px; 61 | flex: 1; 62 | `; 63 | 64 | const LensWrapper = styled.div` 65 | flex: 1; 66 | border: solid 2px #0088ff; 67 | border-radius: 8px; 68 | box-shadow: 0px 4px 4px 2px rgba(0, 0, 0, 0.2); 69 | padding: 8px; 70 | background-color: #fff; 71 | box-sizing: border-box; 72 | display: flex; 73 | flex-direction: column; 74 | gap: 4px; 75 | `; 76 | 77 | const LensStyle = { 78 | "width": "100%", 79 | "flex": "1 1 0", 80 | "overflow": "auto", 81 | "minHeight": "0px" 82 | }; 83 | 84 | const LensHeader = styled.div` 85 | width: 100%; 86 | display: flex; 87 | flex-direction: row; 88 | justify-content: space-between; 89 | 90 | & > div:nth-child(2) { 91 | display: flex; 92 | flex-direction: row; 93 | gap: 4px; 94 | } 95 | `; 96 | 97 | const LensButtonMin = styled.div` 98 | height: 24px; 99 | width: 24px; 100 | border-radius: 4px; 101 | cursor: pointer; 102 | display: flex; 103 | justify-content: center; 104 | align-items: center; 105 | background-color: #fff; 106 | border: solid 2px #0088ff; 107 | fill: #0088ff; 108 | 109 | & > svg { 110 | height: 16px; 111 | width: 16px; 112 | } 113 | `; 114 | 115 | 116 | const CopywritingOuput = () => { 117 | const { lenses, resetLens, changeLensType } = React.useContext(ObjectsContext); 118 | 119 | const [text, setText] = React.useState(""); 120 | const [textColor, setTextColor] = React.useState("#333333"); 121 | 122 | 123 | // setTextColor("#0066ff"); 124 | // setTimeout(() => { 125 | // setTextColor("#333333"); 126 | // }, 500); 127 | 128 | const handleGenerationClick = (generationText: string) => { 129 | setText(text + generationText); 130 | setTextColor("#0066ff"); 131 | setTimeout(() => { 132 | setTextColor("#333333"); 133 | }, 500); 134 | } 135 | 136 | return ( 137 | 138 | 139 |