mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-02-23 02:51:26 +01:00
Merge branches 'PE_153-Close_without_saving' and 'main' of github.com:commandlinedev/prompt-client
This commit is contained in:
commit
f251718d62
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Prompt",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.1",
|
||||
"main": "dist/emain.js",
|
||||
"license": "Proprietary",
|
||||
"dependencies": {
|
||||
@ -13,6 +13,7 @@
|
||||
"mobx": "^6.6.0",
|
||||
"mobx-react": "^7.5.0",
|
||||
"monaco-editor": "^0.41.0",
|
||||
"mustache": "^4.2.0",
|
||||
"node-fetch": "^3.2.10",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
|
14
src/emain.ts
14
src/emain.ts
@ -169,8 +169,13 @@ function getMods(input: any) {
|
||||
}
|
||||
|
||||
function shNavHandler(event: any, url: any) {
|
||||
console.log("navigation canceled", url);
|
||||
event.preventDefault();
|
||||
if (url.startsWith("https://") || url.startsWith("http://") || url.startsWith("file://")) {
|
||||
console.log("open external, shNav", url);
|
||||
electron.shell.openExternal(url);
|
||||
} else {
|
||||
console.log("navigation canceled", url);
|
||||
}
|
||||
}
|
||||
|
||||
function shFrameNavHandler(event: any, url: any) {
|
||||
@ -183,6 +188,7 @@ function shFrameNavHandler(event: any, url: any) {
|
||||
if (event.frame.name == "webview") {
|
||||
// "webview" links always open in new window
|
||||
// this will *not* effect the initial load because srcdoc does not count as an electron navigation
|
||||
console.log("open external, frameNav", url);
|
||||
electron.shell.openExternal(url);
|
||||
return;
|
||||
}
|
||||
@ -301,13 +307,19 @@ function createMainWindow(clientData) {
|
||||
});
|
||||
win.webContents.setWindowOpenHandler(({ url, frameName }) => {
|
||||
if (url.startsWith("https://docs.getprompt.dev/")) {
|
||||
console.log("openExternal docs", url);
|
||||
electron.shell.openExternal(url);
|
||||
} else if (url.startsWith("https://discord.gg/")) {
|
||||
console.log("openExternal discord", url);
|
||||
electron.shell.openExternal(url);
|
||||
} else if (url.startsWith("https://extern/?")) {
|
||||
let qmark = url.indexOf("?");
|
||||
let param = url.substr(qmark + 1);
|
||||
let newUrl = decodeURIComponent(param);
|
||||
console.log("openExternal extern", newUrl);
|
||||
electron.shell.openExternal(newUrl);
|
||||
} else if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("file://")) {
|
||||
console.log("openExternal fallback", url);
|
||||
electron.shell.openExternal(newUrl);
|
||||
}
|
||||
console.log("window-open denied", url);
|
||||
|
@ -2,6 +2,7 @@ import { RendererPluginType } from "./types";
|
||||
import { SimpleImageRenderer } from "./view/image";
|
||||
import { SimpleMarkdownRenderer } from "./view/markdown";
|
||||
import { SourceCodeRenderer } from "./view/code";
|
||||
import { SimpleMustacheRenderer } from "./view/mustache";
|
||||
import { OpenAIRenderer, OpenAIRendererModel } from "./view/openai";
|
||||
import { isBlank } from "./util";
|
||||
import { sprintf } from "sprintf-js";
|
||||
@ -28,6 +29,17 @@ const MarkdownPlugin: RendererPluginType = {
|
||||
simpleComponent: SimpleMarkdownRenderer,
|
||||
};
|
||||
|
||||
const MustachePlugin: RendererPluginType = {
|
||||
name: "mustache",
|
||||
rendererType: "simple",
|
||||
heightType: "pixels",
|
||||
dataType: "blob",
|
||||
collapseType: "hide",
|
||||
globalCss: null,
|
||||
mimeTypes: ["text/plain"],
|
||||
simpleComponent: SimpleMustacheRenderer,
|
||||
};
|
||||
|
||||
const CodePlugin: RendererPluginType = {
|
||||
name: "code",
|
||||
rendererType: "simple",
|
||||
@ -87,6 +99,7 @@ if ((window as any).PluginModel == null) {
|
||||
PluginModel.registerRendererPlugin(MarkdownPlugin);
|
||||
PluginModel.registerRendererPlugin(CodePlugin);
|
||||
PluginModel.registerRendererPlugin(OpenAIPlugin);
|
||||
PluginModel.registerRendererPlugin(MustachePlugin);
|
||||
(window as any).PluginModel = PluginModel;
|
||||
}
|
||||
|
||||
|
@ -254,7 +254,7 @@ input[type="checkbox"] {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
|
||||
.monaco-editor .monaco-editor-background {
|
||||
background-color: rgba(255, 255, 255, 0.075) !important;
|
||||
}
|
||||
@ -331,7 +331,26 @@ input[type="checkbox"] {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.renderer-container .markdown {
|
||||
.renderer-container.mustache-renderer {
|
||||
color: @term-white;
|
||||
.cmd-hints {
|
||||
display: inline-block !important;
|
||||
position: relative;
|
||||
margin-right: 26px;
|
||||
}
|
||||
.hint-item {
|
||||
border-radius: 4px 4px 0 0;
|
||||
padding: 3px 9px 2px 8px;
|
||||
line-height: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
.refresh-button {
|
||||
color: rgb(52, 52, 52);
|
||||
background-color: @term-white;
|
||||
}
|
||||
}
|
||||
|
||||
.renderer-container .content {
|
||||
padding: 5px;
|
||||
line-height: 1.5;
|
||||
width: fit-content;
|
||||
@ -350,6 +369,15 @@ input[type="checkbox"] {
|
||||
margin: 2px 10px 6px 10px;
|
||||
padding: 4px 4px 4px 6px;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
color: @term-white;
|
||||
}
|
||||
}
|
||||
|
||||
.openai-renderer {
|
||||
|
@ -7,6 +7,7 @@ import { Main } from "./main";
|
||||
import { GlobalModel } from "./model";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { loadFonts } from "./util";
|
||||
import * as DOMPurify from "dompurify";
|
||||
|
||||
// @ts-ignore
|
||||
let VERSION = __PROMPT_VERSION__;
|
||||
@ -31,5 +32,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
(window as any).mobx = mobx;
|
||||
(window as any).sprintf = sprintf;
|
||||
(window as any).DOMPurify = DOMPurify;
|
||||
|
||||
console.log("PROMPT", VERSION, BUILD);
|
||||
|
218
src/view/mustache.tsx
Normal file
218
src/view/mustache.tsx
Normal file
@ -0,0 +1,218 @@
|
||||
import * as React from "react";
|
||||
import * as mobx from "mobx";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import cn from "classnames";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If, For, When, Otherwise, Choose } from "tsx-control-statements/components";
|
||||
import * as T from "../types";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { isBlank } from "../util";
|
||||
import mustache from "mustache";
|
||||
import * as DOMPurify from "dompurify";
|
||||
import { GlobalModel } from "../model";
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
|
||||
const MaxMustacheSize = 200000;
|
||||
|
||||
@mobxReact.observer
|
||||
class SimpleMustacheRenderer extends React.Component<
|
||||
{ data: Blob; context: T.RendererContext; opts: T.RendererOpts; savedHeight: number; lineState: T.LineStateType },
|
||||
{}
|
||||
> {
|
||||
templateLoading: OV<boolean> = mobx.observable.box(true, { name: "templateLoading" });
|
||||
templateLoadError: OV<string> = mobx.observable.box(null, { name: "templateLoadError" });
|
||||
dataLoading: OV<boolean> = mobx.observable.box(true, { name: "dataLoading" });
|
||||
dataLoadError: OV<string> = mobx.observable.box(null, { name: "dataLoadError" });
|
||||
mustacheTemplateText: OV<string> = mobx.observable.box(null, { name: "mustacheTemplateText" });
|
||||
parsedData: OV<any> = mobx.observable.box(null, { name: "parsedData" });
|
||||
|
||||
componentDidMount() {
|
||||
this.reloadTemplate();
|
||||
this.reloadData();
|
||||
}
|
||||
|
||||
reloadTemplate() {
|
||||
if (isBlank(this.props.lineState.template)) {
|
||||
mobx.action(() => {
|
||||
this.templateLoading.set(false);
|
||||
this.templateLoadError.set(`no 'template' specified`);
|
||||
})();
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.templateLoading.set(true);
|
||||
this.templateLoadError.set(null);
|
||||
})();
|
||||
let context = this.props.context;
|
||||
let lineState = this.props.lineState;
|
||||
let quotedTemplateName = JSON.stringify(lineState.template);
|
||||
let rtnp = GlobalModel.readRemoteFile(context.screenId, context.lineId, lineState.template);
|
||||
rtnp.then((file) => {
|
||||
if (file.notFound) {
|
||||
this.trySetTemplateLoadError(`mustache template ${quotedTemplateName} not found`);
|
||||
return null;
|
||||
}
|
||||
return file.text();
|
||||
})
|
||||
.then((text) => {
|
||||
if (isBlank(text)) {
|
||||
this.trySetTemplateLoadError(`blank mustache template ${quotedTemplateName}`);
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.mustacheTemplateText.set(text);
|
||||
this.templateLoading.set(false);
|
||||
})();
|
||||
return;
|
||||
})
|
||||
.catch((e) => {
|
||||
this.trySetTemplateLoadError(`loading mustache template ${quotedTemplateName}: ${e}`);
|
||||
});
|
||||
}
|
||||
|
||||
reloadData() {
|
||||
// load json content
|
||||
let dataBlob = this.props.data;
|
||||
if (dataBlob == null || dataBlob.notFound) {
|
||||
mobx.action(() => {
|
||||
this.dataLoading.set(false);
|
||||
this.dataLoadError.set(
|
||||
`file {dataBlob && dataBlob.name ? JSON.stringify(dataBlob.name) : ""} not found`
|
||||
);
|
||||
})();
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.dataLoading.set(true);
|
||||
this.dataLoadError.set(null);
|
||||
})();
|
||||
let rtnp = dataBlob.text();
|
||||
let quotedDataName = dataBlob.name || '"terminal output"';
|
||||
rtnp.then((text) => {
|
||||
mobx.action(() => {
|
||||
try {
|
||||
this.parsedData.set(JSON.parse(text));
|
||||
this.dataLoading.set(false);
|
||||
} catch (e) {
|
||||
this.trySetDataLoadError(`parsing json data from ${quotedDataName}: ${e}`);
|
||||
}
|
||||
})();
|
||||
}).catch((e) => {
|
||||
this.trySetDataLoadError(`loading json data ${quotedDataName}: ${e}`);
|
||||
});
|
||||
}
|
||||
|
||||
trySetTemplateLoadError(msg: string) {
|
||||
if (this.templateLoadError.get() != null) {
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.templateLoadError.set(msg);
|
||||
})();
|
||||
}
|
||||
|
||||
trySetDataLoadError(msg: string) {
|
||||
if (this.dataLoadError.get() != null) {
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.dataLoadError.set(msg);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
doRefresh() {
|
||||
this.reloadTemplate();
|
||||
}
|
||||
|
||||
renderCmdHints() {
|
||||
return (
|
||||
<div style={{ position: "absolute", bottom: "-3px", right: 0 }}>
|
||||
<div className="cmd-hints" style={{ minWidth: "6rem", maxWidth: "6rem", marginLeft: "-18px" }}>
|
||||
<div
|
||||
onClick={this.doRefresh}
|
||||
className={`hint-item refresh-button`}
|
||||
title="reload template and re-render content"
|
||||
>
|
||||
refresh
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let errorMessage = this.dataLoadError.get() ?? this.templateLoadError.get();
|
||||
if (errorMessage != null) {
|
||||
return (
|
||||
<div
|
||||
className="renderer-container mustache-renderer"
|
||||
style={{ fontSize: this.props.opts.termFontSize }}
|
||||
>
|
||||
<div className="load-error-text">ERROR: {errorMessage}</div>
|
||||
{this.renderCmdHints()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (this.templateLoading.get() || this.dataLoading.get()) {
|
||||
return (
|
||||
<div
|
||||
className="renderer-container mustache-renderer"
|
||||
style={{ fontSize: this.props.opts.termFontSize, height: this.props.savedHeight }}
|
||||
>
|
||||
<div className="renderer-loading">
|
||||
loading content <i className="fa fa-ellipsis fa-fade" />
|
||||
</div>
|
||||
{this.renderCmdHints()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let opts = this.props.opts;
|
||||
let maxWidth = opts.maxSize.width;
|
||||
let minWidth = opts.maxSize.width;
|
||||
if (minWidth > 1000) {
|
||||
minWidth = 1000;
|
||||
}
|
||||
let templateText = this.mustacheTemplateText.get();
|
||||
let templateData = this.parsedData.get() || {};
|
||||
let renderedText = null;
|
||||
try {
|
||||
renderedText = mustache.render(templateText, templateData);
|
||||
renderedText = DOMPurify.sanitize(renderedText);
|
||||
} catch (e) {
|
||||
return (
|
||||
<div
|
||||
className="renderer-container mustache-renderer"
|
||||
style={{ fontSize: this.props.opts.termFontSize }}
|
||||
>
|
||||
<div className="load-error-text">ERROR running template: {e.message}</div>
|
||||
{this.renderCmdHints()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// TODO non-term content font-size (default to 16)
|
||||
return (
|
||||
<div className="renderer-container mustache-renderer" style={{ fontSize: 16 }}>
|
||||
<div
|
||||
className="scroller"
|
||||
style={{
|
||||
maxHeight: opts.maxSize.height,
|
||||
minWidth: minWidth,
|
||||
width: "min-content",
|
||||
maxWidth: maxWidth,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="mustache content"
|
||||
style={{ maxHeight: opts.maxSize.height }}
|
||||
dangerouslySetInnerHTML={{ __html: renderedText }}
|
||||
/>
|
||||
</div>
|
||||
{this.renderCmdHints()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { SimpleMustacheRenderer };
|
@ -1,2 +1,2 @@
|
||||
const VERSION = "v0.3.0";
|
||||
const VERSION = "v0.3.1";
|
||||
module.exports = VERSION;
|
||||
|
@ -5628,6 +5628,11 @@ multicast-dns@^7.2.5:
|
||||
dns-packet "^5.2.2"
|
||||
thunky "^1.0.2"
|
||||
|
||||
mustache@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64"
|
||||
integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==
|
||||
|
||||
nanoid@^3.3.6:
|
||||
version "3.3.6"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
|
||||
|
Loading…
Reference in New Issue
Block a user