Skip to content
This repository has been archived by the owner on Sep 23, 2024. It is now read-only.

fix: hovering on a text should stop the text from updating itself #117

Merged
merged 1 commit into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 50 additions & 22 deletions src/home/chat-window/compnent/my-text.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {memo, useEffect, useState} from 'react'
import React, {memo, useCallback, useEffect, useState} from 'react'
import {cx, formatAgo} from "../../../util/util.tsx"
import {Message} from "../../../data-structure/message.tsx"
import {MySpin} from "./widget/icon.tsx"
Expand Down Expand Up @@ -26,22 +26,27 @@ import mifoot from "markdown-it-footnote"
import './widget/highlightjs-plugins/copy-button-plugin.css'
import {LanguageLabelPlugin} from "./widget/highlightjs-plugins/language-label-plugin.tsx";
import {CopyButtonPlugin} from "./widget/highlightjs-plugins/copy-button-plugin.tsx";
import {debounce, throttle} from "lodash";
import {useSnapshot} from "valtio/react";
import {appState} from "../../../state/app-state.ts";
import {controlState} from "../../../state/control-state.ts";
import {debounce, throttle} from "lodash";

hljs.configure({
ignoreUnescapedHTML: true,
});
hljs.addPlugin(new LanguageLabelPlugin());
hljs.addPlugin(new CopyButtonPlugin());

const ha = throttle(debounce(() => hljs.highlightAll(), 1000, {
'trailing': true
}), 1000);
// debounce and throttle function only works as being singleton
const haDebounce = debounce(hljs.highlightAll, 0.5, {
leading: true,
trailing: true
})

const haNow = () => hljs.highlightAll()
const haThrottle = throttle(hljs.highlightAll, 0.5, {
leading: true,
trailing: true
})

interface TextProps {
messageSnap: Message
Expand All @@ -54,16 +59,19 @@ export const MyText: React.FC<TextProps> = ({messageSnap, theme}) => {
const {showMarkdown} = useSnapshot(appState.pref)
const {isWindowsBlurred} = useSnapshot(controlState)
const [text, setText] = useState(messageSnap.text)
const [latestText, setLatestText] = useState<string | undefined>()
const [hovering, setHovering] = useState(false)

// stop updating text when mouse hovering
const haNow = useCallback(() => showMarkdown && hljs.highlightAll(), [showMarkdown])

const haTh = useCallback(() => showMarkdown && haThrottle(), [showMarkdown])

const haDe = useCallback(() => showMarkdown && haDebounce(), [showMarkdown])

// run after text first loading
useEffect(() => {
if (!hovering) {
controlState.isTextPending = false
} else {
controlState.isTextPending = messageSnap.status === "typing"
}
}, [messageSnap.status, hovering]);
haDe()
}, [haDe]);

useEffect(() => {
if (isWindowsBlurred) {
Expand All @@ -72,24 +80,44 @@ export const MyText: React.FC<TextProps> = ({messageSnap, theme}) => {
}, [isWindowsBlurred]);

useEffect(() => {
if (!controlState.isTextPending) {
setText(messageSnap.text)
ha()
setLatestText(messageSnap.text)
}, [messageSnap.text]);

useEffect(() => {
if (!hovering && latestText && latestText != text) {
// if user is moving the cursor away and there is text in the buffer
setText(latestText)
if (messageSnap.status == "typing") {
haTh()
} else {
// highlight after latestText is added to the UI
setTimeout(haNow)
}
}
}, [messageSnap]);
}, [hovering, latestText, messageSnap.status, haNow, text, haTh]);

useEffect(() => {
// apply highlight plugin immediately after message is fully received
if (messageSnap.status === 'received') {
haNow()
hovering && messageSnap.status == "typing" && haNow()
}, [hovering, messageSnap.status, haNow])

useEffect(() => {
if (hovering) {
if (messageSnap.status === "typing") {
controlState.textPendingState = "pending"
return
} else if (latestText && latestText !== text) {
controlState.textPendingState = "done"
return;
}
}
}, [messageSnap.status]);
controlState.textPendingState = "none"
}, [hovering, latestText, messageSnap.status, text]);

return <div
className={cx("flex flex-col rounded-2xl px-3 pt-1.5 pb-0.5",
theme.text, theme.bg
)}
onMouseEnter={() => setHovering(true)}
onMouseOver={() => setHovering(true)}
onMouseLeave={() => setHovering(false)}
>

Expand Down
17 changes: 9 additions & 8 deletions src/home/chat-window/text-area.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {bestModel, matchKeyCombo} from "../../state/shortcuts.ts";
import IosSpinner from "../../assets/svg/ios-spinner.svg?react"
import {modelInUse} from "../../data-structure/client-option.tsx";
import {PopOverDot} from "./compnent/dot.tsx";
import {FaCheckCircle} from "react-icons/fa";

type Props = {
chatProxy: Chat
Expand Down Expand Up @@ -171,7 +172,6 @@ const TextArea: React.FC<Props> = ({chatProxy}) => {
}
}, [inputAreaIsLarge])


return (<div className="flex flex-col items-center w-full mt-auto bottom-0 max-w-4xl">
<div className="relative flex items-center justify-center w-full z-10">
<div className="absolute left-2 flex items-center gap-1.5">
Expand All @@ -191,7 +191,8 @@ const TextArea: React.FC<Props> = ({chatProxy}) => {
<PopOverDot text="MD" popOverText="Display messages in markdown style" activated={showMarkdown}
action={() => appState.pref.showMarkdown = !appState.pref.showMarkdown}/>

{currentModel && <PopOverDot text={currentModel} fixedRound={false} popOverText="Lanuage model in use"/>}
{currentModel &&
<PopOverDot text={currentModel} fixedRound={false} popOverText="Lanuage model in use"/>}
<TextPendingIcon/>
</div>
<button
Expand Down Expand Up @@ -243,15 +244,15 @@ const TextArea: React.FC<Props> = ({chatProxy}) => {

const TextPendingIcon = () => {

const {isTextPending} = useSnapshot(controlState)
const {textPendingState} = useSnapshot(controlState)

return (
<div className={cx("flex rounded-full w-5 h-5 items-center",
"select-none transform duration-200 ",
isTextPending ? "opacity-100" : "opacity-0")}>
<IosSpinner className={"stroke-white"}/>
<div className={cx("flex rounded-full w-4 h-4 items-center",
"select-none transition duration-200",
textPendingState == "none" ? "opacity-0" : "opacity-100")}>
{textPendingState == "pending" && <IosSpinner className={"stroke-white"}/>}
{textPendingState == "done" && <FaCheckCircle className={"fill-white"}/>}
</div>

)
}

Expand Down
4 changes: 2 additions & 2 deletions src/state/control-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type ControlState = {
isMouseLeftDown: boolean
isMouseDragging: boolean
isWindowsBlurred: boolean,
isTextPending: boolean,
textPendingState: "none" | "pending" | "done",
player: Player
recordingMimeType?: RecordingMimeType
recorder: EnhancedRecorder<RecordingCtx>
Expand All @@ -52,7 +52,7 @@ export const controlState = proxy<ControlState>({
isMouseLeftDown: false,
isMouseDragging: false,
isWindowsBlurred: false,
isTextPending: false,
textPendingState: "none",
player: {
autoPlay: true,
isPlaying: false,
Expand Down
Loading