From 957a1e405979488828b43bc749d83e08a89e10bc Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 5 May 2023 13:00:45 -0700 Subject: [PATCH] make lineavatar smaller --- src/linecomps.tsx | 279 ++++++++++++++++++++++++++++++++++++++++-- src/lines.less | 90 ++++++++++++-- src/ptydata.ts | 68 +++++++++- src/types.ts | 2 +- src/view/markdown.tsx | 2 +- 5 files changed, 420 insertions(+), 21 deletions(-) diff --git a/src/linecomps.tsx b/src/linecomps.tsx index 4f4684ae..9a56cf96 100644 --- a/src/linecomps.tsx +++ b/src/linecomps.tsx @@ -16,6 +16,7 @@ import {renderCmdText} from "./elements"; import {SimpleBlobRendererModel, SimpleBlobRenderer} from "./simplerenderer"; import {isBlank} from "./util"; import {PluginModel} from "./plugins"; +import {PtyDataBuffer} from "./ptydata"; import * as lineutil from "./lineutil"; dayjs.extend(localizedFormat) @@ -107,6 +108,237 @@ class LineAvatar extends React.Component<{line : LineType, cmd : Cmd, onRightCli } } +@mobxReact.observer +class SmallLineAvatar extends React.Component<{line : LineType, cmd : Cmd, onRightClick? : (e : any) => void}, {}> { + render() { + let {line, cmd} = this.props; + let lineNumStr = (line.linenumtemp ? "~" : "#") + String(line.linenum); + let status = (cmd != null ? cmd.getStatus() : "done"); + let rtnstate = (cmd != null ? cmd.getRtnState() : false); + let isComment = (line.linetype == "text"); + let icon : string = null; + let iconTitle = null; + if (isComment) { + icon = "fa-comment"; + iconTitle = "comment"; + } + else if (status == "done") { + icon = "fa-check"; + iconTitle = "done"; + } + else if (status == "hangup" || status == "error") { + icon = "fa-triangle-exclamation"; + iconTitle = status; + } + else if (status == "running" || "detached") { + icon = "fa-rotate fa-spin"; + iconTitle = "running"; + } + else { + icon = "fa-square-question"; + iconTitle = "unknown"; + } + return ( +
+ {lineNumStr} +
+ ); + } +} + +@mobxReact.observer +class LineOpenAI extends React.Component<{screen : LineContainerModel, line : LineType, width : number, staticRender : boolean, visible : OV, onHeightChange : LineHeightChangeCallbackType, topBorder : boolean, renderMode : RenderModeType, overrideCollapsed : OV, noSelect? : boolean, showHints? : boolean}, {}> { + dataBuffer : PtyDataBuffer = new PtyDataBuffer(); + loading : OV = mobx.observable.box(null, {name: "loading"}); + loadError : OV = mobx.observable.box(null, {name: "loadError"}); + dataLines : OArr = mobx.observable.array([], {name: "dataLines"}); + dataPos : number = 0; + lineRef : React.RefObject = React.createRef(); + + renderSimple() { + let {screen, line, topBorder, width} = this.props; + let cmd = screen.getCmd(line); + let usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width); + let height = 36 + usedRows; + let formattedTime = lineutil.getLineDateTimeStr(line.ts); + let mainDivCn = cn( + "line", + "line-openai", + "line-simple", + {"top-border": topBorder}, + ); + return ( +
+ +
{formattedTime}
+
+ ); + } + + componentDidMount() { + this.reload(0); + } + + updateLines() : void { + + } + + reload(delayMs : number) { + let {line} = this.props; + + mobx.action(() => { + this.loading.set(true); + this.dataLines.clear(); + })(); + let rtnp = getTermPtyData(lineutil.getRendererContext(line)); + if (rtnp == null) { + console.log("no promise returned from ptyDataSource (simplerenderer)", this.context); + return; + } + rtnp.then((ptydata) => { + setTimeout(() => { + this.dataPos = 0; + this.dataBuffer.reset(); + this.dataBuffer.receiveData(ptydata.pos, ptydata.data, "reload"); + mobx.action(() => { + this.loadError.set(null); + })(); + }, delayMs); + }).catch((e) => { + console.log("error loading data", e); + mobx.action(() => { + this.loadError.set("error loading data: " + e); + })(); + }).finally(() => { + mobx.action(() => { + this.loading.set(false); + })(); + }); + } + + @boundMethod + handleClick() { + } + + @boundMethod + onAvatarRightClick(e : any) { + this.handleLineSettings(e) + } + + @boundMethod + handleLineSettings(e : any) : void { + let {line, noSelect} = this.props; + if (noSelect) { + return; + } + e.preventDefault(); + e.stopPropagation(); + if (line != null) { + mobx.action(() => { + GlobalModel.lineSettingsModal.set(line.linenum); + })(); + } + } + + renderMetaWrap(cmd : Cmd) { + let {line} = this.props; + let model = GlobalModel; + let formattedTime = lineutil.getLineDateTimeStr(line.ts); + let termOpts = cmd.getTermOpts(); + let remote = model.getRemote(cmd.remoteId); + let renderer = line.renderer; + return ( +
+
+
{formattedTime}
+
 
+
openai 
+
+ ({termOpts.rows}x{termOpts.cols}) +
+
+ +
+
+
+ ); + } + + renderPrompt(cmd : Cmd) { + let cmdStr = cmd.getCmdStr().trim(); + if (cmdStr.startsWith("/openai")) { + let spaceIdx = cmdStr.indexOf(" "); + if (spaceIdx > 0) { + cmdStr = cmdStr.substr(spaceIdx+1).trim(); + } + } + return ( +
+ [user] +
{cmdStr}
+
+ ); + } + + renderOutput(cmd : Cmd) { + let output = "...\nhello\nmore"; + return ( +
+
[assistant]
+
{output}
+
+ ); + } + + render() { + let {screen, line, width, staticRender, visible, topBorder, renderMode} = this.props; + let model = GlobalModel; + let lineid = line.lineid; + let isVisible = visible.get(); + if (staticRender || !isVisible) { + return this.renderSimple(); + } + let formattedTime = lineutil.getLineDateTimeStr(line.ts); + let cmd = screen.getCmd(line); + if (cmd == null) { + return ( +
+ [cmd not found '{line.cmdid}'] +
+ ); + } + let status = cmd.getStatus(); + let lineNumStr = (line.linenumtemp ? "~" : "") + String(line.linenum); + let isSelected = mobx.computed(() => (screen.getSelectedLine() == line.linenum), {name: "computed-isSelected"}).get(); + let isFocused = mobx.computed(() => { + let screenFocusType = screen.getFocusType(); + return isSelected && (screenFocusType == "cmd"); + }, {name: "computed-isFocused"}).get(); + let isStatic = staticRender; + let isRunning = cmd.isRunning() + let mainDivCn = cn( + "line", + "line-openai", + {"focus": isFocused}, + {"cmd-done": !isRunning}, + {"has-rtnstate": cmd.getRtnState()}, + {"top-border": topBorder}, + ); + return ( +
+
+
+ + {this.renderMetaWrap(cmd)} +
+ {this.renderPrompt(cmd)} + {this.renderOutput(cmd)} +
+ ); + } +} + @mobxReact.observer class LineCmd extends React.Component<{screen : LineContainerModel, line : LineType, width : number, staticRender : boolean, visible : OV, onHeightChange : LineHeightChangeCallbackType, topBorder : boolean, renderMode : RenderModeType, overrideCollapsed : OV, noSelect? : boolean, showHints? : boolean}, {}> { lineRef : React.RefObject = React.createRef(); @@ -182,7 +414,7 @@ class LineCmd extends React.Component<{screen : LineContainerModel, line : LineT })(); } - renderCmdText(cmd : Cmd, remote : RemoteType) : any { + renderCmdText(cmd : Cmd) : any { if (cmd == null) { return (
@@ -372,14 +604,17 @@ class LineCmd extends React.Component<{screen : LineContainerModel, line : LineT let usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width); height = 36 + usedRows; } + let formattedTime = lineutil.getLineDateTimeStr(line.ts); let mainDivCn = cn( "line", "line-cmd", + "line-simple", {"top-border": topBorder}, ); return (
- + +
{formattedTime}
); } @@ -396,16 +631,39 @@ class LineCmd extends React.Component<{screen : LineContainerModel, line : LineT } } + renderMeta1(cmd : Cmd) { + let {line} = this.props; + let termOpts = cmd.getTermOpts(); + let formattedTime = lineutil.getLineDateTimeStr(line.ts); + let renderer = line.renderer; + return ( +
+ +
{formattedTime}
+
 
+ +
{renderer} 
+
+
+ ({termOpts.rows}x{termOpts.cols}) +
+
+ +
+
+ ); + } + renderMetaWrap(cmd : Cmd) { let {line} = this.props; let model = GlobalModel; let formattedTime = lineutil.getLineDateTimeStr(line.ts); let termOpts = cmd.getTermOpts(); - let remote = model.getRemote(cmd.remoteId); let renderer = line.renderer; return (
+
{formattedTime}
 
@@ -418,7 +676,7 @@ class LineCmd extends React.Component<{screen : LineContainerModel, line : LineT
- {this.renderCmdText(cmd, remote)} + {this.renderCmdText(cmd)}
); } @@ -517,8 +775,10 @@ class LineCmd extends React.Component<{screen : LineContainerModel, line : LineT data-lineid={line.lineid} data-linenum={line.linenum} data-screenid={line.screenid} data-cmdid={line.cmdid}>
- - {this.renderMetaWrap(cmd)} +
+ {this.renderMeta1(cmd)} + {this.renderCmdText(cmd)} +
@@ -565,9 +825,12 @@ class Line extends React.Component<{screen : LineContainerModel, line : LineType if (line.linetype == "text") { return ; } - if (line.linetype == "cmd" || line.linetype == "openai") { + if (line.linetype == "cmd") { return ; } + if (line.linetype == "openai") { + return ; + } return
[invalid line type '{line.linetype}']
; } } @@ -661,9 +924,9 @@ class LineText extends React.Component<{screen : LineContainerModel, line : Line return (
-
+
{formattedTime}
diff --git a/src/lines.less b/src/lines.less index f0035a96..0f404b13 100644 --- a/src/lines.less +++ b/src/lines.less @@ -21,6 +21,42 @@ } } +.line.line-openai { + flex-direction: column; + position: relative; + + .line-header { + display: flex; + flex-direction: row; + height: 18px; + width: 100%; + + .line-icon { + display: block; + cursor: pointer; + padding: 3px; + font-size: 1.5rem; + } + } + + .openai-message { + display: flex; + flex-direction: row; + justify-content: flex-start; + + .openai-role { + color: @term-green; + font-weight: bold; + width: 100px; + } + + .openai-content { + white-space: pre; + color: white; + } + } +} + .line.line-cmd { flex-direction: column; scroll-margin-bottom: 20px; @@ -37,15 +73,6 @@ height: 36px; width: 100%; - .meta-wrap { - flex: 1 1 0px; - min-width: 0; - display: flex; - flex-direction: column; - justify-content: flex-start; - margin-left: 10px; - } - .line-icon { display: block; visibility: hidden; @@ -168,7 +195,7 @@ .line { margin: 0px 5px 0px 5px; - padding: 10px 5px 0px 12px; + padding: 5px 5px 0px 12px; display: flex; line-height: 1.25; overflow: hidden; @@ -180,6 +207,37 @@ top: 5px; } + &.line-simple { + flex-direction: row; + + .ts { + display: flex; + color: #aaa; + .mono-font(11px); + } + } + + .simple-line-status { + .mono-font(12px); + color: #aaa; + + i { + padding-left: 6px; + padding-right: 6px; + } + + .linenum { + cursor: pointer; + } + + &.has-rtnstate { + .linenum { + color: white; + font-weight: bold; + } + } + } + &.top-border { border-top: 1px solid #777; padding-top: 5px; @@ -283,12 +341,24 @@ } } + .meta-wrap { + flex: 1 1 0px; + min-width: 0; + display: flex; + flex-direction: column; + justify-content: flex-start; + } + .meta { display: flex; flex-direction: row; font-size: 1rem; margin-top: -4px; + .simple-line-status { + margin-top: 5px; + } + .user { color: lighten(@soft-blue, 10%); font-weight: bold; diff --git a/src/ptydata.ts b/src/ptydata.ts index 3e8ea44b..ac395c91 100644 --- a/src/ptydata.ts +++ b/src/ptydata.ts @@ -1,6 +1,10 @@ import * as mobx from "mobx"; import {incObs} from "./util"; +type OV = mobx.IObservableValue; +type OArr = mobx.IObservableArray; +type OMap = mobx.ObservableMap; + const InitialSize = 10*1024; const IncreaseFactor = 1.5; @@ -55,4 +59,66 @@ class PtyDataBuffer { } } -export {PtyDataBuffer}; +const NewLineCharCode = "\n".charCodeAt(0); + +class PacketDataBuffer extends PtyDataBuffer { + parsePos : number; + packets : OArr; + + constructor() { + super(); + this.parsePos = 0; + this.packets = mobx.observable.array([], {name: "packets"}); + } + + processLine(line : string) { + if (line.length == 0) { + return; + } + if (!line.startsWith("##")) { + console.log("invalid line packet", line); + return; + } + let bracePos = line.indexOf("{"); + if (bracePos == -1) { + console.log("invalid line packet", line); + return; + } + let packetStr = line.substring(bracePos); + let sizeStr = line.substring(2, bracePos); + if (sizeStr != "N") { + let packetSize = parseInt(sizeStr); + if (isNaN(packetSize) || packetSize != packetStr.length) { + console.log("invalid line packet", line); + } + } + try { + let packet = JSON.parse(packetStr); + this.packets.push(packet); + } + catch (e) { + console.log("invalid line packet (bad json)", line); + return; + } + } + + parseData() { + for (let i=this.parsePos; i -
+