first cut ready ... requires discussion with Mike

This commit is contained in:
Amarsh Anand 2023-08-20 17:08:05 -07:00
parent 2ab0f4af4c
commit 81be0d4b38
7 changed files with 5694 additions and 4560 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@ import { RendererPluginType } from "./types";
import { SimpleImageRenderer } from "./view/image";
import { SimpleMarkdownRenderer } from "./view/markdown";
import { SimpleJsonRenderer } from "./view/json";
import { CodeRenderer } from "./view/code";
import { SourceCodeRenderer } from "./view/code";
import { OpenAIRenderer, OpenAIRendererModel } from "./view/openai";
import { isBlank } from "./util";
import { sprintf } from "sprintf-js";
@ -48,7 +48,7 @@ const CodePlugin: RendererPluginType = {
collapseType: "hide",
globalCss: null,
mimeTypes: ["text/plain"],
simpleComponent: CodeRenderer,
simpleComponent: SourceCodeRenderer,
};
const OpenAIPlugin: RendererPluginType = {

View File

@ -39,7 +39,7 @@
@import "views.less";
@import "sidebar.less";
@import "modals.less"; // includes settings
@import "comps.less"; // includes terminal
@import "comps.less"; // includes terminal
@import "tabs.less";
@import "cmdinput.less";
@import "lines.less";
@ -47,251 +47,259 @@
// global settings / overrides
:root {
--fa-style-family: "Font Awesome 6 Sharp";
--fa-style-family: "Font Awesome 6 Sharp";
}
html, body, #app, #main {
background-color: #000;
height: 100vh;
html,
body,
#app,
#main {
background-color: #000;
height: 100vh;
}
.content {
a:hover {
color: #485fc7;
}
a:hover {
color: #485fc7;
}
}
body {
overflow: hidden;
overflow: hidden;
}
body::-webkit-scrollbar {
display: none;
display: none;
}
*::-webkit-scrollbar {
background-color: #777;
width: 5px;
height: 5px;
background-color: #777;
width: 5px;
height: 5px;
}
*::-webkit-scrollbar-thumb {
background: white;
background: white;
}
input[type=checkbox] {
cursor: pointer;
input[type="checkbox"] {
cursor: pointer;
}
// main layout
#main {
height: 100vh;
height: 100vh;
display: flex;
flex-direction: column;
.main-content {
display: flex;
flex-direction: column;
flex-direction: row;
background-color: black;
height: 100%;
.main-content {
display: flex;
flex-direction: row;
background-color: black;
height: 100%;
.session-view,
.history-view,
.bookmarks-view {
flex-grow: 1;
display: flex;
flex-direction: column;
min-width: 300px;
position: relative;
.session-view, .history-view, .bookmarks-view {
flex-grow: 1;
display: flex;
flex-direction: column;
min-width: 300px;
position: relative;
&.is-hidden {
display: none;
}
}
.screen-view {
flex-grow: 1;
border-right: 1px solid #ccc;
position: relative;
}
.window-view {
display: flex;
flex-direction: column;
position: relative;
.rendermode-tag {
position: absolute;
top: 0;
right: 0;
background-color: rgba(78, 154, 6, 0.65);
color: black;
padding: 2px 8px 2px 4px;
border-bottom-left-radius: 5px;
z-index: 10;
font-size: 12px;
&.is-active {
color: #ccc;
}
.render-mode {
padding-top: 2px;
font-size: 16px;
position: relative;
cursor: pointer;
color: #ccc;
&:hover {
color: white;
}
}
}
.share-tag {
color: #ccc;
position: absolute;
top: 0;
left: 40%;
background-color: darken(rgb(0, 177, 10), 20%);
padding: 2px 8px 2px 4px;
z-index: 11;
font-size: 12px;
/* border-radius: 0 0 5px 5px; */
opacity: 0.8;
display: flex;
flex-direction: column;
.share-tag-link {
margin-top: 10px;
display: none;
}
&:hover {
.share-tag-title {
font-size: 14px;
font-weight: bold;
}
opacity: 1.0;
padding: 20px;
width: 250px;
border: 1px solid #ccc;
border-top: 0;
.share-tag-link {
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
}
}
}
.window-empty {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 10px;
height: 100%;
color: #ccc;
.mono-font();
code {
background-color: black;
color: #4e9a06;
}
&.should-fade {
opacity: 1.0;
animation: fade-in 2.5s;
}
}
}
&.is-hidden {
display: none;
}
}
.screen-view {
flex-grow: 1;
border-right: 1px solid #ccc;
position: relative;
}
.window-view {
display: flex;
flex-direction: column;
position: relative;
.rendermode-tag {
position: absolute;
top: 0;
right: 0;
background-color: rgba(78, 154, 6, 0.65);
color: black;
padding: 2px 8px 2px 4px;
border-bottom-left-radius: 5px;
z-index: 10;
font-size: 12px;
&.is-active {
color: #ccc;
}
.render-mode {
padding-top: 2px;
font-size: 16px;
position: relative;
cursor: pointer;
color: #ccc;
&:hover {
color: white;
}
}
}
.share-tag {
color: #ccc;
position: absolute;
top: 0;
left: 40%;
background-color: darken(rgb(0, 177, 10), 20%);
padding: 2px 8px 2px 4px;
z-index: 11;
font-size: 12px;
/* border-radius: 0 0 5px 5px; */
opacity: 0.8;
display: flex;
flex-direction: column;
.share-tag-link {
margin-top: 10px;
display: none;
}
&:hover {
.share-tag-title {
font-size: 14px;
font-weight: bold;
}
opacity: 1;
padding: 20px;
width: 250px;
border: 1px solid #ccc;
border-top: 0;
.share-tag-link {
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
}
}
}
.window-empty {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 10px;
height: 100%;
color: #ccc;
.mono-font();
code {
background-color: black;
color: #4e9a06;
}
&.should-fade {
opacity: 1;
animation: fade-in 2.5s;
}
}
}
}
}
.remote-field .remote-status {
top: 4px;
top: 4px;
}
.image-renderer {
padding: 10px;
img {
display: block;
}
padding: 10px;
img {
display: block;
}
}
.renderer-container {
.error-container {
color: @term-red;
font-size: 14px;
padding: 5px;
}
.error-container {
color: @term-red;
font-size: 14px;
padding: 5px;
}
.scroller {
overflow: auto;
display: flex;
flex-direction: row;
}
.scroller {
overflow: auto;
display: flex;
flex-direction: row;
}
}
.renderer-container.code-renderer {
margin-top: 10px;
}
.renderer-container.json-renderer {
padding: 10px;
font-size: 12px;
padding: 10px;
font-size: 12px;
}
.markdown-renderer .markdown {
padding: 5px;
line-height: 1.5;
width: fit-content;
padding: 5px;
line-height: 1.5;
width: fit-content;
blockquote {
background-color: #222;
}
blockquote {
background-color: #222;
}
code {
background-color: #222;
font-size: 14px;
}
code {
background-color: #222;
font-size: 14px;
}
pre {
background-color: #222;
margin: 2px 10px 6px 10px;
padding: 4px 4px 4px 6px;
}
pre {
background-color: #222;
margin: 2px 10px 6px 10px;
padding: 4px 4px 4px 6px;
}
}
.openai-renderer {
.openai-message {
display: flex;
flex-direction: row;
justify-content: flex-start;
.openai-role {
color: @term-bright-green;
font-weight: bold;
width: 100px;
}
.openai-message {
display: flex;
flex-direction: row;
justify-content: flex-start;
.openai-role.openai-role-assistant {
color: @term-bright-white;
}
.openai-content-user {
white-space: pre;
color: white;
}
.openai-content-assistant {
color: white;
}
.openai-role-error {
color: @term-bright-red;
}
.openai-content-error {
color: @term-bright-red;
}
.openai-role {
color: @term-bright-green;
font-weight: bold;
width: 100px;
}
.openai-role.openai-role-assistant {
color: @term-bright-white;
}
.openai-content-user {
white-space: pre;
color: white;
}
.openai-content-assistant {
color: white;
}
.openai-role-error {
color: @term-bright-red;
}
.openai-content-error {
color: @term-bright-red;
}
}
}

View File

@ -1,182 +1,229 @@
import * as React from "react";
import * as mobxReact from "mobx-react";
import * as mobx from "mobx";
import {sprintf} from "sprintf-js";
import {boundMethod} from "autobind-decorator";
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
import type {RendererModelInitializeParams, TermOptsType, RendererContext, RendererOpts, SimpleBlobRendererComponent, RendererModelContainerApi, RendererPluginType, PtyDataType, RendererModel, RendererOptsUpdate, LineType, TermContextUnion, RendererContainerType} from "./types";
import {PacketDataBuffer} from "./ptydata";
import {debounce, throttle} from "throttle-debounce";
import { sprintf } from "sprintf-js";
import { boundMethod } from "autobind-decorator";
import {
If,
For,
When,
Otherwise,
Choose,
} from "tsx-control-statements/components";
import type {
RendererModelInitializeParams,
TermOptsType,
RendererContext,
RendererOpts,
SimpleBlobRendererComponent,
RendererModelContainerApi,
RendererPluginType,
PtyDataType,
RendererModel,
RendererOptsUpdate,
LineType,
TermContextUnion,
RendererContainerType,
} from "./types";
import { PacketDataBuffer } from "./ptydata";
import { debounce, throttle } from "throttle-debounce";
type OV<V> = mobx.IObservableValue<V>;
type CV<V> = mobx.IComputedValue<V>;
class SimpleBlobRendererModel {
context : RendererContext;
opts : RendererOpts;
isDone : OV<boolean>;
api : RendererModelContainerApi;
savedHeight : number;
loading : OV<boolean>;
loadError : OV<string> = mobx.observable.box(null, {name: "renderer-loadError"});
ptyData : PtyDataType;
ptyDataSource : (termContext : TermContextUnion) => Promise<PtyDataType>;
initialize(params : RendererModelInitializeParams) : void {
this.loading = mobx.observable.box(true, {name: "renderer-loading"});
this.isDone = mobx.observable.box(params.isDone, {name: "renderer-isDone"});
this.context = params.context;
this.opts = params.opts;
this.api = params.api;
this.savedHeight = params.savedHeight;
this.ptyDataSource = params.ptyDataSource;
if (this.isDone.get()) {
setTimeout(() => this.reload(0), 10);
}
}
context: RendererContext;
opts: RendererOpts;
isDone: OV<boolean>;
api: RendererModelContainerApi;
savedHeight: number;
loading: OV<boolean>;
loadError: OV<string> = mobx.observable.box(null, {
name: "renderer-loadError",
});
ptyData: PtyDataType;
ptyDataSource: (termContext: TermContextUnion) => Promise<PtyDataType>;
dispose() : void {
return;
}
giveFocus() : void {
return;
initialize(params: RendererModelInitializeParams): void {
this.loading = mobx.observable.box(true, { name: "renderer-loading" });
this.isDone = mobx.observable.box(params.isDone, {
name: "renderer-isDone",
});
this.context = params.context;
this.opts = params.opts;
this.api = params.api;
this.savedHeight = params.savedHeight;
this.ptyDataSource = params.ptyDataSource;
if (this.isDone.get()) {
setTimeout(() => this.reload(0), 10);
}
}
updateOpts(update : RendererOptsUpdate) : void {
Object.assign(this.opts, update);
}
dispose(): void {
return;
}
updateHeight(newHeight : number) : void {
if (this.savedHeight != newHeight) {
this.savedHeight = newHeight;
this.api.saveHeight(newHeight);
}
giveFocus(): void {
return;
}
updateOpts(update: RendererOptsUpdate): void {
Object.assign(this.opts, update);
}
updateHeight(newHeight: number): void {
if (this.savedHeight != newHeight) {
this.savedHeight = newHeight;
this.api.saveHeight(newHeight);
}
setIsDone() : void {
if (this.isDone.get()) {
return;
}
}
setIsDone(): void {
if (this.isDone.get()) {
return;
}
mobx.action(() => {
this.isDone.set(true);
})();
this.reload(0);
}
reload(delayMs: number): void {
mobx.action(() => {
this.loading.set(true);
})();
let rtnp = this.ptyDataSource(this.context);
if (rtnp == null) {
console.log(
"no promise returned from ptyDataSource (simplerenderer)",
this.context
);
return;
}
rtnp
.then((ptydata) => {
setTimeout(() => {
this.ptyData = ptydata;
mobx.action(() => {
this.loading.set(false);
this.loadError.set(null);
})();
}, delayMs);
})
.catch((e) => {
console.log("error loading data", e);
mobx.action(() => {
this.isDone.set(true);
this.loadError.set("error loading data: " + e);
})();
this.reload(0);
}
});
}
reload(delayMs : number) : void {
mobx.action(() => {
this.loading.set(true);
})();
let rtnp = this.ptyDataSource(this.context);
if (rtnp == null) {
console.log("no promise returned from ptyDataSource (simplerenderer)", this.context);
return;
}
rtnp.then((ptydata) => {
setTimeout(() => {
this.ptyData = ptydata;
mobx.action(() => {
this.loading.set(false);
this.loadError.set(null);
})();
}, delayMs);
}).catch((e) => {
console.log("error loading data", e);
mobx.action(() => {
this.loadError.set("error loading data: " + e);
})();
});
}
receiveData(pos : number, data : Uint8Array, reason? : string) : void {
// this.dataBuf.receiveData(pos, data, reason);
}
receiveData(pos: number, data: Uint8Array, reason?: string): void {
// this.dataBuf.receiveData(pos, data, reason);
}
}
@mobxReact.observer
class SimpleBlobRenderer extends React.Component<{rendererContainer : RendererContainerType, lineId : string, plugin : RendererPluginType, onHeightChange : () => void, initParams : RendererModelInitializeParams}, {}> {
model : SimpleBlobRendererModel;
wrapperDivRef : React.RefObject<any> = React.createRef();
rszObs : ResizeObserver;
updateHeight_debounced : (newHeight : number) => void;
class SimpleBlobRenderer extends React.Component<
{
rendererContainer: RendererContainerType;
lineId: string;
plugin: RendererPluginType;
onHeightChange: () => void;
initParams: RendererModelInitializeParams;
},
{}
> {
model: SimpleBlobRendererModel;
wrapperDivRef: React.RefObject<any> = React.createRef();
rszObs: ResizeObserver;
updateHeight_debounced: (newHeight: number) => void;
constructor(props : any) {
super(props);
let {rendererContainer, lineId, plugin, initParams} = this.props;
this.model = new SimpleBlobRendererModel();
this.model.initialize(initParams);
rendererContainer.registerRenderer(lineId, this.model);
this.updateHeight_debounced = debounce(1000, this.updateHeight.bind(this));
}
constructor(props: any) {
super(props);
let { rendererContainer, lineId, plugin, initParams } = this.props;
this.model = new SimpleBlobRendererModel();
this.model.initialize(initParams);
rendererContainer.registerRenderer(lineId, this.model);
this.updateHeight_debounced = debounce(1000, this.updateHeight.bind(this));
}
updateHeight(newHeight : number) : void {
this.model.updateHeight(newHeight);
}
updateHeight(newHeight: number): void {
this.model.updateHeight(newHeight);
}
handleResize(entries : ResizeObserverEntry[]) : void {
if (this.model.loading.get()) {
return;
}
if (this.props.onHeightChange) {
this.props.onHeightChange();
}
if (!this.model.loading.get() && this.wrapperDivRef.current != null) {
let height = this.wrapperDivRef.current.offsetHeight;
this.updateHeight_debounced(height);
}
handleResize(entries: ResizeObserverEntry[]): void {
if (this.model.loading.get()) {
return;
}
if (this.props.onHeightChange) {
this.props.onHeightChange();
}
if (!this.model.loading.get() && this.wrapperDivRef.current != null) {
let height = this.wrapperDivRef.current.offsetHeight;
this.updateHeight_debounced(height);
}
}
checkRszObs() {
if (this.rszObs != null) {
return;
}
if (this.wrapperDivRef.current == null) {
return;
}
this.rszObs = new ResizeObserver(this.handleResize.bind(this));
this.rszObs.observe(this.wrapperDivRef.current);
checkRszObs() {
if (this.rszObs != null) {
return;
}
componentDidMount() {
this.checkRszObs();
if (this.wrapperDivRef.current == null) {
return;
}
this.rszObs = new ResizeObserver(this.handleResize.bind(this));
this.rszObs.observe(this.wrapperDivRef.current);
}
componentWillUnmount() {
let {rendererContainer, lineId} = this.props;
rendererContainer.unloadRenderer(lineId);
if (this.rszObs != null) {
this.rszObs.disconnect();
this.rszObs = null;
}
}
componentDidMount() {
this.checkRszObs();
}
componentDidUpdate() {
this.checkRszObs();
componentWillUnmount() {
let { rendererContainer, lineId } = this.props;
rendererContainer.unloadRenderer(lineId);
if (this.rszObs != null) {
this.rszObs.disconnect();
this.rszObs = null;
}
}
render() {
let {plugin} = this.props;
let model = this.model;
if (model.loading.get()) {
let height = this.model.savedHeight;
return (<div ref={this.wrapperDivRef} style={{minHeight: height}}>...</div>);
}
let Comp = plugin.simpleComponent;
if (Comp == null) {
<div ref={this.wrapperDivRef}>
(no component found in plugin)
</div>
}
let dataBlob = new Blob([model.ptyData.data]);
let simpleModel = (model as SimpleBlobRendererModel);
return (
<div ref={this.wrapperDivRef}>
<Comp data={dataBlob} context={simpleModel.context} opts={simpleModel.opts} savedHeight={simpleModel.savedHeight}/>
</div>
);
componentDidUpdate() {
this.checkRszObs();
}
render() {
let { plugin } = this.props;
let model = this.model;
if (model.loading.get()) {
let height = this.model.savedHeight;
return (
<div ref={this.wrapperDivRef} style={{ minHeight: height }}>
...
</div>
);
}
let Comp = plugin.simpleComponent;
if (Comp == null) {
<div ref={this.wrapperDivRef}>(no component found in plugin)</div>;
}
let dataBlob = new Blob([model.ptyData.data]);
let simpleModel = model as SimpleBlobRendererModel;
let { festate, cmdstr } = this.props.initParams.rawCmd;
return (
<div ref={this.wrapperDivRef}>
<Comp
cwd={festate.cwd}
cmdstr={cmdstr}
data={dataBlob}
context={simpleModel.context}
opts={simpleModel.opts}
savedHeight={simpleModel.savedHeight}
/>
</div>
);
}
}
export {SimpleBlobRendererModel, SimpleBlobRenderer};
export { SimpleBlobRendererModel, SimpleBlobRenderer };

View File

@ -409,23 +409,19 @@ type RendererModel = {
};
type SimpleBlobRendererComponent = React.ComponentType<{
path: String;
data: Blob;
context: RendererContext;
opts: RendererOpts;
savedHeight: number;
}>;
// @mike - I guess we can remove SimpleJsonRendererComponent - its doesnt have any references
type SimpleJsonRendererComponent = React.ComponentType<{
data: any;
context: RendererContext;
opts: RendererOpts;
savedHeight: number;
}>;
type SimpleCodeRendererComponent = React.ComponentType<{
data: any;
context: RendererContext;
opts: RendererOpts;
savedHeight: number;
}>;
type FullRendererComponent = React.ComponentType<{ model: any }>;
type WindowSize = {

View File

@ -15,9 +15,10 @@ type OV<V> = mobx.IObservableValue<V>;
const MaxJsonSize = 50000;
@mobxReact.observer
class CodeRenderer extends React.Component<
class SourceCodeRenderer extends React.Component<
{
data: Blob;
path: String;
context: RendererContext;
opts: RendererOpts;
savedHeight: number;
@ -33,17 +34,34 @@ class CodeRenderer extends React.Component<
deep: false,
});
componentDidMount() {
let dataBlob = this.props.data;
let prtn = dataBlob.text();
prtn.then((text) => {
this.code.set(text);
const detectedLanguage = `javascript`;
this.language.set(detectedLanguage);
console.log(`1. ${detectedLanguage}\n\n${text}\n\n`);
});
editorRef;
constructor(props) {
super(props);
this.editorRef = React.createRef();
}
componentDidMount() {
let prtn = this.props.data.text();
prtn.then((text) => this.code.set(text));
}
handleEditorDidMount = (editor, monaco) => {
const extension = this.props.cmdstr.split(".").pop();
const detectedLanguage = monaco.languages
.getLanguages()
.find(
(lang) => lang.extensions && lang.extensions.includes("." + extension)
);
if (detectedLanguage) {
this.editorRef.current = editor;
const model = editor.getModel();
if (model) {
monaco.editor.setModelLanguage(model, detectedLanguage.id);
this.language.set(detectedLanguage.id);
}
}
};
render() {
let opts = this.props.opts;
let maxWidth = opts.maxSize.width;
@ -53,16 +71,20 @@ class CodeRenderer extends React.Component<
}
let lang = this.language.get();
let code = this.code.get();
console.log(`2. ${lang}\n\n${code}\n\n`);
if (!lang) return <></>;
if (!code) return <></>;
return (
<div className="renderer-container json-renderer">
<div className="renderer-container code-renderer">
<div className="scroller" style={{ maxHeight: opts.maxSize.height }}>
<Editor
height="30vh"
theme="vs-dark"
theme="hc-black"
defaultLanguage={lang}
defaultValue={code}
onMount={this.handleEditorDidMount}
options={{
scrollBeyondLastLine: false,
fontSize: "14px",
}}
/>
</div>
</div>
@ -70,4 +92,4 @@ class CodeRenderer extends React.Component<
}
}
export { CodeRenderer };
export { SourceCodeRenderer };