mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-21 04:39:06 +00:00
Merge branch 'main' into jose/ui
This commit is contained in:
31
.pre-commit-config.yaml
Normal file
31
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# See https://pre-commit.com for more information
|
||||||
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
|
repos:
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: yarn-format
|
||||||
|
name: run yarn format
|
||||||
|
language: system
|
||||||
|
entry: bash -c 'cd www && yarn format'
|
||||||
|
pass_filenames: false
|
||||||
|
files: ^www/
|
||||||
|
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.4.0
|
||||||
|
hooks:
|
||||||
|
- id: debug-statements
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- id: check-added-large-files
|
||||||
|
- id: detect-private-key
|
||||||
|
|
||||||
|
- repo: https://github.com/pycqa/isort
|
||||||
|
rev: 5.12.0
|
||||||
|
hooks:
|
||||||
|
- id: isort
|
||||||
|
args: ["--profile", "black"]
|
||||||
|
|
||||||
|
- repo: https://github.com/psf/black
|
||||||
|
rev: 23.1.0
|
||||||
|
hooks:
|
||||||
|
- id: black
|
||||||
|
args: ["--line-length", "120"]
|
||||||
79
server/trials/title_summary/chat_llm.py
Normal file
79
server/trials/title_summary/chat_llm.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
"""
|
||||||
|
This is an example code containing the bare essentials to load a chat
|
||||||
|
LLM and infer from it using a predefined prompt. The purpose of this file
|
||||||
|
is to show an example of inferring from a chat LLM which is required for
|
||||||
|
banana.dev due to its design and platform limitations
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The following logic was tested on the monadical-ml machine
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
import torch
|
||||||
|
from transformers import (
|
||||||
|
AutoModelForCausalLM,
|
||||||
|
AutoTokenizer
|
||||||
|
)
|
||||||
|
from transformers.generation import GenerationConfig
|
||||||
|
|
||||||
|
# This can be passed via the environment variable or the params supplied
|
||||||
|
# when starting the program via banana.dev platform
|
||||||
|
MODEL_NAME = "lmsys/vicuna-13b-v1.5"
|
||||||
|
|
||||||
|
# Load the model in half precision, and less memory usage
|
||||||
|
model = AutoModelForCausalLM.from_pretrained(MODEL_NAME,
|
||||||
|
low_cpu_mem_usage=True,
|
||||||
|
torch_dtype=torch.bfloat16
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generation config
|
||||||
|
model.config.max_new_tokens = 300
|
||||||
|
gen_cfg = GenerationConfig.from_model_config(model.config)
|
||||||
|
gen_cfg.max_new_tokens = 300
|
||||||
|
|
||||||
|
# Load the tokenizer
|
||||||
|
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
|
||||||
|
|
||||||
|
# Move model to GPU
|
||||||
|
model = model.cuda()
|
||||||
|
print(f"Loading {MODEL_NAME} successful")
|
||||||
|
|
||||||
|
# Inputs
|
||||||
|
sample_chunks = [
|
||||||
|
"You all just came off of your incredible Google Cloud next conference where you released a wide variety of functionality and features and new products across artisan television and also across the entire sort of cloud ecosystem . You want to just first by walking through , first start by walking through all the innovations that you sort of released and what you 're excited about when you come to Google Cloud ? Now our vision is super simple . If you look at what smartphones did for a consumer , you know they took a computer and internet browser , a communication device , and a camera , and made it so that it 's in everybody 's pocket , so it really brought computation to every person . We feel that , you know , our , what we 're trying to do is take all the technological innovation that Google 's doing , but make it super simple so that everyone can consume it . And so that includes our global data center footprint , all the new types of hardware and large-scale systems we work on , the software that we 're making available for people to do high-scale computation , tools for data processing , tools for cybersecurity , processing , tools for cyber security , tools for machine learning , but make it so simple that everyone can use it . And every step that we do to simplify things for people , we think adoption can grow . And so that 's a lot of what we 've done these last three , four years , and we made a number of announcements that next in machine learning and AI in particular , you know , we look at our work as four elements , how we take our large-scale compute systems that were building for AI and how we make that available to everybody . Second , what we 're doing with the software stacks and top of it , things like jacks and other things and how we 're making those available to everybody . Third is advances because different people have different levels of expertise . Some people say I need the hardware to build my own large language model or algorithm . Other people say , look , I really need to use a building block . You guys give me . So , 30s we 've done a lot with AutoML and we announce new capability for image , video , and translation to make it available to everybody . And then lastly , we 're also building completely packaged solutions for some areas and we announce some new stuff . ",
|
||||||
|
" We 're joined next by Thomas Curian , CEO of Google Cloud , and Alexander Wang , CEO and founder of Scale AI . Thomas joined Google in November 2018 as the CEO of Google Cloud . Prior to Google , Thomas spent 22 years at Oracle , where most recently he was president of product development . Before that , Thomas worked at McKinsey as a business analyst and engagement manager . His nearly 30 years of experience have given him a deep knowledge of engineering enterprise relationships and leadership of large organizations . Thomas 's degrees include an MBA in administration and management from Stanford University , as an RJ Miller scholar and a BSEE in electrical engineering and computer science from Princeton University , where he graduated suma cum laude . Thomas serves as a member of the Stanford graduate School of Business Advisory Council and Princeton University School of Engineering Advisory Council . Please welcome to the stage , Thomas Curian and Alexander Wang . This is a super exciting conversation . Thanks for being here , Thomas ."]
|
||||||
|
|
||||||
|
# Model Prompt template for current model
|
||||||
|
prompt = f"""
|
||||||
|
### Human:
|
||||||
|
Create a JSON object as response.The JSON object must have 2 fields:
|
||||||
|
i) title and ii) summary.For the title field,generate a short title
|
||||||
|
for the given text. For the summary field, summarize the given text
|
||||||
|
in three sentences.
|
||||||
|
|
||||||
|
{sample_chunks[0]}
|
||||||
|
|
||||||
|
### Assistant:
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Inference : Chat generation
|
||||||
|
input_ids = tokenizer.encode(prompt, return_tensors='pt').to(model.device)
|
||||||
|
output = model.generate(input_ids, generation_config=gen_cfg)
|
||||||
|
|
||||||
|
# Process output
|
||||||
|
response = tokenizer.decode(output[0].cpu(), skip_special_tokens=True)
|
||||||
|
response = response.split("### Assistant:\n")
|
||||||
|
print("TitleSummaryJsonResponse :", json.loads(response[1]))
|
||||||
|
print("Inference successful")
|
||||||
|
|
||||||
|
# Sample response for sample_chunks[0]
|
||||||
|
|
||||||
|
# TitleSummaryJsonResponse :
|
||||||
|
# {
|
||||||
|
# 'title': 'Google Cloud Next Conference: Simplifying AI and Machine Learning for Everyone',
|
||||||
|
# 'summary': 'Google Cloud announced a wide range of innovations and new products in the AI
|
||||||
|
# and machine learning space at the recent Google Cloud Next conference. The goal
|
||||||
|
# is to make these technologies accessible to everyone by simplifying the process
|
||||||
|
# and providing tools for data processing, cybersecurity, and machine learning.
|
||||||
|
# Google is also working on advances in AutoML and packaged solutions for certain areas.'
|
||||||
|
# }
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# Project Timeline
|
|
||||||
|
|
||||||
Here's a structured timeline for our project completion:
|
|
||||||
|
|
||||||
| Day | Objective |
|
|
||||||
| --------- | ------------------------------------------------------ |
|
|
||||||
| Tuesday | Front-end and Back-end integration |
|
|
||||||
| Wednesday | Project will be polished and tested by Adam |
|
|
||||||
| Thursday | Project completion. Additional tests will be performed |
|
|
||||||
| Friday | Big demo presentation |
|
|
||||||
|
|
||||||
Let's stay focused and get our tasks done on time for a successful demo on Friday. Let's have a successful week!
|
|
||||||
@@ -18,81 +18,81 @@ class CustomRecordPlugin extends RecordPlugin {
|
|||||||
return new CustomRecordPlugin(options || {});
|
return new CustomRecordPlugin(options || {});
|
||||||
}
|
}
|
||||||
render(stream) {
|
render(stream) {
|
||||||
if (!this.wavesurfer) return () => undefined
|
if (!this.wavesurfer) return () => undefined;
|
||||||
|
|
||||||
const container = this.wavesurfer.getWrapper()
|
const container = this.wavesurfer.getWrapper();
|
||||||
const canvas = document.createElement('canvas')
|
const canvas = document.createElement("canvas");
|
||||||
canvas.width = container.clientWidth
|
canvas.width = container.clientWidth;
|
||||||
canvas.height = container.clientHeight
|
canvas.height = container.clientHeight;
|
||||||
canvas.style.zIndex = '10'
|
canvas.style.zIndex = "10";
|
||||||
container.appendChild(canvas)
|
container.appendChild(canvas);
|
||||||
|
|
||||||
const canvasCtx = canvas.getContext('2d')
|
const canvasCtx = canvas.getContext("2d");
|
||||||
const audioContext = new AudioContext()
|
const audioContext = new AudioContext();
|
||||||
const source = audioContext.createMediaStreamSource(stream)
|
const source = audioContext.createMediaStreamSource(stream);
|
||||||
const analyser = audioContext.createAnalyser()
|
const analyser = audioContext.createAnalyser();
|
||||||
analyser.fftSize = 2 ** 5
|
analyser.fftSize = 2 ** 5;
|
||||||
source.connect(analyser)
|
source.connect(analyser);
|
||||||
const bufferLength = analyser.frequencyBinCount
|
const bufferLength = analyser.frequencyBinCount;
|
||||||
const dataArray = new Uint8Array(bufferLength)
|
const dataArray = new Uint8Array(bufferLength);
|
||||||
|
|
||||||
let animationId, previousTimeStamp;
|
let animationId, previousTimeStamp;
|
||||||
const BUFFER_SIZE = 2 ** 8
|
const BUFFER_SIZE = 2 ** 8;
|
||||||
const dataBuffer = new Array(BUFFER_SIZE).fill(canvas.height)
|
const dataBuffer = new Array(BUFFER_SIZE).fill(canvas.height);
|
||||||
|
|
||||||
const drawWaveform = (timeStamp) => {
|
const drawWaveform = (timeStamp) => {
|
||||||
if (!canvasCtx) return
|
if (!canvasCtx) return;
|
||||||
|
|
||||||
analyser.getByteTimeDomainData(dataArray)
|
analyser.getByteTimeDomainData(dataArray);
|
||||||
canvasCtx.clearRect(0, 0, canvas.width, canvas.height)
|
canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
canvasCtx.fillStyle = '#cc3347'
|
canvasCtx.fillStyle = "#cc3347";
|
||||||
|
|
||||||
if (previousTimeStamp === undefined) {
|
if (previousTimeStamp === undefined) {
|
||||||
previousTimeStamp = timeStamp
|
previousTimeStamp = timeStamp;
|
||||||
dataBuffer.push(Math.min(...dataArray))
|
dataBuffer.push(Math.min(...dataArray));
|
||||||
dataBuffer.splice(0, 1)
|
dataBuffer.splice(0, 1);
|
||||||
}
|
}
|
||||||
const elapsed = timeStamp - previousTimeStamp;
|
const elapsed = timeStamp - previousTimeStamp;
|
||||||
if (elapsed > 10) {
|
if (elapsed > 10) {
|
||||||
previousTimeStamp = timeStamp
|
previousTimeStamp = timeStamp;
|
||||||
dataBuffer.push(Math.min(...dataArray))
|
dataBuffer.push(Math.min(...dataArray));
|
||||||
dataBuffer.splice(0, 1)
|
dataBuffer.splice(0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drawing
|
// Drawing
|
||||||
const sliceWidth = canvas.width / dataBuffer.length
|
const sliceWidth = canvas.width / dataBuffer.length;
|
||||||
let x = 0
|
let x = 0;
|
||||||
|
|
||||||
for (let i = 0; i < dataBuffer.length; i++) {
|
for (let i = 0; i < dataBuffer.length; i++) {
|
||||||
const valueNormalized = dataBuffer[i] / canvas.height
|
const valueNormalized = dataBuffer[i] / canvas.height;
|
||||||
const y = valueNormalized * canvas.height / 2
|
const y = (valueNormalized * canvas.height) / 2;
|
||||||
const sliceHeight = canvas.height + 1 - y * 2
|
const sliceHeight = canvas.height + 1 - y * 2;
|
||||||
|
|
||||||
canvasCtx.fillRect(x, y, sliceWidth * 2 / 3, sliceHeight)
|
canvasCtx.fillRect(x, y, (sliceWidth * 2) / 3, sliceHeight);
|
||||||
x += sliceWidth
|
x += sliceWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
animationId = requestAnimationFrame(drawWaveform)
|
animationId = requestAnimationFrame(drawWaveform);
|
||||||
}
|
};
|
||||||
|
|
||||||
drawWaveform()
|
drawWaveform();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (animationId) {
|
if (animationId) {
|
||||||
cancelAnimationFrame(animationId)
|
cancelAnimationFrame(animationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source) {
|
if (source) {
|
||||||
source.disconnect()
|
source.disconnect();
|
||||||
source.mediaStream.getTracks().forEach((track) => track.stop())
|
source.mediaStream.getTracks().forEach((track) => track.stop());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioContext) {
|
if (audioContext) {
|
||||||
audioContext.close()
|
audioContext.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas?.remove()
|
canvas?.remove();
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
startRecording(stream) {
|
startRecording(stream) {
|
||||||
this.preventInteraction();
|
this.preventInteraction();
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { Mulberry32 } from "../utils.js";
|
import { Mulberry32 } from "../utils.js";
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faChevronRight, faChevronDown } from '@fortawesome/free-solid-svg-icons'
|
import {
|
||||||
|
faChevronRight,
|
||||||
|
faChevronDown,
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
export function Dashboard({
|
export function Dashboard({
|
||||||
isRecording,
|
isRecording,
|
||||||
@@ -24,7 +27,8 @@ export function Dashboard({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleScroll = (e) => {
|
const handleScroll = (e) => {
|
||||||
const bottom = e.target.scrollHeight - e.target.scrollTop === e.target.clientHeight;
|
const bottom =
|
||||||
|
e.target.scrollHeight - e.target.scrollTop === e.target.clientHeight;
|
||||||
if (!bottom && autoscrollEnabled) {
|
if (!bottom && autoscrollEnabled) {
|
||||||
setAutoscrollEnabled(false);
|
setAutoscrollEnabled(false);
|
||||||
} else if (bottom && !autoscrollEnabled) {
|
} else if (bottom && !autoscrollEnabled) {
|
||||||
@@ -44,10 +48,18 @@ export function Dashboard({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`absolute top-7 right-5 w-10 h-10 ${autoscrollEnabled ? 'hidden' : 'flex'} justify-center items-center text-2xl cursor-pointer opacity-70 hover:opacity-100 transition-opacity duration-200 animate-bounce rounded-xl border-slate-400 bg-[#3c82f638] text-[#3c82f6ed]`}
|
className={`absolute top-7 right-5 w-10 h-10 ${
|
||||||
|
autoscrollEnabled ? "hidden" : "flex"
|
||||||
|
} justify-center items-center text-2xl cursor-pointer opacity-70 hover:opacity-100 transition-opacity duration-200 animate-bounce rounded-xl border-slate-400 bg-[#3c82f638] text-[#3c82f6ed]`}
|
||||||
onClick={scrollToBottom}
|
onClick={scrollToBottom}
|
||||||
>⬇</div>
|
>
|
||||||
<div id="topics-div" className="py-2 overflow-y-auto" onScroll={handleScroll}>
|
⬇
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="topics-div"
|
||||||
|
className="py-2 overflow-y-auto"
|
||||||
|
onScroll={handleScroll}
|
||||||
|
>
|
||||||
{topics.map((item, index) => (
|
{topics.map((item, index) => (
|
||||||
<div key={index} className="border-b-2 py-2 hover:bg-[#8ec5fc30]">
|
<div key={index} className="border-b-2 py-2 hover:bg-[#8ec5fc30]">
|
||||||
<div
|
<div
|
||||||
@@ -64,7 +76,9 @@ export function Dashboard({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{openIndex === index && (
|
{openIndex === index && (
|
||||||
<div className="p-2 mt-2 -mb-2 bg-slate-50 rounded">{item.transcript}</div>
|
<div className="p-2 mt-2 -mb-2 bg-slate-50 rounded">
|
||||||
|
{item.transcript}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -7,27 +7,28 @@ import "react-dropdown/style.css";
|
|||||||
|
|
||||||
import CustomRecordPlugin from "./CustomRecordPlugin";
|
import CustomRecordPlugin from "./CustomRecordPlugin";
|
||||||
|
|
||||||
|
|
||||||
const AudioInputsDropdown = (props) => {
|
const AudioInputsDropdown = (props) => {
|
||||||
const [ddOptions, setDdOptions] = useState([]);
|
const [ddOptions, setDdOptions] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
// Request permission to use audio inputs
|
// Request permission to use audio inputs
|
||||||
await navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => stream.getTracks().forEach((t) => t.stop()))
|
await navigator.mediaDevices
|
||||||
|
.getUserMedia({ audio: true })
|
||||||
|
.then((stream) => stream.getTracks().forEach((t) => t.stop()));
|
||||||
|
|
||||||
const devices = await navigator.mediaDevices.enumerateDevices()
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||||
const audioDevices = devices
|
const audioDevices = devices
|
||||||
.filter((d) => d.kind === "audioinput" && d.deviceId != "")
|
.filter((d) => d.kind === "audioinput" && d.deviceId != "")
|
||||||
.map((d) => ({ value: d.deviceId, label: d.label }))
|
.map((d) => ({ value: d.deviceId, label: d.label }));
|
||||||
|
|
||||||
if (audioDevices.length < 1) return console.log("no audio input devices")
|
if (audioDevices.length < 1) return console.log("no audio input devices");
|
||||||
|
|
||||||
setDdOptions(audioDevices)
|
setDdOptions(audioDevices);
|
||||||
props.setDeviceId(audioDevices[0].value)
|
props.setDeviceId(audioDevices[0].value);
|
||||||
}
|
};
|
||||||
init()
|
init();
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const handleDropdownChange = (e) => {
|
const handleDropdownChange = (e) => {
|
||||||
props.setDeviceId(e.value);
|
props.setDeviceId(e.value);
|
||||||
@@ -40,8 +41,8 @@ const AudioInputsDropdown = (props) => {
|
|||||||
value={ddOptions[0]}
|
value={ddOptions[0]}
|
||||||
disabled={props.disabled}
|
disabled={props.disabled}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function Recorder(props) {
|
export default function Recorder(props) {
|
||||||
const waveformRef = useRef();
|
const waveformRef = useRef();
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ const useWebRTC = (stream) => {
|
|||||||
duration: serverData.duration,
|
duration: serverData.duration,
|
||||||
summary: serverData.summary,
|
summary: serverData.summary,
|
||||||
},
|
},
|
||||||
text: ''
|
text: "",
|
||||||
}));
|
}));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ export default function RootLayout({ children }) {
|
|||||||
<body className={roboto.className + " flex flex-col min-h-screen"}>
|
<body className={roboto.className + " flex flex-col min-h-screen"}>
|
||||||
{children}
|
{children}
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ const App = () => {
|
|||||||
// transcription, summary, etc
|
// transcription, summary, etc
|
||||||
const serverData = useWebRTC(stream);
|
const serverData = useWebRTC(stream);
|
||||||
|
|
||||||
const sendStopCmd = () => serverData?.peer?.send(JSON.stringify({ cmd: "STOP" }))
|
const sendStopCmd = () =>
|
||||||
|
serverData?.peer?.send(JSON.stringify({ cmd: "STOP" }));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center h-[100svh] bg-gradient-to-r from-[#8ec5fc30] to-[#e0c3fc42]">
|
<div className="flex flex-col items-center h-[100svh] bg-gradient-to-r from-[#8ec5fc30] to-[#e0c3fc42]">
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
output: 'standalone',
|
output: "standalone",
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = nextConfig;
|
module.exports = nextConfig;
|
||||||
|
|
||||||
|
|
||||||
// Sentry content below
|
// Sentry content below
|
||||||
|
|
||||||
const { withSentryConfig } = require("@sentry/nextjs");
|
const { withSentryConfig } = require("@sentry/nextjs");
|
||||||
@@ -40,5 +39,5 @@ module.exports = withSentryConfig(
|
|||||||
|
|
||||||
// Automatically tree-shake Sentry logger statements to reduce bundle size
|
// Automatically tree-shake Sentry logger statements to reduce bundle size
|
||||||
disableLogger: true,
|
disableLogger: true,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -30,8 +30,8 @@
|
|||||||
},
|
},
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"repository": "https://github.com/Monadical-SAS/reflector-ui.git",
|
"repository": "https://github.com/Monadical-SAS/reflector-ui.git",
|
||||||
"author": "Koper <andreas@monadical.com>",
|
"author": "Andreas <andreas@monadical.com>",
|
||||||
"license": "MIT",
|
"license": "All Rights Reserved",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prettier": "^3.0.0"
|
"prettier": "^3.0.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,10 @@ export default function Home() {
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
Next, look for the error on the{" "}
|
Next, look for the error on the{" "}
|
||||||
<a href="https://monadical.sentry.io/issues/?project=4505634666577920">Issues Page</a>.
|
<a href="https://monadical.sentry.io/issues/?project=4505634666577920">
|
||||||
|
Issues Page
|
||||||
|
</a>
|
||||||
|
.
|
||||||
</p>
|
</p>
|
||||||
<p style={{ marginTop: "24px" }}>
|
<p style={{ marginTop: "24px" }}>
|
||||||
For more information, see{" "}
|
For more information, see{" "}
|
||||||
|
|||||||
Reference in New Issue
Block a user