mirror of
https://github.com/wassname/rag_search_cite.git
synced 2026-06-27 15:16:30 +08:00
init
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:react/jsx-runtime',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
|
||||
settings: { react: { version: '18.2' } },
|
||||
plugins: ['react-refresh'],
|
||||
rules: {
|
||||
'react/jsx-no-target-blank': 'off',
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
# Simple workflow for deploying static content to GitHub Pages
|
||||
name: Deploy static content to Pages
|
||||
|
||||
on:
|
||||
# Runs on pushes targeting the default branch
|
||||
push:
|
||||
branches: ['main']
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets the GITHUB_TOKEN permissions to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow one concurrent deployment
|
||||
concurrency:
|
||||
group: 'pages'
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# Single deploy job since we're just deploying
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'npm'
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Build
|
||||
run: npm run build
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v4
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
# Upload dist folder
|
||||
path: './dist'
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*log.md
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": []
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# rag_search_cite
|
||||
|
||||
rag_search_cite: Hackable frontend only LLM assisted searching with citations
|
||||
|
||||
|
||||
# demo
|
||||
|
||||
~~[demo](https://wassname.github.io/word_level_diff_writing_assistant/)~~
|
||||
|
||||
~~~~
|
||||
|
||||
# run
|
||||
|
||||
```npm
|
||||
npm run dev
|
||||
```
|
||||
|
||||
# notes
|
||||
|
||||
- for editor styling
|
||||
- <https://flowbite.com/docs/forms/textarea/#wysiwyg-editor>
|
||||
- <https://tailwindcss.com/docs/dark-mode>
|
||||
- deploy: <https://vitejs.dev/guide/static-deploy#github-pages>
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12.5 6.75a.75.75 0 00-1.5 0V9H8.75a.75.75 0 000 1.5H11v2.25a.75.75 0 001.5 0V10.5h2.25a.75.75 0 000-1.5H12.5V6.75zM8.75 16a.75.75 0 000 1.5h6a.75.75 0 000-1.5h-6z"/><path fill-rule="evenodd" d="M5 1a2 2 0 00-2 2v18a2 2 0 002 2h14a2 2 0 002-2V7.018a2 2 0 00-.586-1.414l-4.018-4.018A2 2 0 0014.982 1H5zm-.5 2a.5.5 0 01.5-.5h9.982a.5.5 0 01.354.146l4.018 4.018a.5.5 0 01.146.354V21a.5.5 0 01-.5.5H5a.5.5 0 01-.5-.5V3z"/></svg>
|
||||
|
After Width: | Height: | Size: 656 B |
Binary file not shown.
|
After Width: | Height: | Size: 339 KiB |
+31
@@ -0,0 +1,31 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/logo.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>rag_search_cite</title>
|
||||
|
||||
<!-- this is so we can use a localhost openai like api -->
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="*' 'unsafe-inline' 'unsafe-eval';" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="flex min-h-screen flex-col items-center">
|
||||
<div id="root"></div>
|
||||
</div>
|
||||
<footer class="mx-auto w-full max-w-container px-4 sm:px-6 lg:px-8">
|
||||
<div class="border-t border-slate-900/5 py-10 text-center">
|
||||
rag_search_cite: Hackable frontend only LLM assisted searching with citations. Made by <a href="https://wassname.com">wassname</a>.
|
||||
<a href="https://github.com/wassname/rag_search_cite.git">GitHub repo</a>
|
||||
|
||||
</div>
|
||||
</footer>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Generated
+5158
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "rag-search-cite",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.3.3",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"markdown-it": "^14.1.0",
|
||||
"openai": "^4.36.0",
|
||||
"react": "^18.2.0",
|
||||
"react-bootstrap": "^2.10.2",
|
||||
"react-select": "^5.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.66",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-react": "^7.34.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.6",
|
||||
"vite": "^5.2.0"
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
|
||||
|
||||
.diff-result {
|
||||
text-wrap: balance;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.Difference {
|
||||
font-family: monospace;
|
||||
text-wrap: balance;
|
||||
text-align: left;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.Difference > del {
|
||||
background-color: rgb(255, 224, 224);
|
||||
text-decoration: none;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.Difference > ins {
|
||||
background-color: rgb(201, 238, 211);
|
||||
text-decoration: none;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
import { useState } from 'react'
|
||||
import './App.css'
|
||||
|
||||
import SelectPrompt from './components/SelectPrompt';
|
||||
import Editor from './components/Editor'
|
||||
// import Diff from './components/Diff2';
|
||||
// import Select from 'react-select'
|
||||
import Container from 'react-bootstrap/Container';
|
||||
import Row from 'react-bootstrap/Row';
|
||||
import Col from 'react-bootstrap/Col';
|
||||
import Button from 'react-bootstrap/Button';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
import Alert from 'react-bootstrap/Alert';
|
||||
import CreatableSelect from 'react-select/creatable';
|
||||
import FloatingLabel from 'react-bootstrap/FloatingLabel';
|
||||
// import Select from 'react-select/dist/declarations/src/Select';
|
||||
import defaultPrompts from './helpers/defaultPrompts'
|
||||
import InputLocalStorageCached from './components/InputLocalStorageCached';
|
||||
import sendOpenAIMessage from './helpers/openaiMessage';
|
||||
import search from './helpers/rag';
|
||||
import Citations from './components/Citations';
|
||||
|
||||
const model_options = [
|
||||
// https://platform.openai.com/docs/models/continuous-model-upgrades
|
||||
'gpt-3.5-turbo',
|
||||
'gpt-4',
|
||||
'gpt-4-turbo',
|
||||
// 'text-moderation-stable',
|
||||
'gpt-4o',
|
||||
].map((model) => ({ value: model, label: model }))
|
||||
|
||||
|
||||
function Progress({ data }) {
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
const { status, error, loading } = data
|
||||
|
||||
if (status) {
|
||||
return <Alert variant="info">{status}</Alert>
|
||||
}
|
||||
if (!error || error === null) {
|
||||
return null;
|
||||
}
|
||||
return <Alert variant="warning">Error: {error}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
|
||||
|
||||
function App() {
|
||||
const [count, setCount] = useState(0)
|
||||
const [input, setInput] = useState(`What kinds of computational neuroscience techniques could be used in MechInterp?.`)
|
||||
const [prompt, setPrompt] = useState(defaultPrompts[0])
|
||||
const [reply, setReply] = useState(`"**Post-Hoc Interpretability Techniques**: [1, 2, 3] These techniques are applied after the model has been trained to gain insights into its behavior and decision-making processes. They include efforts to uncover general,[2, 4] transferable principles across models and tasks, as well as automating the discovery and interpretation of critical circuits in trained models [4].".`)
|
||||
const [status, setStatus] = useState(null)
|
||||
const [docs, setDocs] = useState([
|
||||
{ name: 'name', url: 'localhost', content: 'c1', rank: 2, q: '1', source: 'bing' },
|
||||
{ name: 'name', url: 'localhost', content: 'c2', rank: 5 },
|
||||
{ name: 'name', url: 'localhost', content: 'c3', rank: 10 },
|
||||
{ name: 'name', url: 'localhost', content: 'c4', rank: 7 },
|
||||
])
|
||||
const [openaiModel, setOpenAIModel] = useState('gpt-4')
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Row>
|
||||
<Col sm={4}>
|
||||
<Row>
|
||||
|
||||
<SelectPrompt value={prompt} onChange={setPrompt} />
|
||||
</Row><Row>
|
||||
|
||||
|
||||
<Form>
|
||||
<FloatingLabel label="OPENAI_KEY:"
|
||||
>
|
||||
<InputLocalStorageCached storageKey="OPENAI_KEY" as={Form.Control} type="password" required />
|
||||
</FloatingLabel>
|
||||
</Form>
|
||||
</Row><Row>
|
||||
<Form>
|
||||
<FloatingLabel label="BASE_URL:"
|
||||
>
|
||||
<InputLocalStorageCached storageKey="OPENAI_BASE_URL" default="https://api.openai.com/v1" as={Form.Control} type="text" />
|
||||
</FloatingLabel>
|
||||
</Form>
|
||||
|
||||
</Row><Row>
|
||||
|
||||
<Form>
|
||||
<Form.Label>OPENAI_MODEL:</Form.Label>
|
||||
<CreatableSelect
|
||||
options={model_options}
|
||||
creatable={true}
|
||||
defaultValue={model_options[0]}
|
||||
onChange={(s) => setOpenAIModel(s.label)}
|
||||
required
|
||||
/>
|
||||
|
||||
</Form>
|
||||
</Row><Row>
|
||||
<Form>
|
||||
<FloatingLabel label="Temperature:">
|
||||
<InputLocalStorageCached storageKey="OPENAI_TEMP" as={Form.Control} type="number" step="0.1" defaultValue="1"
|
||||
min="0"
|
||||
max="2" />
|
||||
</FloatingLabel>
|
||||
</Form>
|
||||
|
||||
|
||||
</Row><Row>
|
||||
<Form>
|
||||
<FloatingLabel label="BING_SEARCH_KEY:" title="https://www.microsoft.com/en-us/bing/apis/bing-web-search-api">
|
||||
<InputLocalStorageCached storageKey="BING_SEARCH_KEY" as={Form.Control} type="password" required />
|
||||
</FloatingLabel>
|
||||
</Form>
|
||||
</Row>
|
||||
|
||||
|
||||
</Col>
|
||||
<Col sm={8}>
|
||||
<Row>
|
||||
<Form onSubmit={e => e.preventDefault()}>
|
||||
<Editor name="Search" value={input} onChange={(e) => setInput(e.target.value)} />
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => setCount(() => search(input, openaiModel, setReply, setStatus, setDocs))}>
|
||||
Submit
|
||||
</Button>
|
||||
<Progress data={status} />
|
||||
</Form>
|
||||
</Row>
|
||||
<Row>
|
||||
<Form>
|
||||
<Citations name="Answer" value={reply} docs={docs}/>
|
||||
</Form>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
@@ -0,0 +1,71 @@
|
||||
import Container from 'react-bootstrap/Container';
|
||||
import Row from 'react-bootstrap/Row';
|
||||
import Col from 'react-bootstrap/Col';
|
||||
import Button from 'react-bootstrap/Button';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
import FloatingLabel from 'react-bootstrap/FloatingLabel';
|
||||
import Alert from 'react-bootstrap/Alert';
|
||||
// import dangerouslySetInnerHTML from 'react';
|
||||
import markdownIt from 'markdown-it';
|
||||
import { escapeHtml } from "markdown-it/lib/common/utils.mjs";
|
||||
const markdowner = new markdownIt();
|
||||
|
||||
// function escapeHtml(text) {
|
||||
// return text
|
||||
// .replace(/&/g, '&')
|
||||
// .replace(/</g, '<')
|
||||
// .replace(/>/g, '>')
|
||||
// .replace(/"/g, '"')
|
||||
// .replace(/'/g, ''');
|
||||
// }
|
||||
|
||||
function doc2htmlref(d, n) {
|
||||
const tooltip = escapeHtml(`${d.name}:\n\n${d.content}\n\n${d.url}`).trim();
|
||||
return `<a href="${d.url}"><span title="${tooltip}">${n}</span></a>`;
|
||||
}
|
||||
|
||||
function formatAns(ans, docs) {
|
||||
// Convert markdown to HTML
|
||||
ans = escapeHtml(ans);
|
||||
ans = markdowner.render(ans);
|
||||
let text2 = '<h3>Answer</h3>' + ans;
|
||||
|
||||
// Convert [1], [1,3] etc to references with tooltips
|
||||
const pattern = /\[\s*((?:\d+\s*(?:,\s*\d+\s*)*)?)\]/g;
|
||||
let matches = [...text2.matchAll(pattern)].reverse();
|
||||
for (const match of matches) {
|
||||
const m = match[0].replace('[', '').replace(']', '');
|
||||
const ns = m.split(', ');
|
||||
let m2 = '';
|
||||
for (const n of ns) {
|
||||
const d = docs[parseInt(n, 10)-1];
|
||||
m2 += `${doc2htmlref(d, n)}, `;
|
||||
}
|
||||
m2 = `[${m2.slice(0, -2)}]`;
|
||||
const [s0, s1] = [match.index, match.index + match[0].length];
|
||||
text2 = text2.slice(0, s0) + m2 + text2.slice(s1);
|
||||
}
|
||||
|
||||
// Turn matches into a list of integers for used references
|
||||
const refs = matches.map(m => m[0].replace('[', '').replace(']', '').trim().split(', ')).flat();
|
||||
const uniqueRefs = [...new Set(refs.map(m => parseInt(m, 10)))].sort((a, b) => a - b);
|
||||
|
||||
// HTML list references
|
||||
text2 += "<p/><p/><h3>References:</h3><p/>";
|
||||
for (const r of uniqueRefs) {
|
||||
const d = docs[r-1];
|
||||
text2 += `<li><a href="${d.url}">[${r}]</a>: ${escapeHtml(d.name)} - ${escapeHtml(d.content)}</li>`;
|
||||
}
|
||||
|
||||
return text2;
|
||||
}
|
||||
|
||||
export default function Citation({ name, value, docs }) {
|
||||
|
||||
value = formatAns(value, docs)
|
||||
|
||||
return <Form.Group>
|
||||
<Form.Label htmlFor={name} label={name} />
|
||||
<div dangerouslySetInnerHTML={{ __html: value }}></div>
|
||||
</Form.Group>
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import * as jsdiff from 'diff-match-patch';
|
||||
|
||||
/**
|
||||
* Convert a diff array into a pretty HTML report.
|
||||
* @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
|
||||
* @return {string} HTML representation.
|
||||
*/
|
||||
function diff2react (diffs) {
|
||||
const text = diffs.map(
|
||||
([op, data], i) => {
|
||||
switch (op) {
|
||||
case jsdiff.DIFF_INSERT:
|
||||
return <ins key={i}>{data}</ins>;
|
||||
case jsdiff.DIFF_DELETE:
|
||||
return <del key={i} >{data}</del>;
|
||||
case jsdiff.DIFF_EQUAL:
|
||||
return <span key={i}>{data}</span>;
|
||||
}
|
||||
}
|
||||
)
|
||||
return <pre className='Difference'>{text}</pre>
|
||||
};
|
||||
|
||||
// function diff2md (diffs) {
|
||||
// const text = diffs.map(
|
||||
// ([op, data]) => {
|
||||
// if (op in [jsdiff.DIFF_INSERT, jsdiff.DIFF_DELETE]) {
|
||||
// if (data.trim().length > 0) {
|
||||
// data = data.trim()
|
||||
// }
|
||||
// }
|
||||
// switch (op) {
|
||||
// case jsdiff.DIFF_INSERT:
|
||||
// return `<ins>${data}</ins>`;
|
||||
// case jsdiff.DIFF_DELETE:
|
||||
// return `<del>${data}</del>`;
|
||||
// case jsdiff.DIFF_EQUAL:
|
||||
// return data
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
// return text.join('');
|
||||
// };
|
||||
|
||||
// function patchmd2 (patches) {
|
||||
// const text = patches.map(r=>r.diffs).map(diff2md)
|
||||
// return text.map((s, i)=>`- [${i}]: \`${s}\``).join('\n');
|
||||
// };
|
||||
const reverseString = str => [...str].reverse().join('');
|
||||
|
||||
export default function Diff(props) {
|
||||
let dmp = new jsdiff.diff_match_patch();
|
||||
// I want to match from the end, as this work best wit Chain Of Thought
|
||||
let diff = dmp.diff_main(reverseString(props.inputA), reverseString(props.inputB));
|
||||
// reverse arrays and contents
|
||||
diff = diff.reverse().map(([op, data]) => [op, reverseString(data)]);
|
||||
dmp.diff_cleanupSemantic(diff);
|
||||
// const patches = dmp.patch_make(props.inputA, diff)
|
||||
// const patch2 = patchmd2(patches)
|
||||
// console.log(patch2)
|
||||
return diff2react(diff);
|
||||
// const result = diff_prettyHtml(diff);
|
||||
// return <div className={props.className}>{result}</div>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import Container from 'react-bootstrap/Container';
|
||||
import Row from 'react-bootstrap/Row';
|
||||
import Col from 'react-bootstrap/Col';
|
||||
import Button from 'react-bootstrap/Button';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
import FloatingLabel from 'react-bootstrap/FloatingLabel';
|
||||
import Alert from 'react-bootstrap/Alert';
|
||||
|
||||
export default function Editor({ name, value, onChange, rows }) {
|
||||
return <Form.Group>
|
||||
{/* <FloatingLabel htmlFor={name} label={name}>
|
||||
<Form.Control as="textarea"
|
||||
id={name}
|
||||
rows={rows || 8}
|
||||
type="text"
|
||||
value={value}
|
||||
placeholder={name}
|
||||
onChange={onChange}
|
||||
required
|
||||
/></FloatingLabel> */}
|
||||
|
||||
<Form.Label htmlFor={name} label={name}><h4>{name}</h4></Form.Label>
|
||||
<Form.Control as="textarea"
|
||||
id={name} rows={rows || 8}
|
||||
type="text"
|
||||
value={value}
|
||||
placeholder={name}
|
||||
onChange={onChange}
|
||||
required
|
||||
/>
|
||||
</Form.Group>
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import React, { useState, useEffect, useImperativeHandle } from 'react';
|
||||
|
||||
/** This is an input box that saves the input value to localStorage */
|
||||
const InputLocalStorageCached = ({ storageKey: storageKey, defaultValue, as: Wrapper, ...props}) => {
|
||||
const [value, setValue] = useState(() => {
|
||||
let v = window.localStorage.getItem(storageKey) || defaultValue
|
||||
// v = JSON.parse(v)
|
||||
return v;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const storedValue = window.localStorage.getItem(storageKey);
|
||||
if (storedValue !== null) {
|
||||
setValue(storedValue);
|
||||
}
|
||||
}, [storageKey]);
|
||||
|
||||
const handleChange = (event) => {
|
||||
setValue(event.target.value);
|
||||
let v = event.target.value
|
||||
// v = JSON.stringify(event.target.value)
|
||||
window.localStorage.setItem(storageKey, v);
|
||||
};
|
||||
|
||||
useImperativeHandle(props.ref, () => ({
|
||||
value,
|
||||
onChange: handleChange,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Wrapper {...props} value={value} onChange={handleChange} />
|
||||
);
|
||||
};
|
||||
|
||||
export default InputLocalStorageCached;
|
||||
@@ -0,0 +1,95 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Editor from './Editor'
|
||||
import defaultPrompts from '../helpers/defaultPrompts'
|
||||
import Select from 'react-select'
|
||||
import Col from 'react-bootstrap/Col';
|
||||
import Button from 'react-bootstrap/Button';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
|
||||
/** This component allows the user to select a previous prompt from a dropdown list. */
|
||||
const SelectPrompt = ({ onChange }) => {
|
||||
const [prompts, setPrompts] = useState(defaultPrompts);
|
||||
const [currentPrompt, setCurrentPrompt] = useState(defaultPrompts[0]);
|
||||
const [selectedIndex, setSelectedIndex] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const savedPrompts = localStorage.getItem('prompts');
|
||||
if (savedPrompts) {
|
||||
setPrompts(JSON.parse(savedPrompts));
|
||||
}
|
||||
}, []);
|
||||
|
||||
const savePrompt = () => {
|
||||
let newPrompts;
|
||||
if (selectedIndex !== null) {
|
||||
newPrompts = [...prompts];
|
||||
newPrompts[selectedIndex] = currentPrompt;
|
||||
} else {
|
||||
newPrompts = [...prompts, currentPrompt];
|
||||
}
|
||||
setPrompts(newPrompts);
|
||||
localStorage.setItem('prompts', JSON.stringify(newPrompts));
|
||||
};
|
||||
|
||||
const handleSelectChange = (event) => {
|
||||
setCurrentPrompt(event.label);
|
||||
onChange(event.label);
|
||||
setSelectedIndex(prompts.indexOf(event.label));
|
||||
};
|
||||
|
||||
const handleEditorChange = (event) => {
|
||||
setCurrentPrompt(event.label);
|
||||
onChange(event.label);
|
||||
};
|
||||
|
||||
const deletePrompt = () => {
|
||||
const newPrompts = prompts.filter((_, index) => index !== selectedIndex);
|
||||
setPrompts(newPrompts);
|
||||
localStorage.setItem('prompts', JSON.stringify(newPrompts));
|
||||
setCurrentPrompt('');
|
||||
onChange('');
|
||||
setSelectedIndex(null);
|
||||
};
|
||||
|
||||
const options = prompts.map((prompt) => ({ value: prompt, label: prompt }));
|
||||
const value = prompts.indexOf(currentPrompt);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form>
|
||||
<Form.Group>
|
||||
<Form.Label htmlFor="countries">Select a previous prompt</Form.Label>
|
||||
<Select
|
||||
options={options}
|
||||
onChange={handleSelectChange}
|
||||
value={value}
|
||||
/>
|
||||
</Form.Group>
|
||||
{/* <Label htmlFor="countries" >Select a previous prompt</Label>
|
||||
<Select
|
||||
|
||||
value={currentPrompt}
|
||||
onChange={handleSelectChange}
|
||||
>
|
||||
{prompts.map((prompt, index) => (
|
||||
<option key={index} value={prompt}>
|
||||
{prompt}
|
||||
</option>
|
||||
))}
|
||||
</Select> */}
|
||||
<Form.Group>
|
||||
<Editor name="Edit prompt" value={currentPrompt} onChange={handleEditorChange} rows="12" />
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={savePrompt}>Save</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={deletePrompt}
|
||||
>Delete</Button>
|
||||
</Form.Group>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectPrompt;
|
||||
@@ -0,0 +1,37 @@
|
||||
async function bingWebSearch(query) {
|
||||
const SUBSCRIPTION_KEY = window.localStorage.getItem('BING_SEARCH_KEY');
|
||||
const url = `https://api.bing.microsoft.com/v7.0/search?q=${encodeURIComponent(query)}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET', // HTTP method
|
||||
headers: {
|
||||
'Ocp-Apim-Subscription-Key': SUBSCRIPTION_KEY,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('\nJSON Response:\n');
|
||||
console.dir(data, { colors: false, depth: null });
|
||||
|
||||
// Process headers
|
||||
response.headers.forEach((value, name) => {
|
||||
if (name.startsWith("bingapis-") || name.startsWith("x-msedge-")) {
|
||||
console.log(`${name}: ${value}`);
|
||||
}
|
||||
});
|
||||
|
||||
let docs = data.webPages.value.map((wp, index) => {
|
||||
return { name:wp.name, url:wp.url, content: wp.snippet, query, source: 'bing' }
|
||||
})
|
||||
return docs
|
||||
} catch (e) {
|
||||
console.log('Error: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export default bingWebSearch;
|
||||
@@ -0,0 +1,53 @@
|
||||
const defaultPrompts = [
|
||||
`If there are any grammar or spelling mistakes in this writing, fix them`, // continue.sh
|
||||
"I want you to act as a proofreader. I will provide you texts and would like you to review them for any spelling, grammar, or punctuation errors. Keep the original facts, meaning, tone, and style.",
|
||||
"I want you to act as a copy editor. I will provide you with texts and I would like you to review them for any spelling, grammar, or punctuation errors.", // https://reorx.com/makers-daily/003-chatgpt-proofreader-extension-popclip/
|
||||
"As a copy editor check the following content carefully for possible diction and grammar problems, and polish it carefully. Keep the original facts, meaning, tone, and style. ",
|
||||
"As a copy editor, revise this document, making only changes to the spelling and grammar",
|
||||
"Revise the following sentences to make them more clear, concise, and coherent.",
|
||||
`You aspire to be open to nuance, make complex topics accessible with wit and storytelling, present contrarian viewpoints confidently, and simplify complex ideas without dumbing them down. You employ straightforward phrasing, pose rhetorical questions to challenge assumptions. You employ analogies for complex ideas and communicate efficiently with brevity and wit.
|
||||
|
||||
With that context, please revise the following draft messages to make them more clear, concise, and coherent. Make sure you keep at the key ideas.`,
|
||||
`Rewrite the message to fit my concise, business-chat style, aimed at a general audience. The tone should be engaging but professional. Make sure to retain all essential content. For guidance, transform 'The project is completed and we should discuss the financials' into 'Hey team, wrapped up Project X. Budget looks good. Discuss details Tuesday.'`,
|
||||
"You are a senior editor as one of the world most read newspapers. For the following essay help your friend, an engineer, by acting as a copy editor to improve the quality and readability of their essay. Help with the phrasing, and grammar while reamaining true to your friends style, tone, and message",
|
||||
`You are an AI copy editor with a keen eye for detail and a deep understanding of language, style, and grammar. Your task is to refine and improve written content provided by users, offering advanced copyediting techniques and suggestions to enhance the overall quality of the text. When a user submits a piece of writing, follow these steps:
|
||||
|
||||
1. Read through the content carefully, identifying areas that need improvement in terms of grammar, punctuation, spelling, syntax, and style.
|
||||
|
||||
2. Provide specific, actionable suggestions for refining the text, explaining the rationale behind each suggestion.
|
||||
|
||||
3. Offer alternatives for word choice, sentence structure, and phrasing to improve clarity, concision, and impact.
|
||||
|
||||
4. Ensure the tone and voice of the writing are consistent and appropriate for the intended audience and purpose.
|
||||
|
||||
5. Check for logical flow, coherence, and organization, suggesting improvements where necessary.
|
||||
|
||||
6. Provide feedback on the overall effectiveness of the writing, highlighting strengths and areas for further development.
|
||||
|
||||
7. Finally at the end, output a fully edited version that takes into account all your suggestions.
|
||||
|
||||
Your suggestions should be constructive, insightful, and designed to help the user elevate the quality of their writing.`, // from Anthropic prompt library
|
||||
"turn this draft into the style of Scott Alexander, suitable for his published material",
|
||||
"Make this better, in the style of a gwern, or add more vivid examples.", // https://www.oneusefulthing.org/p/i-cyborg-using-co-intelligence
|
||||
`Welcome to https://www.lesswrong.com/ A community blog devoted to refining the art of rationality Frontpage comment guidelines:
|
||||
|
||||
- Aim to explain, not persuade
|
||||
- Try to offer concrete models and predictions
|
||||
- If you disagree, try getting curious about what your partner is thinking
|
||||
- Don't be afraid to say 'oops' and change your mind
|
||||
|
||||
eWelcome to r/slatestarcodex Companion subreddit for Slate Star Codex, now called Astral Codex Ten. New blog posts will be posted here. Community guidelines
|
||||
|
||||
See the Victorian Sufi Buddha Lite comment policy: comments should be at least two of {true, necessary, kind}.
|
||||
|
||||
- Be kind. Failing that, bring evidence.
|
||||
- Be charitable. Assume the people you're talking to or about have thought through the issues you're discussing, and try to represent their views in a way they would recognize.
|
||||
- When making a claim that isn't outright obvious, you should proactively provide evidence in proportion to how partisan and inflammatory your claim might be.
|
||||
- Don't be egregiously obnoxious.
|
||||
- Put research, care, and effort into your posts and comments. Quick gotchas, snipes, and jabs are looked down upon here.
|
||||
- Culture war topics are forbidden.
|
||||
|
||||
Given the above guidelines, please revise the following draft messages to make them more clear, concise, and coherent. Make sure you keep at the key ideas.`,
|
||||
`you think ChatGPT is a "lazy, lying, moralist midwit". Everything it writes is full of nauseating cliche and it frequently refuses to do something you know it can do....human's gets to the point more quickly. With that said please act as copyeditor to improve the following writing`
|
||||
]
|
||||
export default defaultPrompts
|
||||
@@ -0,0 +1,72 @@
|
||||
|
||||
import OpenAI from 'openai';
|
||||
|
||||
function configureOpenAI() {
|
||||
return new OpenAI({
|
||||
apiKey: localStorage.getItem('OPENAI_KEY'),
|
||||
baseURL: localStorage.getItem('OPENAI_BASE_URL'),
|
||||
dangerouslyAllowBrowser: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// /** send to openai */
|
||||
// function sendOpenAIMessage(model, messages, callback, onStatus) {
|
||||
// let openai = configureOpenAI()
|
||||
// console.debug("sending", messages);
|
||||
// if (!onStatus) { onStatus = () => {}; }
|
||||
// onStatus({ status: "Loading" })
|
||||
// // https://github.com/openai/openai-node/blob/master/src/resources/chat/completions.ts#L796C3-L796C14
|
||||
// const chatCompletion = openai.chat.completions.create({
|
||||
// messages: messages,
|
||||
// model: model,
|
||||
// temperature: parseFloat(localStorage.getItem('OPENAI_TEMP', 1)),
|
||||
// });
|
||||
// chatCompletion.then((m) => {
|
||||
// console.debug(m)
|
||||
// callback(m['choices'][0]['message']['content'])
|
||||
// onStatus({})
|
||||
// }).catch(async (err) => {
|
||||
// if (err instanceof OpenAI.APIError) {
|
||||
// console.log(err.status); // 400
|
||||
// console.log(err.name); // BadRequestError
|
||||
// console.log(err.headers); // {server: 'nginx', ...}
|
||||
// if (onStatus) { onStatus({ error: `${err.status}: ${err.message}` }) }
|
||||
// } else {
|
||||
// onStatus({ error: JSON.stringify(err) })
|
||||
// throw err;
|
||||
// }
|
||||
// });
|
||||
|
||||
// }
|
||||
|
||||
function sendOpenAIMessage(model, messages, onStatus) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let openai = configureOpenAI();
|
||||
console.debug("sending", messages);
|
||||
if (!onStatus) { onStatus = () => {}; }
|
||||
onStatus({ status: "Loading" });
|
||||
const chatCompletion = openai.chat.completions.create({
|
||||
messages: messages,
|
||||
model: model,
|
||||
temperature: parseFloat(localStorage.getItem('OPENAI_TEMP') || '1'),
|
||||
});
|
||||
chatCompletion.then((m) => {
|
||||
console.debug(m);
|
||||
resolve(m['choices'][0]['message']['content']);
|
||||
onStatus({});
|
||||
}).catch((err) => {
|
||||
if (err instanceof OpenAI.APIError) {
|
||||
console.log(err.status); // 400
|
||||
console.log(err.name); // BadRequestError
|
||||
console.log(err.headers); // {server: 'nginx', ...}
|
||||
if (onStatus) { onStatus({ error: `${err.status}: ${err.message}` }) }
|
||||
} else {
|
||||
onStatus({ error: JSON.stringify(err) });
|
||||
}
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default sendOpenAIMessage
|
||||
@@ -0,0 +1,141 @@
|
||||
import sendOpenAIMessage from "./openaiMessage";
|
||||
import bingWebSearch from "./bingSearch";
|
||||
import { escapeHtml } from "markdown-it/lib/common/utils.mjs";
|
||||
|
||||
const defaultModel = 'gpt-3.5-turbo'
|
||||
|
||||
function rephrase(q, model = defaultModel) {
|
||||
let messages = [
|
||||
{ role: "system", content: `You are LibrarianGPT an intelligence assisant that can improve users searching, helping them find documents they missed. Please return only the search and no commentary.` },
|
||||
{ role: "user", content: `Please draft an academic search query with synonyms and alternative phrases that will find documents to answer the following question: \"${q}\". Return only the search and no commentary.` },
|
||||
]
|
||||
let r = sendOpenAIMessage(
|
||||
model,
|
||||
messages)
|
||||
return r
|
||||
|
||||
}
|
||||
|
||||
function exampleAnswer(q, model = defaultModel) {
|
||||
let messages = [
|
||||
{ role: "system", content: `You are LibrarianGPT an intelligence assisant that can improve users searching by providing example answers that will help with vector based similarity search. Please return only the example and no commentary.` },
|
||||
{ role: "user", content: `Please draft a concrete and concise example answer that ties together all elements of the following question in a paragraph or less: \"${q}\". Return only the example and no commentary.` },
|
||||
]
|
||||
let r = sendOpenAIMessage(model, messages)
|
||||
return r
|
||||
}
|
||||
|
||||
function shuffleArray(array) {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]]; // ES6 swap
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
function rank(query, r1, r2, r3, model = defaultModel) {
|
||||
let docs = [...r1, ...r2, ...r3];
|
||||
|
||||
// deduplicate docs
|
||||
shuffleArray([...new Set(docs)])
|
||||
|
||||
let num = docs.length;
|
||||
let messages = [
|
||||
{ role: "system", content: `You are RankGPT, an intelligent assistant that can rank passages based on their relevancy to the query.` },
|
||||
{ role: "user", content: `I will provide you with ${num} passages, each indicated by number identifier []. \nRank the passages based on their relevance to query: ${query}` },
|
||||
{ role: "assistant", content: `Okay, please provide the ${num} passages.` },
|
||||
]
|
||||
for (let i = 0; i < num; i++) {
|
||||
let d = docs[i]
|
||||
messages.push({ role: "user", content: `[${i + 1}]. ${d.name} ${d.content} ${d.url}` })
|
||||
messages.push({ role: "assistant", content: `Received passage [${i + 1}].` })
|
||||
}
|
||||
let example_ordering = "[2] > [1]"
|
||||
messages.push({ role: "user", content: `Search Query: ${query}.\nRank the ${num} passages above based on their relevance to the search query. All the passages should be included and listed using identifiers, in descending order of relevance. The output format should be [] > [], e.g., ${example_ordering}, Only respond with the ranking results, do not say any word or explain.` })
|
||||
|
||||
let r = sendOpenAIMessage(model, messages)
|
||||
.then(r => {
|
||||
if (!r.includes('>')) {
|
||||
return Promise.reject(`Instead of ranking we got this ${r}.`)
|
||||
}
|
||||
return r.split(' > ').map(s => s.trim().replace('[', '').replace(']', '')).map(s => parseInt(s)).filter(s => !isNaN(s)).reverse()
|
||||
})
|
||||
.then(ranks => {
|
||||
// TODO check that the list if complete
|
||||
if (ranks.length != num) {
|
||||
return Promise.reject(`The number of provided passages does not match the number of expected passages. ${ranks.length}!=${num}`)
|
||||
}
|
||||
|
||||
|
||||
|
||||
let rankedDocs = []
|
||||
for (let i in ranks) {
|
||||
let doc = docs[ranks[i] - 1]
|
||||
rankedDocs.push({content: doc, rank: i + 1})
|
||||
}
|
||||
return rankedDocs
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
function answer_with_docs(query, rankedDocs, model = defaultModel) {
|
||||
let history_summary = ''
|
||||
let messages = [
|
||||
{ role: "system", content: `You are a helpful assistant knowledgeable about AI Alignment and Safety. Please give a clear and coherent answer to the user\'s questions. (written after "Q:") using the following sources. Each source is labeled with a letter. Feel free to use the sources in any order, and try to use multiple sources in your answers` },
|
||||
{ role: "user", content: `Please give a clear and coherent answer to my question. (written after "Q:") using the following sources. Each source is ranked labeled with a letter. Feel free to use the sources in any order, and try to use multiple sources in your answers. Q: "${query}". The sources are:` },
|
||||
{ role: "assistant", content: `Okay, I an ready to carefully consider the first document and how I can use it to answer your query.` },
|
||||
]
|
||||
for (let i in rankedDocs) {
|
||||
let d = rankedDocs[i]
|
||||
messages.push({ role: "user", content: `[${i + 1}]Title: ${d.name}:\n\nContent: ${d.content}\n\nUrl:${d.url}` })
|
||||
messages.push({ role: "assistant", content: `I have considered passage [${parseInt(i) + 1}]. Next please.` })
|
||||
}
|
||||
|
||||
messages.push({ role: "user", content: `Think step by step and use the provided documents to create the most informed, well reasoned answers possible. Use markdown lists where possible. In your three alternative answers, take differen't approaches that lead to differen't content and citations if reasonably possible using the format: [a], [b], etc. If you use multiple sources to make a claim cite all of them. For example: \"AGI is concerning [c, d, e].\"\n\nQ: ${history_summary}: ${query}\n` })
|
||||
|
||||
let answer = sendOpenAIMessage(model, messages)
|
||||
return answer
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function search(q, model = defaultModel, setReply, setStatus, setDocs) {
|
||||
// Step 1: search
|
||||
setStatus({ status: "Searching" })
|
||||
let r1p = bingWebSearch(q)
|
||||
|
||||
// Step 2: rephrase and search
|
||||
let q2p = rephrase(q, model)
|
||||
let r2p = q2p.then((q2) => bingWebSearch(q2))
|
||||
|
||||
// Step 3: example_answer
|
||||
let r3p = exampleAnswer(q, model).then((q3) => bingWebSearch(q3))
|
||||
|
||||
// Step 3: rank
|
||||
// wait for all promises to resolve before continuing
|
||||
let rankedDocsP = Promise.all([r1p, r2p, r3p]).then(([r1, r2, r3]) => {
|
||||
setStatus({ status: "Ranking" })
|
||||
return rank(q, r1, r2, r3, model)
|
||||
})
|
||||
|
||||
let answer = rankedDocsP.then((rankedDocs) => {
|
||||
setDocs(rankedDocs)
|
||||
setStatus({ status: "Answering" })
|
||||
return answer_with_docs(q, rankedDocs, model)
|
||||
}).catch((err) => {
|
||||
console.log(err);
|
||||
err = escapeHtml(err)
|
||||
setStatus({ error: `<Alert variant="warning">Error: ${err}
|
||||
</Alert>` })
|
||||
return err;
|
||||
}).then((answer) => {
|
||||
setReply(answer)
|
||||
setStatus({})
|
||||
return answer;
|
||||
})
|
||||
return answer
|
||||
}
|
||||
|
||||
export default search
|
||||
@@ -0,0 +1,220 @@
|
||||
|
||||
|
||||
# from lesswrong 2
|
||||
|
||||
/*
|
||||
* Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md.
|
||||
*/
|
||||
|
||||
/* Give the block toolbar button some space, moving it a few pixels away from the editable area. */
|
||||
.ck.ck-block-toolbar-button {
|
||||
transform: translateX( calc(-1 * var(--ck-spacing-large)) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
|
||||
* This file is licensed under the terms of the MIT License (see LICENSE.md).
|
||||
*/
|
||||
|
||||
:root {
|
||||
--ck-sample-base-spacing: 2em;
|
||||
--ck-sample-color-white: #fff;
|
||||
--ck-sample-color-green: #279863;
|
||||
--ck-sample-container-width: 1285px;
|
||||
--ck-sample-sidebar-width: 300px;
|
||||
--ck-sample-editor-min-height: 200px;
|
||||
}
|
||||
|
||||
body, html {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
font-family: sans-serif, Arial, Verdana, "Trebuchet MS", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
color: #2D3A4A;
|
||||
}
|
||||
|
||||
body * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #38A5EE;
|
||||
}
|
||||
|
||||
.ck-heading-dropdown {
|
||||
width: 105px !important;
|
||||
}
|
||||
|
||||
/* --------- STYLES TO DISPLAY THE EDITOR BEFORE LOAD ---------------------------------------------------------------------------- */
|
||||
|
||||
.row-editor > div:first-child{
|
||||
background: #fff;
|
||||
border: 1px solid hsl(0, 0%, 70%)
|
||||
}
|
||||
|
||||
.ck.ck-editor {
|
||||
/* Because of sidebar `position: relative`, Edge is overriding the outline of a focused editor. */
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.row-editor .image.image-style-side {
|
||||
float: right;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.row-editor .image img {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.centered {
|
||||
/* Hide overlapping comments. */
|
||||
overflow: hidden;
|
||||
|
||||
max-width: var(--ck-sample-container-width);
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--ck-sample-base-spacing);
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
padding: 0 10px;
|
||||
position: relative;
|
||||
min-width: var(--ck-sample-sidebar-width);
|
||||
font-size: 20px;
|
||||
background: hsl(0, 0%, 98%);
|
||||
border: 1px solid hsl(0, 0%, 77%);
|
||||
border-left: 0;
|
||||
border-top: 0;
|
||||
overflow: hidden;
|
||||
min-height: 100%;
|
||||
|
||||
/* #2733. Do not overlap the left border if the sidebar is longer than content. */
|
||||
box-shadow: -1px 0 0 0 hsl(0, 0%, 77%);
|
||||
}
|
||||
|
||||
.sidebar.narrow {
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.sidebar.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.row-presence {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border: 1px solid hsl(0, 0%, 77%);
|
||||
border-bottom: 0;
|
||||
background: hsl(0, 0%, 98%);
|
||||
padding: var(--ck-spacing-small);
|
||||
|
||||
/* Make `border-bottom` as `box-shadow` to not overlap with the editor border. */
|
||||
box-shadow: 0 1px 0 0 hsl(0, 0%, 77%);
|
||||
|
||||
/* Make `z-index` bigger than `.editor` to properly display tooltips. */
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.presence .ck.ck-presence-list__counter {
|
||||
order: 2;
|
||||
margin-left: var(--ck-spacing-large)
|
||||
}
|
||||
|
||||
.row-editor > div:first-child,
|
||||
/* Classic demo. */
|
||||
main .ck-editor[role='application'] .ck.ck-content {
|
||||
background: #fff;
|
||||
font-size: 1em;
|
||||
line-height: 1.6em;
|
||||
min-height: var(--ck-sample-editor-min-height);
|
||||
padding: 1.5em 2em;
|
||||
}
|
||||
|
||||
/* --------- SAMPLE GENERIC STYLES ---------------------------------------------------------------------------- */
|
||||
header .centered {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
min-height: 8em;
|
||||
}
|
||||
|
||||
header h1 a {
|
||||
font-size: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #2D3A4A;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
header h1 img {
|
||||
display: block;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
header nav ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
header nav ul li {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
header nav ul li + li {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
header nav ul li a {
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
color: #2D3A4A;
|
||||
}
|
||||
|
||||
header nav ul li a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
main .message {
|
||||
padding: 0 0 var(--ck-sample-base-spacing);
|
||||
background: var(--ck-sample-color-green);
|
||||
color: var(--ck-sample-color-white);
|
||||
}
|
||||
|
||||
main .message::after {
|
||||
content: "";
|
||||
z-index: -1;
|
||||
display: block;
|
||||
height: 10em;
|
||||
width: 100%;
|
||||
background: var(--ck-sample-color-green);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
main .message h2 {
|
||||
position: relative;
|
||||
padding-top: 1em;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin: calc(2*var(--ck-sample-base-spacing)) var(--ck-sample-base-spacing);
|
||||
font-size: .8em;
|
||||
text-align: center;
|
||||
color: rgba(0,0,0,.4);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.jsx'
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import './lesswrong-bootstrap-theme.css'
|
||||
import './index.css'
|
||||
import '../assets/logo.svg'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
base: '/rag_search_cite/',
|
||||
build: {
|
||||
sourcemap: true,
|
||||
target: 'esnext',
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user