Compare commits

..

1 Commits

Author SHA1 Message Date
Evan Simkowitz 356b4151bf init 2024-05-03 13:42:31 -07:00
83 changed files with 1959 additions and 21052 deletions
-7
View File
@@ -16,10 +16,6 @@ jobs:
arch: "amd64"
runner: "ubuntu-latest"
scripthaus: "build-package-linux"
- platform: "linux"
arch: "arm64"
runner: ubuntu-24.04-arm64-16core
scripthaus: "build-package-linux"
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
@@ -32,8 +28,6 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends -y libarchive-tools libopenjp2-tools rpm
- name: Install FPM # The version of FPM that comes bundled with electron-builder doesn't include a Linux ARM target. Installing Gems onto the runner is super quick so we'll just do this for all targets.
run: sudo gem install fpm
- uses: actions/setup-go@v5
with:
go-version: ${{env.GO_VERSION}}
@@ -64,7 +58,6 @@ jobs:
run: scripthaus run ${{ matrix.scripthaus }}
env:
GOARCH: ${{ matrix.arch }}
USE_SYSTEM_FPM: true # Ensure that the installed version of FPM is used rather than the bundled one.
CSC_LINK: ${{ matrix.platform == 'darwin' && secrets.PROD_MACOS_CERTIFICATE}}
CSC_KEY_PASSWORD: ${{ matrix.platform == 'darwin' && secrets.PROD_MACOS_CERTIFICATE_PWD }}
APPLE_ID: ${{ matrix.platform == 'darwin' && secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
+66
View File
@@ -0,0 +1,66 @@
name: TestDriver.ai Regression Testing
on:
push:
branches:
- main
pull_request:
branches:
- main
schedule:
- cron: 0 21 * * *
workflow_dispatch: null
permissions:
contents: read # To allow the action to read repository contents
pull-requests: write # To allow the action to create/update pull request comments
jobs:
test:
name: TestDriver
runs-on: ubuntu-latest
steps:
- uses: dashcamio/testdriver@main
id: testdriver
with:
version: v2.10.2
prerun: |
rm ~/Desktop/WITH-LOVE-FROM-AMERICA.txt
cd ~/actions-runner/_work/testdriver/testdriver/
brew install go
brew tap scripthaus-dev/scripthaus
brew install corepack
brew install scripthaus
corepack enable
yarn install
scripthaus run build-backend
echo "Yarn"
yarn
echo "Rebuild"
scripthaus run electron-rebuild
echo "Webpack"
scripthaus run webpack-build
echo "Starting Electron"
scripthaus run electron 1>/dev/null 2>&1 &
echo "Electron Done"
exit
prompt: |
1. wait 10 seconds
1. click "Get Started"
1. validate that overlapping text does not appear in the application
1. focus the Wave input with the keyboard shorcut Command + I
1. type 'ls' into the input
1. press return
1. validate Wave shows the result of 'ls'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: peter-evans/create-or-update-comment@v4
if: ${{always()}}
with:
issue-number: ${{ github.event.pull_request.number }}
body: |
## TestDriver Summary
${{ steps.testdriver.outputs.markdown }}
${{ steps.testdriver.outputs.summary }}
reactions: |
+1
-1
-49
View File
@@ -1,49 +0,0 @@
name: TestDriver.ai Regression Testing - Waveterm
on:
push:
branches:
- main
pull_request:
branches:
- main
schedule:
- cron: 0 21 * * *
workflow_dispatch: null
permissions:
contents: read # To allow the action to read repository contents
pull-requests: write # To allow the action to create/update pull request comments
jobs:
test:
name: "TestDriver"
runs-on: ubuntu-latest
steps:
- uses: dashcamio/testdriver@main
id: testdriver
with:
key: ${{secrets.DASHCAM_API}}
os: mac
prerun: |
cd ~/actions-runner/_work/testdriver/testdriver/
brew install go
brew tap scripthaus-dev/scripthaus
brew install corepack
brew install scripthaus
corepack enable
yarn install
scripthaus run build-backend
echo "Yarn"
yarn
echo "Rebuild"
scripthaus run electron-rebuild
echo "Webpack"
scripthaus run webpack-build
echo "Starting Electron"
scripthaus run electron 1>/dev/null 2>&1 &
echo "Electron Done"
npm install testdriverai -g
npm install dashcam -g
exit
prompt: |
1. /run testdriver/theme.yml
+18
View File
@@ -0,0 +1,18 @@
rm ~/Desktop/WITH-LOVE-FROM-AMERICA.txt
cd ~/actions-runner/_work/testdriver/testdriver/
brew install go
brew tap scripthaus-dev/scripthaus
brew install scripthaus
corepack enable
yarn install
scripthaus run build-backend
echo "Yarn"
yarn
echo "Rebuild"
scripthaus run electron-rebuild
echo "Webpack"
scripthaus run webpack-build
echo "Starting Electron"
scripthaus run electron 1>/dev/null 2>&1 &
echo "Electron Done"
exit
+4 -4
View File
@@ -285,6 +285,10 @@
"keys": ["Ctrl:r"],
"commandStr": ["/history"]
},
{
"command": "cmdinput:openAIChat",
"keys": ["Ctrl:Space"]
},
{
"command": "history:closeHistory",
"keys": ["Ctrl:g", "Ctrl:c"]
@@ -328,9 +332,5 @@
{
"command": "codeedit:togglePreview",
"keys": ["Cmd:p"]
},
{
"command": "rightsidebar:toggle",
"keys": ["Cmd:Shift:Space"]
}
]
+10 -13
View File
@@ -34,23 +34,20 @@ You'll now have to move the built `scripthaus` binary to a directory in your pat
sudo cp scripthaus /usr/local/bin
```
## Install nodejs and yarn
## Install nodejs, npm, and yarn
You also need a relatively modern nodejs with npm and yarn installed.
We use [nvm](https://github.com/nvm-sh/nvm) to install nodejs on Linux (you can use an alternate installer if you wish). You must have a relatively recent version of node in order to build the terminal. Different distributions and shells will require different setup instructions. These instructions work for Ubuntu 22 using bash (will install node v20.8.1):
Node can be installed from [https://nodejs.org](https://nodejs.org).
```
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash
source ~/.bashrc
nvm install v20.8.1
```
We use Yarn Modern to manage our packages. The recommended way to install Yarn Modern is using Corepack, a new utility shipped by NodeJS that lets you manage your package manager versioning as you would any packages.
Now we can install yarn:
If you installed NodeJS from the official feed (via the website or using NVM), this should come preinstalled. If you use Homebrew or some other feed, you may need to manually install Corepack using `npm install -g corepack`.
For more information on Corepack, check out [this link](https://yarnpkg.com/corepack).
Once you've verified that you have Corepack installed, run the following script to set up Yarn for the repository:
```sh
corepack enable
yarn install
```
npm install -g yarn
```
## Clone the Wave Repo
-14710
View File
File diff suppressed because it is too large Load Diff
+52 -54
View File
@@ -6,7 +6,7 @@
},
"productName": "Wave",
"description": "An Open-Source, AI-Native, Terminal Built for Seamless Workflows",
"version": "0.7.6",
"version": "0.7.5",
"main": "dist/emain.js",
"license": "Apache-2.0",
"repository": {
@@ -18,95 +18,93 @@
"appId": "dev.commandline.waveterm"
},
"dependencies": {
"@lexical/react": "^0.17.0",
"@monaco-editor/react": "^4.6.0",
"@lexical/react": "^0.14.3",
"@monaco-editor/react": "^4.5.1",
"@table-nav/core": "^0.0.7",
"@table-nav/react": "^0.0.7",
"@tanstack/match-sorter-utils": "^8.19.4",
"@tanstack/react-table": "^8.20.1",
"@withfig/autocomplete": "^2.672.0",
"@tanstack/match-sorter-utils": "^8.8.4",
"@tanstack/react-table": "^8.10.3",
"autobind-decorator": "^2.4.0",
"base64-js": "^1.5.1",
"clsx": "^2.1.1",
"dayjs": "^1.11.12",
"dompurify": "^3.1.6",
"electron-squirrel-startup": "^1.0.1",
"electron-updater": "^6.3.2",
"framer-motion": "^10.18.0",
"dayjs": "^1.11.3",
"dompurify": "^3.0.2",
"electron-squirrel-startup": "^1.0.0",
"electron-updater": "^6.1.8",
"framer-motion": "^10.16.16",
"lexical": "0.14.5",
"mobx": "6.12.5",
"mobx-react": "^7.6.0",
"mobx": "6.12",
"mobx-react": "^7.5.0",
"monaco-editor": "0.48.0",
"mustache": "^4.2.0",
"node-fetch": "^3.3.2",
"overlayscrollbars": "^2.10.0",
"overlayscrollbars-react": "^0.5.6",
"node-fetch": "^3.2.10",
"overlayscrollbars": "^2.6.1",
"overlayscrollbars-react": "^0.5.5",
"papaparse": "^5.4.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-markdown": "^9.0.1",
"react": "^18.1.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.1.0",
"react-markdown": "^9.0.0",
"remark": "^15.0.1",
"remark-gfm": "^4.0.0",
"sprintf-js": "^1.1.3",
"throttle-debounce": "^5.0.2",
"tinycolor2": "^1.6.0",
"sprintf-js": "^1.1.2",
"throttle-debounce": "^5.0.0",
"tsx-control-statements": "^5.1.1",
"uuid": "^9.0.1",
"winston": "^3.13.1",
"uuid": "^9.0.0",
"winston": "^3.8.2",
"xterm": "^5.3.0",
"xterm-addon-serialize": "^0.11.0",
"xterm-addon-web-links": "^0.9.0",
"xterm-addon-webgl": "^0.16.0"
},
"devDependencies": {
"@babel/cli": "^7.24.8",
"@babel/core": "^7.25.2",
"@babel/cli": "^7.17.10",
"@babel/core": "^7.18.2",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.24.7",
"@babel/plugin-proposal-decorators": "^7.18.2",
"@babel/plugin-proposal-private-methods": "^7.18.6",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/plugin-transform-react-jsx": "^7.25.2",
"@babel/plugin-transform-runtime": "^7.24.7",
"@babel/preset-env": "^7.25.3",
"@babel/preset-react": "^7.24.7",
"@babel/preset-typescript": "^7.24.7",
"@babel/plugin-transform-react-jsx": "^7.17.12",
"@babel/plugin-transform-runtime": "^7.23.4",
"@babel/preset-env": "^7.18.2",
"@babel/preset-react": "^7.23.3",
"@babel/preset-typescript": "^7.17.12",
"@electron/rebuild": "^3.6.0",
"@svgr/webpack": "^8.1.0",
"@types/electron": "^1.6.10",
"@types/node": "^22.1.0",
"@types/papaparse": "^5.3.14",
"@types/react": "^18.3.3",
"@types/semver": "^7.5.8",
"@types/sprintf-js": "^1.1.4",
"@types/throttle-debounce": "^5.0.2",
"@types/tinycolor2": "^1.4.6",
"@types/uuid": "^9.0.8",
"@types/webpack-env": "^1.18.5",
"@withfig/autocomplete-types": "^1.31.0",
"@types/node": "^20.11.0",
"@types/papaparse": "^5.3.10",
"@types/react": "^18.0.12",
"@types/semver": "^7.5.6",
"@types/sprintf-js": "^1.1.3",
"@types/throttle-debounce": "^5.0.1",
"@types/uuid": "^9.0.7",
"@types/webpack-env": "^1.18.3",
"babel-loader": "^9.1.3",
"babel-plugin-jsx-control-statements": "^4.1.2",
"copy-webpack-plugin": "^12.0.2",
"css-loader": "^7.1.2",
"electron": "^31.3.1",
"copy-webpack-plugin": "^12.0.0",
"css-loader": "^7.1.0",
"electron": "^30.0.1",
"electron-builder": "^24.13.3",
"electron-builder-squirrel-windows": "25.0.0-alpha.10",
"electron-builder-squirrel-windows": "^24.13.3",
"file-loader": "^6.2.0",
"http-server": "^14.1.1",
"less": "^4.2.0",
"less-loader": "^12.2.0",
"less": "^4.1.2",
"less-loader": "^12.0.0",
"lodash-webpack-plugin": "^0.11.6",
"mini-css-extract-plugin": "^2.9.0",
"mini-css-extract-plugin": "^2.6.0",
"prettier": "^2.8.8",
"raw-loader": "^4.0.2",
"react-split-it": "^2.0.0",
"style-loader": "4.0.0",
"typescript": "^5.5.4",
"webpack": "^5.94.0",
"webpack-bundle-analyzer": "^4.10.2",
"typescript": "^5.0.0",
"webpack": "^5.73.0",
"webpack-bundle-analyzer": "^4.10.1",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.0.4",
"webpack-merge": "^5.10.0",
"yaml": "^2.5.0"
"webpack-merge": "^5.8.0",
"yaml": "^2.4.0"
},
"scripts": {
"postinstall": "electron-builder install-app-deps"
-1
View File
@@ -170,7 +170,6 @@
--cmdinput-button-bg-color: var(--tab-green);
--cmdinput-disabled-bg-color: var(--app-text-bg-disabled-color);
--cmdinput-history-bg-color: var(--app-bg-color);
--cmdinput-ghost-text-color: rgb(145, 150, 144);
/* screen view color */
--screen-view-text-caption-color: rgb(139, 145, 138);
-1
View File
@@ -50,7 +50,6 @@
--modal-header-bottom-border-color: rgba(0, 0, 0, 0.3);
/* cmd input */
--cmdinput-ghost-text-color: rgb(116, 116, 116);
/* scroll colors */
--scrollbar-background-color: var(--app-bg-color);
+9 -16
View File
@@ -31,20 +31,12 @@ class App extends React.Component<{}, {}> {
dcWait: OV<boolean> = mobx.observable.box(false, { name: "dcWait" });
mainContentRef: React.RefObject<HTMLDivElement> = React.createRef();
termThemesLoaded: OV<boolean> = mobx.observable.box(false, { name: "termThemesLoaded" });
chatFocusTimeoutId: NodeJS.Timeout = null;
constructor(props: {}) {
super(props);
if (GlobalModel.isDev) document.body.classList.add("is-dev");
}
componentWillUnmount() {
if (this.chatFocusTimeoutId) {
clearTimeout(this.chatFocusTimeoutId);
this.chatFocusTimeoutId = null;
}
}
@boundMethod
handleContextMenu(e: any) {
let isInNonTermInput = false;
@@ -76,15 +68,16 @@ class App extends React.Component<{}, {}> {
@boundMethod
openMainSidebar() {
GlobalModel.mainSidebarModel.setCollapsed(false);
const mainSidebarModel = GlobalModel.mainSidebarModel;
const width = mainSidebarModel.getWidth(true);
mainSidebarModel.saveState(width, false);
}
@boundMethod
openRightSidebar() {
GlobalModel.rightSidebarModel.setCollapsed(false);
this.chatFocusTimeoutId = setTimeout(() => {
GlobalModel.inputModel.setChatSidebarFocus();
}, 100);
const rightSidebarModel = GlobalModel.rightSidebarModel;
const width = rightSidebarModel.getWidth(true);
rightSidebarModel.saveState(width, false);
}
@boundMethod
@@ -166,13 +159,13 @@ class App extends React.Component<{}, {}> {
</div>
</div>
</If>
<If condition={rightSidebarCollapsed && activeMainView == "session"}>
<div className="right-sidebar-triggers" title="Open Wave AI (Cmd-Shift-Space)">
<If condition={GlobalModel.isDev && rightSidebarCollapsed && activeMainView == "session"}>
<div className="right-sidebar-triggers">
<Button
className="secondary ghost right-sidebar-trigger"
onClick={this.openRightSidebar}
>
<i className="fa-sharp fa-regular fa-sparkles"></i>
<i className="fa-sharp fa-solid fa-sidebar-flip"></i>
</Button>
</div>
</If>
-1
View File
@@ -72,4 +72,3 @@ export const ErrorCode_InvalidCwd = "ERRCWD";
export const InputAuxView_History = "history";
export const InputAuxView_Info = "info";
export const InputAuxView_AIChat = "aichat";
export const InputAuxView_Suggestions = "suggestions";
+2 -33
View File
@@ -11,10 +11,10 @@ import { Toggle, InlineSettingsTextEdit, SettingsError, Dropdown } from "@/commo
import { commandRtnHandler, isBlank } from "@/util/util";
import { getTermThemes } from "@/util/themeutil";
import * as appconst from "@/app/appconst";
import { MainView } from "@/common/elements/mainview";
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
import "./clientsettings.less";
import { MainView } from "../common/elements/mainview";
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
class ClientSettingsKeybindings extends React.Component<{}, {}> {
componentDidMount() {
@@ -110,19 +110,6 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
GlobalModel.getElectronApi().changeAutoUpdate(val);
}
@boundMethod
handleChangeAutocompleteEnabled(val: boolean): void {
const prtn: Promise<CommandRtnType> = GlobalCommandRunner.setAutocompleteEnabled(val);
commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
handleChangeAutocompleteDebuggingEnabled(val: boolean): void {
mobx.action(() => {
GlobalModel.autocompleteModel.loggingEnabled = val;
})();
}
getFontSizes(): DropdownItem[] {
const availableFontSizes: DropdownItem[] = [];
for (let s = appconst.MinFontSize; s <= appconst.MaxFontSize; s++) {
@@ -460,24 +447,6 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
/>
</div>
</div>
<div className="settings-field">
<div className="settings-label">Command Autocomplete</div>
<div className="settings-input">
<Toggle
checked={cdata.clientopts.autocompleteenabled ?? false}
onChange={this.handleChangeAutocompleteEnabled}
/>
</div>
</div>
<div className="settings-field">
<div className="settings-label">Command Autocomplete Debugging</div>
<div className="settings-input">
<Toggle
checked={GlobalModel.autocompleteModel.loggingEnabled}
onChange={this.handleChangeAutocompleteDebuggingEnabled}
/>
</div>
</div>
<SettingsError errorMessage={this.errorMessage} />
</div>
</MainView>
+2 -8
View File
@@ -3,13 +3,11 @@ import { Button } from "./button";
import { boundMethod } from "autobind-decorator";
import * as mobxReact from "mobx-react";
import * as mobx from "mobx";
import { clsx } from "clsx";
import "./copybutton.less";
type CopyButtonProps = {
title: string;
className?: string;
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
};
@@ -36,14 +34,10 @@ class CopyButton extends React.Component<CopyButtonProps, {}> {
}
render() {
const { title, className } = this.props;
const { title, onClick } = this.props;
const isCopied = this.isCopied.get();
return (
<Button
onClick={this.handleOnClick}
className={clsx("copy-button secondary ghost", className)}
title={title}
>
<Button onClick={this.handleOnClick} className="copy-button secondary ghost" title={title}>
{isCopied ? (
<i className="fa-sharp fa-solid fa-check"></i>
) : (
-1
View File
@@ -19,4 +19,3 @@ export { TabIcon } from "./tabicon";
export { DatePicker } from "./datepicker";
export { TermStyleList } from "./termstyle";
export { CopyButton } from "./copybutton";
export { TypingIndicator } from "./typingindicator";
+14 -57
View File
@@ -48,69 +48,30 @@
pre {
background-color: var(--markdown-bg-color);
margin: 4px 10px;
padding: 0.4em 0.7em;
margin: 4px 10px 4px 10px;
padding: 0.7em;
border-radius: 4px;
position: relative;
code {
line-height: 1.5;
white-space: pre-wrap;
word-wrap: break-word;
overflow: auto;
overflow: hidden;
background-color: transparent;
padding: 0;
line-height: normal;
}
.codeblock-actions {
display: none;
position: absolute;
top: 4px;
right: 4px;
border-radius: 4px;
align-items: center;
justify-content: flex-end;
padding-left: 4px;
padding-right: 4px;
background-color: var(--line-actions-bg-color);
backdrop-filter: blur(8px);
i {
color: var(--line-actions-inactive-color);
&:first-child {
margin-right: 4px;
}
&:hover {
color: var(--line-actions-active-color);
}
&.fa-check {
color: var(--app-success-color);
}
&.fa-square-terminal {
cursor: pointer;
}
}
}
&:hover .codeblock-actions {
display: flex;
}
}
code {
color: var(--app-text-color);
background-color: var(--markdown-bg-color);
font-family: var(--termfontfamily);
border-radius: 4px;
}
pre.selected {
outline: 2px solid var(--markdown-outline-color);
}
code {
color: var(--app-text-color);
font-family: var(--termfontfamily);
border-radius: 4px;
background-color: var(--markdown-bg-color);
padding: 0.15em 0.5em;
line-height: 1.5;
}
.title {
font-weight: semibold;
padding-top: 6px;
@@ -140,10 +101,6 @@
}
}
.markdown.content {
line-height: 1.5;
}
.markdown > *:first-child {
margin-top: 0 !important;
}
+75 -65
View File
@@ -5,14 +5,14 @@ import * as React from "react";
import * as mobxReact from "mobx-react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { CopyButton } from "@/elements";
import { clsx } from "clsx";
import * as mobx from "mobx";
import { If } from "tsx-control-statements/components";
import { GlobalModel } from "@/models";
import { v4 as uuidv4 } from "uuid";
import "./markdown.less";
import { boundMethod } from "autobind-decorator";
function Link(props: any): JSX.Element {
function LinkRenderer(props: any): any {
let newUrl = "https://extern?" + encodeURIComponent(props.href);
return (
<a href={newUrl} target="_blank" rel={"noopener"}>
@@ -21,86 +21,96 @@ function Link(props: any): JSX.Element {
);
}
function Header(props: any, hnum: number): JSX.Element {
function HeaderRenderer(props: any, hnum: number): any {
return <div className={clsx("title", "is-" + hnum)}>{props.children}</div>;
}
function Code(props: any): JSX.Element {
function CodeRenderer(props: any): any {
return <code>{props.children}</code>;
}
const CodeBlock = mobxReact.observer(
(props: { children: React.ReactNode; onClickExecute?: (cmd: string) => void }): JSX.Element => {
const copied: OV<boolean> = mobx.observable.box(false, { name: "copied" });
@mobxReact.observer
class CodeBlockMarkdown extends React.Component<
{ children: React.ReactNode; codeSelectSelectedIndex?: number; uuid: string },
{}
> {
blockIndex: number;
blockRef: React.RefObject<HTMLPreElement>;
const getTextContent = (children: any) => {
if (typeof children === "string") {
return children;
} else if (Array.isArray(children)) {
return children.map(getTextContent).join("");
} else if (children.props && children.props.children) {
return getTextContent(children.props.children);
}
return "";
constructor(props) {
super(props);
this.blockRef = React.createRef();
this.blockIndex = GlobalModel.inputModel.addCodeBlockToCodeSelect(this.blockRef, this.props.uuid);
}
render() {
let clickHandler: (e: React.MouseEvent<HTMLElement>, blockIndex: number) => void;
let inputModel = GlobalModel.inputModel;
clickHandler = (e: React.MouseEvent<HTMLElement>, blockIndex: number) => {
inputModel.setCodeSelectSelectedCodeBlock(blockIndex);
};
const handleCopy = async (e: React.MouseEvent) => {
let textToCopy = getTextContent(props.children);
textToCopy = textToCopy.replace(/\n$/, ""); // remove trailing newline
await navigator.clipboard.writeText(textToCopy);
copied.set(true);
setTimeout(() => copied.set(false), 2000); // Reset copied state after 2 seconds
};
const handleExecute = (e: React.MouseEvent) => {
let textToCopy = getTextContent(props.children);
textToCopy = textToCopy.replace(/\n$/, ""); // remove trailing newline
if (props.onClickExecute) {
props.onClickExecute(textToCopy);
return;
}
};
let selected = this.blockIndex == this.props.codeSelectSelectedIndex;
return (
<pre className="codeblock">
{props.children}
<div className="codeblock-actions">
<CopyButton className="copy-button" onClick={handleCopy} title="Copy" />
<If condition={props.onClickExecute}>
<i className="fa-regular fa-square-terminal" onClick={handleExecute}></i>
</If>
</div>
<pre
ref={this.blockRef}
className={clsx({ selected: selected })}
onClick={(event) => clickHandler(event, this.blockIndex)}
>
{this.props.children}
</pre>
);
}
);
}
@mobxReact.observer
class Markdown extends React.Component<
{
text: string;
style?: any;
className?: string;
onClickExecute?: (cmd: string) => void;
},
{ text: string; style?: any; extraClassName?: string; codeSelect?: boolean },
{}
> {
render() {
let { text, className, onClickExecute } = this.props;
let markdownComponents = {
a: Link,
h1: (props) => <Header {...props} hnum={1} />,
h2: (props) => <Header {...props} hnum={2} />,
h3: (props) => <Header {...props} hnum={3} />,
h4: (props) => <Header {...props} hnum={4} />,
h5: (props) => <Header {...props} hnum={5} />,
h6: (props) => <Header {...props} hnum={6} />,
code: Code,
pre: (props) => <CodeBlock {...props} onClickExecute={onClickExecute} />,
};
curUuid: string;
constructor(props) {
super(props);
this.curUuid = uuidv4();
}
@boundMethod
CodeBlockRenderer(props: any, codeSelect: boolean, codeSelectIndex: number, curUuid: string): any {
if (codeSelect) {
return (
<CodeBlockMarkdown codeSelectSelectedIndex={codeSelectIndex} uuid={curUuid}>
{props.children}
</CodeBlockMarkdown>
);
} else {
const clickHandler = (e: React.MouseEvent<HTMLElement>) => {
let blockText = (e.target as HTMLElement).innerText;
if (blockText) {
blockText = blockText.replace(/\n$/, ""); // remove trailing newline
navigator.clipboard.writeText(blockText);
}
};
return <pre onClick={(event) => clickHandler(event)}>{props.children}</pre>;
}
}
render() {
let text = this.props.text;
let codeSelect = this.props.codeSelect;
let curCodeSelectIndex = GlobalModel.inputModel.getCodeSelectSelectedIndex();
let markdownComponents = {
a: LinkRenderer,
h1: (props) => HeaderRenderer(props, 1),
h2: (props) => HeaderRenderer(props, 2),
h3: (props) => HeaderRenderer(props, 3),
h4: (props) => HeaderRenderer(props, 4),
h5: (props) => HeaderRenderer(props, 5),
h6: (props) => HeaderRenderer(props, 6),
code: (props) => CodeRenderer(props),
pre: (props) => this.CodeBlockRenderer(props, codeSelect, curCodeSelectIndex, this.curUuid),
};
return (
<div className={clsx("markdown content", className)} style={this.props.style}>
<div className={clsx("markdown content", this.props.extraClassName)} style={this.props.style}>
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
{text}
</ReactMarkdown>
@@ -1,43 +0,0 @@
@dot-width: 11px;
@dot-color: var(--app-success-color);
@speed: 1.5s;
.typing {
position: relative;
height: @dot-width;
span {
content: "";
animation: blink @speed infinite;
animation-fill-mode: both;
height: @dot-width;
width: @dot-width;
background: @dot-color;
position: absolute;
left: 0;
top: 0;
border-radius: 50%;
&:nth-child(2) {
animation-delay: 0.2s;
margin-left: @dot-width * 1.5;
}
&:nth-child(3) {
animation-delay: 0.4s;
margin-left: @dot-width * 3;
}
}
}
@keyframes blink {
0% {
opacity: 0.1;
}
20% {
opacity: 1;
}
100% {
opacity: 0.1;
}
}
@@ -1,19 +0,0 @@
import React from "react";
import { clsx } from "clsx";
import "./typingindicator.less";
type Props = {
className?: string;
};
const TypingIndicator: React.FC<Props> = (props: { className?: string }) => {
return (
<div className={clsx("typing", props.className)}>
<span></span>
<span></span>
<span></span>
</div>
);
};
export { TypingIndicator };
+2
View File
@@ -135,12 +135,14 @@ export const StatusIndicator: React.FC<StatusIndicatorProps> = (props) => {
*/
React.useEffect(() => {
if (runningCommands && !timeoutState) {
console.log("show spinner");
setTimeoutState(
setTimeout(() => {
setSpinnerVisible(true);
}, 100)
);
} else if (!runningCommands) {
console.log("clear spinner");
clearSpinnerTimeout();
}
return () => {

Some files were not shown because too many files have changed in this diff Show More