Implemented chat bot; changed appicon; fixed issues
This commit is contained in:
parent
7d6b136662
commit
ab3bcbb753
6
NOTES.md
6
NOTES.md
|
@ -1,5 +1,11 @@
|
||||||
# Doing
|
# Doing
|
||||||
|
|
||||||
|
New chat message modal:
|
||||||
|
- submit button in modal component (check for button on click and button text)
|
||||||
|
- primary button (save)
|
||||||
|
- secondary button (delete)
|
||||||
|
- on close: reset values (in stream chat message component)
|
||||||
|
|
||||||
Create loading indicator before API is called
|
Create loading indicator before API is called
|
||||||
|
|
||||||
If api query returns error:
|
If api query returns error:
|
||||||
|
|
198
app.go
198
app.go
|
@ -10,6 +10,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tylertravisty/rum-goggles/internal/api"
|
"github.com/tylertravisty/rum-goggles/internal/api"
|
||||||
|
"github.com/tylertravisty/rum-goggles/internal/chatbot"
|
||||||
"github.com/tylertravisty/rum-goggles/internal/config"
|
"github.com/tylertravisty/rum-goggles/internal/config"
|
||||||
rumblelivestreamlib "github.com/tylertravisty/rumble-livestream-lib-go"
|
rumblelivestreamlib "github.com/tylertravisty/rumble-livestream-lib-go"
|
||||||
)
|
)
|
||||||
|
@ -27,6 +28,8 @@ type App struct {
|
||||||
cfgMu sync.Mutex
|
cfgMu sync.Mutex
|
||||||
api *api.Api
|
api *api.Api
|
||||||
apiMu sync.Mutex
|
apiMu sync.Mutex
|
||||||
|
cb *chatbot.ChatBot
|
||||||
|
cbMu sync.Mutex
|
||||||
logError *log.Logger
|
logError *log.Logger
|
||||||
logInfo *log.Logger
|
logInfo *log.Logger
|
||||||
}
|
}
|
||||||
|
@ -126,6 +129,193 @@ func (a *App) AddChannel(url string) (*config.App, error) {
|
||||||
return a.cfg, nil
|
return a.cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) ChatBotMessages(cid string) (map[string]config.ChatMessage, error) {
|
||||||
|
a.cfgMu.Lock()
|
||||||
|
defer a.cfgMu.Unlock()
|
||||||
|
channel, exists := a.cfg.Channels[cid]
|
||||||
|
if !exists {
|
||||||
|
a.logError.Println("channel does not exist:", cid)
|
||||||
|
return nil, fmt.Errorf("Cannot find channel. Try reloading.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return channel.ChatBot.Messages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) AddChatMessage(cid string, asChannel bool, interval time.Duration, message string) (map[string]config.ChatMessage, error) {
|
||||||
|
var err error
|
||||||
|
a.cfgMu.Lock()
|
||||||
|
defer a.cfgMu.Unlock()
|
||||||
|
_, err = a.cfg.NewChatMessage(cid, asChannel, interval, message)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error creating new chat:", err)
|
||||||
|
return nil, fmt.Errorf("Error creating new chat message. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.cfg.Save()
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error saving config:", err)
|
||||||
|
return nil, fmt.Errorf("Error saving chat message information. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
a.updateChatBotConfig(a.cfg.Channels[cid].ChatBot)
|
||||||
|
|
||||||
|
return a.cfg.Channels[cid].ChatBot.Messages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) DeleteChatMessage(mid string, cid string) (map[string]config.ChatMessage, error) {
|
||||||
|
a.cbMu.Lock()
|
||||||
|
if a.cb != nil {
|
||||||
|
err := a.cb.StopMessage(mid)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error stopping chat bot message:", err)
|
||||||
|
return nil, fmt.Errorf("Error stopping message. Try Again.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.cbMu.Unlock()
|
||||||
|
|
||||||
|
a.cfgMu.Lock()
|
||||||
|
defer a.cfgMu.Unlock()
|
||||||
|
err := a.cfg.DeleteChatMessage(mid, cid)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error deleting chat message:", err)
|
||||||
|
return nil, fmt.Errorf("Error deleting chat message. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.cfg.Save()
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error saving config:", err)
|
||||||
|
return nil, fmt.Errorf("Error saving chat message information. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
a.updateChatBotConfig(a.cfg.Channels[cid].ChatBot)
|
||||||
|
|
||||||
|
return a.cfg.Channels[cid].ChatBot.Messages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) UpdateChatMessage(id string, cid string, asChannel bool, interval time.Duration, message string) (map[string]config.ChatMessage, error) {
|
||||||
|
var err error
|
||||||
|
a.cfgMu.Lock()
|
||||||
|
defer a.cfgMu.Unlock()
|
||||||
|
_, err = a.cfg.UpdateChatMessage(id, cid, asChannel, interval, message)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error updating chat message:", err)
|
||||||
|
return nil, fmt.Errorf("Error updating chat message. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.cfg.Save()
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error saving config:", err)
|
||||||
|
return nil, fmt.Errorf("Error saving chat message information. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
a.updateChatBotConfig(a.cfg.Channels[cid].ChatBot)
|
||||||
|
|
||||||
|
return a.cfg.Channels[cid].ChatBot.Messages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) NewChatBot(cid string, username string, password string, streamUrl string) error {
|
||||||
|
a.cbMu.Lock()
|
||||||
|
defer a.cbMu.Unlock()
|
||||||
|
|
||||||
|
if a.cb != nil {
|
||||||
|
err := a.resetChatBot()
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error resetting chat bot:", err)
|
||||||
|
return fmt.Errorf("Error creating chat bot. Try Again.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
channel, exists := a.cfg.Channels[cid]
|
||||||
|
if !exists {
|
||||||
|
a.logError.Println("channel does not exist:", cid)
|
||||||
|
return fmt.Errorf("Channel does not exist.")
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
a.cb, err = chatbot.NewChatBot(a.ctx, streamUrl, channel.ChatBot, a.logError)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error creating new chat bot:", err)
|
||||||
|
return fmt.Errorf("Error creating new chat bot. Try Again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.cb.Login(username, password)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error logging into chat bot:", err)
|
||||||
|
return fmt.Errorf("Error logging in. Try Again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// a.cb = cb
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) ResetChatBot() error {
|
||||||
|
a.cbMu.Lock()
|
||||||
|
defer a.cbMu.Unlock()
|
||||||
|
|
||||||
|
err := a.resetChatBot()
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error resetting chat bot:", err)
|
||||||
|
return fmt.Errorf("Error resetting chat bot. Try Again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) resetChatBot() error {
|
||||||
|
if a.cb == nil {
|
||||||
|
// return fmt.Errorf("chat bot is nil")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := a.cb.StopAllMessages()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error stopping all chat bot messages: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.cb.Logout()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error logging out of chat bot: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.cb = nil
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) StartChatBotMessage(mid string) error {
|
||||||
|
a.cbMu.Lock()
|
||||||
|
defer a.cbMu.Unlock()
|
||||||
|
|
||||||
|
if a.cb == nil {
|
||||||
|
return fmt.Errorf("Chat bot not initialized.")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := a.cb.StartMessage(mid)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error starting chat bot message:", err)
|
||||||
|
return fmt.Errorf("Error starting message. Try Again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) StopChatBotMessage(mid string) error {
|
||||||
|
a.cbMu.Lock()
|
||||||
|
defer a.cbMu.Unlock()
|
||||||
|
|
||||||
|
// If chat bot not initialized, then stop does nothing
|
||||||
|
if a.cb == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := a.cb.StopMessage(mid)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error stopping chat bot message:", err)
|
||||||
|
return fmt.Errorf("Error stopping message. Try Again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) StartApi(cid string) error {
|
func (a *App) StartApi(cid string) error {
|
||||||
channel, found := a.cfg.Channels[cid]
|
channel, found := a.cfg.Channels[cid]
|
||||||
if !found {
|
if !found {
|
||||||
|
@ -145,3 +335,11 @@ func (a *App) StartApi(cid string) error {
|
||||||
func (a *App) StopApi() {
|
func (a *App) StopApi() {
|
||||||
a.api.Stop()
|
a.api.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) updateChatBotConfig(cfg config.ChatBot) {
|
||||||
|
a.cbMu.Lock()
|
||||||
|
defer a.cbMu.Unlock()
|
||||||
|
if a.cb != nil {
|
||||||
|
a.cb.Cfg = cfg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 57 KiB |
BIN
frontend/src/assets/icons/gear-fill.png
Normal file
BIN
frontend/src/assets/icons/gear-fill.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6 KiB |
|
@ -1,6 +1,7 @@
|
||||||
import eye from './eye.png';
|
import eye from './eye.png';
|
||||||
import eye_slash from './eye-slash.png';
|
import eye_slash from './eye-slash.png';
|
||||||
import gear from './gear.png';
|
import gear from './gear.png';
|
||||||
|
import gear_fill from './gear-fill.png';
|
||||||
import heart from './heart-fill.png';
|
import heart from './heart-fill.png';
|
||||||
import house from './house.png';
|
import house from './house.png';
|
||||||
import pause from './pause-fill.png';
|
import pause from './pause-fill.png';
|
||||||
|
@ -9,10 +10,12 @@ import plus_circle from './plus-circle-fill.png';
|
||||||
import star from './star-fill.png';
|
import star from './star-fill.png';
|
||||||
import thumbs_down from './hand-thumbs-down.png';
|
import thumbs_down from './hand-thumbs-down.png';
|
||||||
import thumbs_up from './hand-thumbs-up.png';
|
import thumbs_up from './hand-thumbs-up.png';
|
||||||
|
import x_lg from './x-lg.png';
|
||||||
|
|
||||||
export const Eye = eye;
|
export const Eye = eye;
|
||||||
export const EyeSlash = eye_slash;
|
export const EyeSlash = eye_slash;
|
||||||
export const Gear = gear;
|
export const Gear = gear;
|
||||||
|
export const GearFill = gear_fill;
|
||||||
export const Heart = heart;
|
export const Heart = heart;
|
||||||
export const House = house;
|
export const House = house;
|
||||||
export const Pause = pause;
|
export const Pause = pause;
|
||||||
|
@ -21,3 +24,4 @@ export const PlusCircle = plus_circle;
|
||||||
export const Star = star;
|
export const Star = star;
|
||||||
export const ThumbsDown = thumbs_down;
|
export const ThumbsDown = thumbs_down;
|
||||||
export const ThumbsUp = thumbs_up;
|
export const ThumbsUp = thumbs_up;
|
||||||
|
export const XLg = x_lg;
|
||||||
|
|
BIN
frontend/src/assets/icons/x-lg.png
Normal file
BIN
frontend/src/assets/icons/x-lg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
47
frontend/src/components/ChatBot.css
Normal file
47
frontend/src/components/ChatBot.css
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
.chat-bot-error {
|
||||||
|
border: 1px solid red;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: red;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 5px;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-bot-modal {
|
||||||
|
align-items: left;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-bot-setting {
|
||||||
|
align-items: start;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-top: 10px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-bot-setting-label {
|
||||||
|
color: white;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 20px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-bot-setting-input {
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 16px;
|
||||||
|
outline: none;
|
||||||
|
padding: 10px;
|
||||||
|
resize: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
129
frontend/src/components/ChatBot.jsx
Normal file
129
frontend/src/components/ChatBot.jsx
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { Modal } from './Modal';
|
||||||
|
|
||||||
|
import { NewChatBot } from '../../wailsjs/go/main/App';
|
||||||
|
|
||||||
|
import './ChatBot.css';
|
||||||
|
|
||||||
|
export function ChatBotModal(props) {
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
const updatePassword = (event) => setPassword(event.target.value);
|
||||||
|
const [url, setUrl] = useState('');
|
||||||
|
const updateUrl = (event) => setUrl(event.target.value);
|
||||||
|
const [username, setUsername] = useState('');
|
||||||
|
const updateUsername = (event) => setUsername(event.target.value);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (saving) {
|
||||||
|
// let user = username;
|
||||||
|
// let p = password;
|
||||||
|
// let u = url;
|
||||||
|
// props.onSubmit(user, p, u);
|
||||||
|
NewChatBot(props.cid, username, password, url)
|
||||||
|
.then(() => {
|
||||||
|
reset();
|
||||||
|
props.onClose();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setSaving(false);
|
||||||
|
setError(error);
|
||||||
|
console.log('Error creating new chat bot:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [saving]);
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
setError('');
|
||||||
|
setPassword('');
|
||||||
|
setSaving(false);
|
||||||
|
setUrl('');
|
||||||
|
setUsername('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
reset();
|
||||||
|
props.onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
if (username === '') {
|
||||||
|
setError('Add username');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password === '') {
|
||||||
|
setError('Add password');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url === '') {
|
||||||
|
setError('Add stream URL');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSaving(true);
|
||||||
|
// let user = username;
|
||||||
|
// let p = password;
|
||||||
|
// let u = url;
|
||||||
|
// reset();
|
||||||
|
// props.onSubmit(user, p, u);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
onClose={close}
|
||||||
|
show={props.show}
|
||||||
|
style={{ minWidth: '300px', maxWidth: '400px' }}
|
||||||
|
cancelButton={'Cancel'}
|
||||||
|
onCancel={close}
|
||||||
|
submitButton={saving ? 'Saving' : 'Save'}
|
||||||
|
onSubmit={
|
||||||
|
saving
|
||||||
|
? () => {
|
||||||
|
console.log('Saving');
|
||||||
|
}
|
||||||
|
: submit
|
||||||
|
}
|
||||||
|
title={'Chat Bot'}
|
||||||
|
>
|
||||||
|
<div className='chat-bot-modal'>
|
||||||
|
{error && <span className='chat-bot-error'>{error}</span>}
|
||||||
|
<div className='chat-bot-setting'>
|
||||||
|
<span className='chat-bot-setting-label'>Username</span>
|
||||||
|
<input
|
||||||
|
className='chat-bot-setting-input'
|
||||||
|
onChange={updateUsername}
|
||||||
|
placeholder='Username'
|
||||||
|
type='text'
|
||||||
|
value={username}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='chat-bot-setting'>
|
||||||
|
<span className='chat-bot-setting-label'>Password</span>
|
||||||
|
<input
|
||||||
|
className='chat-bot-setting-input'
|
||||||
|
onChange={updatePassword}
|
||||||
|
placeholder='Password'
|
||||||
|
type='password'
|
||||||
|
value={password}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='chat-bot-setting'>
|
||||||
|
<span className='chat-bot-setting-label'>Stream URL</span>
|
||||||
|
<input
|
||||||
|
className='chat-bot-setting-input'
|
||||||
|
onChange={updateUrl}
|
||||||
|
placeholder='https://'
|
||||||
|
type='text'
|
||||||
|
value={url}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StreamChatMessageItem() {}
|
176
frontend/src/components/Modal.css
Normal file
176
frontend/src/components/Modal.css
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
|
||||||
|
.modal-background {
|
||||||
|
align-items: center;
|
||||||
|
background-color: transparent;
|
||||||
|
display: flex;
|
||||||
|
height: 100vh;
|
||||||
|
justify-content: center;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
height: 80%;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-button {
|
||||||
|
background-color: #85c742;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: #061726;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
/* width: 20%; */
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-button-cancel {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid #495a6a;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: #495a6a;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
/* width: 20%; */
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-button-delete {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid red;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: red;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
/* width: 20%; */
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close {
|
||||||
|
align-items: center;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: center;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close-icon {
|
||||||
|
height: 24px;
|
||||||
|
padding: 0px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-container {
|
||||||
|
align-items: center;
|
||||||
|
background-color: rgba(6,23,38,1);
|
||||||
|
border: 1px solid #495a6a;
|
||||||
|
border-radius: 15px;
|
||||||
|
color: black;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 50%;
|
||||||
|
justify-content: space-between;
|
||||||
|
opacity: 1;
|
||||||
|
padding: 10px 20px;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 10%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 10%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
color: white;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-modal-button-delete {
|
||||||
|
background-color: red;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
/* width: 20%; */
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-modal-container {
|
||||||
|
align-items: center;
|
||||||
|
/* background-color: rgba(6,23,38,1); */
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #495a6a;
|
||||||
|
/* border: 1px solid black; */
|
||||||
|
border-radius: 15px;
|
||||||
|
color: black;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 50%;
|
||||||
|
justify-content: space-between;
|
||||||
|
opacity: 1;
|
||||||
|
padding: 10px 20px;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-modal-header {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 10%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-modal-footer {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 20%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-modal-message {
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-modal-title {
|
||||||
|
color: black;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
86
frontend/src/components/Modal.jsx
Normal file
86
frontend/src/components/Modal.jsx
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
import { XLg } from '../assets/icons';
|
||||||
|
import './Modal.css';
|
||||||
|
|
||||||
|
export function Modal(props) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className='modal-background'
|
||||||
|
onClick={props.onClose}
|
||||||
|
style={{ zIndex: props.show ? 10 : -10 }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className='modal-container'
|
||||||
|
onClick={(event) => event.stopPropagation()}
|
||||||
|
style={props.style}
|
||||||
|
>
|
||||||
|
<div className='modal-header'>
|
||||||
|
<span className='modal-title'>{props.title}</span>
|
||||||
|
<button className='modal-close' onClick={props.onClose}>
|
||||||
|
<img className='modal-close-icon' src={XLg} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className='modal-body'>{props.children}</div>
|
||||||
|
<div className='modal-footer'>
|
||||||
|
{props.cancelButton && (
|
||||||
|
<button className='modal-button-cancel' onClick={props.onCancel}>
|
||||||
|
{props.cancelButton}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{props.deleteButton && (
|
||||||
|
<button className='modal-button-delete' onClick={props.onDelete}>
|
||||||
|
{props.deleteButton}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{props.submitButton && (
|
||||||
|
<button className='modal-button' onClick={props.onSubmit}>
|
||||||
|
{props.submitButton}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SmallModal(props) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className='modal-background'
|
||||||
|
onClick={props.onClose}
|
||||||
|
style={{ zIndex: props.show ? 10 : -10 }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className='small-modal-container'
|
||||||
|
onClick={(event) => event.stopPropagation()}
|
||||||
|
style={props.style}
|
||||||
|
>
|
||||||
|
<div className='small-modal-header'>
|
||||||
|
<span className='small-modal-title'>{props.title}</span>
|
||||||
|
<button className='modal-close' onClick={props.onClose}>
|
||||||
|
<img className='modal-close-icon' src={XLg} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className='modal-body'>
|
||||||
|
<span className='small-modal-message'>{props.message}</span>
|
||||||
|
</div>
|
||||||
|
<div className='small-modal-footer'>
|
||||||
|
{props.cancelButton && (
|
||||||
|
<button className='modal-button-cancel' onClick={props.onCancel}>
|
||||||
|
{props.cancelButton}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{props.deleteButton && (
|
||||||
|
<button className='small-modal-button-delete' onClick={props.onDelete}>
|
||||||
|
{props.deleteButton}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{props.submitButton && (
|
||||||
|
<button className='modal-button' onClick={props.onSubmit}>
|
||||||
|
{props.submitButton}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -3,24 +3,43 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stream-chat-add-button {
|
.stream-chat-button {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: rgba(6,23,38,1);
|
|
||||||
border: none;
|
border: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stream-chat-add-button:hover {
|
.stream-chat-button:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stream-chat-add-icon {
|
.stream-chat-button-title {
|
||||||
|
background-color: rgba(6,23,38,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stream-chat-button-chat {
|
||||||
|
align-items: center;
|
||||||
|
background-color: #000312;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
width: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stream-chat-icon {
|
||||||
height: 24px;
|
height: 24px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stream-chat-controls {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 55px;
|
||||||
|
}
|
||||||
|
|
||||||
.stream-chat-header {
|
.stream-chat-header {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: rgba(6,23,38,1);
|
background-color: rgba(6,23,38,1);
|
||||||
|
@ -33,6 +52,50 @@
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stream-chat-item {
|
||||||
|
border-bottom: 1px solid #82b1ff;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
font-family: sans-serif;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 10px 20px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stream-chat-item-sender {
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
justify-content: left;
|
||||||
|
padding-left: 10px;
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stream-chat-item-interval {
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
justify-content: left;
|
||||||
|
padding-left: 10px;
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stream-chat-item-message {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: left;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stream-chat-list {
|
||||||
|
overflow-y: auto;
|
||||||
|
height: calc(100vh - 84px - 40px - 179px);
|
||||||
|
}
|
||||||
|
|
||||||
.stream-chat-title {
|
.stream-chat-title {
|
||||||
color: white;
|
color: white;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
|
|
|
@ -1,20 +1,182 @@
|
||||||
import { PlusCircle } from '../assets/icons';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { StartChatBotMessage, StopChatBotMessage } from '../../wailsjs/go/main/App';
|
||||||
|
import { EventsOn } from '../../wailsjs/runtime/runtime';
|
||||||
|
import { GearFill, Pause, Play, PlusCircle } from '../assets/icons';
|
||||||
import './StreamChat.css';
|
import './StreamChat.css';
|
||||||
|
import { SmallModal } from './Modal';
|
||||||
|
|
||||||
function StreamChat(props) {
|
function StreamChat(props) {
|
||||||
|
const sortChatsAlpha = () => {
|
||||||
|
let keys = Object.keys(props.chats);
|
||||||
|
|
||||||
|
let sorted = [...keys].sort((a, b) =>
|
||||||
|
props.chats[a].text.toLowerCase() > props.chats[b].text.toLowerCase() ? 1 : -1
|
||||||
|
);
|
||||||
|
return sorted;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='stream-chat'>
|
<div className='stream-chat'>
|
||||||
<div className='stream-chat-header'>
|
<div className='stream-chat-header'>
|
||||||
<span className='stream-chat-title'>{props.title}</span>
|
<span className='stream-chat-title'>{props.title}</span>
|
||||||
|
<div className='stream-chat-controls'>
|
||||||
<button
|
<button
|
||||||
onClick={() => console.log('Add chat bot')}
|
className='stream-chat-button stream-chat-button-title'
|
||||||
className='stream-chat-add-button'
|
onClick={props.onAdd}
|
||||||
>
|
>
|
||||||
<img className='stream-chat-add-icon' src={PlusCircle} />
|
<img className='stream-chat-icon' src={PlusCircle} />
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className='stream-chat-button stream-chat-button-title'
|
||||||
|
onClick={props.onSettings}
|
||||||
|
>
|
||||||
|
<img className='stream-chat-icon' src={GearFill} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='stream-chat-list'>
|
||||||
|
{sortChatsAlpha().map((chat, index) => (
|
||||||
|
<StreamChatItem chat={props.chats[chat]} onItemClick={props.onEdit} />
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default StreamChat;
|
export default StreamChat;
|
||||||
|
|
||||||
|
function StreamChatItem(props) {
|
||||||
|
const [active, setActive] = useState(props.chat.active);
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
|
const changeActive = (bool) => {
|
||||||
|
console.log('ChangeActive:', bool);
|
||||||
|
props.chat.active = bool;
|
||||||
|
setActive(bool);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
EventsOn('ChatBotMessageActive-' + props.chat.id, (mid) => {
|
||||||
|
console.log('ChatBotMessageActive', props.chat.id, mid);
|
||||||
|
if (mid === props.chat.id) {
|
||||||
|
changeActive(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
EventsOn('ChatBotMessageError-' + props.chat.id, (mid) => {
|
||||||
|
console.log('ChatBotMessageError', props.chat.id, mid);
|
||||||
|
if (mid === props.chat.id) {
|
||||||
|
changeActive(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const prependZero = (value) => {
|
||||||
|
if (value < 10) {
|
||||||
|
return '0' + value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '' + value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const printInterval = (interval) => {
|
||||||
|
let hours = Math.floor(interval / 3600);
|
||||||
|
let minutes = Math.floor(interval / 60 - hours * 60);
|
||||||
|
let seconds = Math.floor(interval - hours * 3600 - minutes * 60);
|
||||||
|
|
||||||
|
// hours = prependZero(hours);
|
||||||
|
// minutes = prependZero(minutes);
|
||||||
|
// seconds = prependZero(seconds);
|
||||||
|
// return hours + ':' + minutes + ':' + seconds;
|
||||||
|
|
||||||
|
return hours + 'h ' + minutes + 'm ' + seconds + 's';
|
||||||
|
};
|
||||||
|
|
||||||
|
const intervalToTimer = (interval) => {
|
||||||
|
let hours = Math.floor(interval / 3600);
|
||||||
|
let minutes = Math.floor(interval / 60 - hours * 60);
|
||||||
|
let seconds = Math.floor(interval - hours * 3600 - minutes * 60);
|
||||||
|
|
||||||
|
if (minutes !== 0) {
|
||||||
|
seconds = prependZero(seconds);
|
||||||
|
}
|
||||||
|
if (hours !== 0) {
|
||||||
|
minutes = prependZero(minutes);
|
||||||
|
}
|
||||||
|
if (hours === 0) {
|
||||||
|
hours = '';
|
||||||
|
if (minutes === 0) {
|
||||||
|
minutes = '';
|
||||||
|
if (seconds === 0) {
|
||||||
|
seconds = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hours + minutes + seconds;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openChat = () => {
|
||||||
|
props.onItemClick(
|
||||||
|
props.chat.id,
|
||||||
|
props.chat.as_channel,
|
||||||
|
intervalToTimer(props.chat.interval),
|
||||||
|
props.chat.text
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const startMessage = () => {
|
||||||
|
StartChatBotMessage(props.chat.id)
|
||||||
|
.then(() => {
|
||||||
|
changeActive(true);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setError(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopMessage = () => {
|
||||||
|
StopChatBotMessage(props.chat.id).then(() => {
|
||||||
|
changeActive(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SmallModal
|
||||||
|
onClose={() => setError('')}
|
||||||
|
show={error !== ''}
|
||||||
|
style={{ minWidth: '300px', maxWidth: '200px', maxHeight: '200px' }}
|
||||||
|
title={'Error'}
|
||||||
|
message={error}
|
||||||
|
submitButton={'OK'}
|
||||||
|
onSubmit={() => setError('')}
|
||||||
|
/>
|
||||||
|
<div className='stream-chat-item' onClick={() => openChat()}>
|
||||||
|
<span className='stream-chat-item-message'>{props.chat.text}</span>
|
||||||
|
<span className='stream-chat-item-interval'>
|
||||||
|
{printInterval(props.chat.interval)}
|
||||||
|
</span>
|
||||||
|
<span className='stream-chat-item-sender'>
|
||||||
|
{props.chat.as_channel ? 'Channel' : 'User'}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
className='stream-chat-button stream-chat-button-chat'
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
console.log('message ID:', props.chat.id);
|
||||||
|
if (active) {
|
||||||
|
console.log('Stop message');
|
||||||
|
stopMessage();
|
||||||
|
} else {
|
||||||
|
console.log('Start message');
|
||||||
|
startMessage();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img className='stream-chat-icon' src={active ? Pause : Play} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.modal-chat {
|
/* .modal-chat {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: red;
|
background-color: red;
|
||||||
color: black;
|
color: black;
|
||||||
|
@ -18,4 +18,170 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
|
} */
|
||||||
|
|
||||||
|
.chat-as-channel {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-top: 10px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-as-channel-label {
|
||||||
|
color: white;
|
||||||
|
font-family: sans-serif;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-interval {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-top: 10px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-interval-input {
|
||||||
|
border: none;
|
||||||
|
border-radius: 34px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 16px;
|
||||||
|
outline: none;
|
||||||
|
padding: 5px 10px 5px 10px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-interval-input-zero::placeholder {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-interval-input-value::placeholder {
|
||||||
|
color: black;
|
||||||
|
opacity: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-interval-label {
|
||||||
|
color: white;
|
||||||
|
font-family: sans-serif;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stream-chat-message {
|
||||||
|
align-items: center;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-family: sans-serif;
|
||||||
|
justify-content: start;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stream-chat-message-error {
|
||||||
|
border: 1px solid red;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: red;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 5px;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stream-chat-message-label {
|
||||||
|
padding: 5px 0px;
|
||||||
|
/* width: 50%; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.stream-chat-message-modal {
|
||||||
|
align-items: left;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stream-chat-message-textarea {
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 16px;
|
||||||
|
outline: none;
|
||||||
|
padding: 10px;
|
||||||
|
resize: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stream-chat-message-title {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: start;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-as-channel-switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 50px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-as-channel-switch input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-as-channel-slider {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #495a6a;
|
||||||
|
-webkit-transition: .4s;
|
||||||
|
transition: .4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-as-channel-slider:before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
left: 4px;
|
||||||
|
bottom: 4px;
|
||||||
|
background-color: white;
|
||||||
|
-webkit-transition: .4s;
|
||||||
|
transition: .4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .chat-as-channel-slider {
|
||||||
|
background-color: #85c742;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .chat-as-channel-slider:before {
|
||||||
|
-webkit-transform: translateX(26px);
|
||||||
|
-ms-transform: translateX(26px);
|
||||||
|
transform: translateX(26px);
|
||||||
|
}
|
||||||
|
/* Rounded sliders */
|
||||||
|
.chat-as-channel-slider.round {
|
||||||
|
border-radius: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-as-channel-slider.round:before {
|
||||||
|
border-radius: 50%;
|
||||||
}
|
}
|
|
@ -1,12 +1,194 @@
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { Modal, SmallModal } from './Modal';
|
||||||
|
|
||||||
import './StreamChatMessage.css';
|
import './StreamChatMessage.css';
|
||||||
|
|
||||||
export function StreamChatMessageModal() {
|
export function StreamChatMessageModal(props) {
|
||||||
|
const [asChannel, setAsChannel] = useState(props.asChannel);
|
||||||
|
const [openDelete, setOpenDelete] = useState(false);
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
const [message, setMessage] = useState(props.message);
|
||||||
|
const updateMessage = (event) => setMessage(event.target.value);
|
||||||
|
const [timer, setTimer] = useState(props.interval);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('update chat');
|
||||||
|
setAsChannel(props.asChannel);
|
||||||
|
setError('');
|
||||||
|
setMessage(props.message);
|
||||||
|
setTimer(props.interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
setAsChannel(false);
|
||||||
|
setError('');
|
||||||
|
setMessage('');
|
||||||
|
setTimer('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
reset();
|
||||||
|
props.onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
if (message === '') {
|
||||||
|
setError('Add message');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timer === '') {
|
||||||
|
setError('Set timer');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ac = asChannel;
|
||||||
|
let msg = message;
|
||||||
|
let int = timerToInterval();
|
||||||
|
reset();
|
||||||
|
props.onSubmit(props.chatID, ac, int, msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteMessage = () => {
|
||||||
|
if (props.chatID === '') {
|
||||||
|
close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setOpenDelete(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmDelete = () => {
|
||||||
|
reset();
|
||||||
|
setOpenDelete(false);
|
||||||
|
props.onDelete(props.chatID);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateTimerBackspace = (e) => {
|
||||||
|
if (timer.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.keyCode === 8) {
|
||||||
|
setTimer(timer.substring(0, timer.length - 1));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateTimer = (e) => {
|
||||||
|
let nums = '0123456789';
|
||||||
|
let digit = e.target.value;
|
||||||
|
|
||||||
|
if (!nums.includes(digit)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timer.length === 6) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timer.length === 0 && digit === '0') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimer(timer + digit);
|
||||||
|
};
|
||||||
|
|
||||||
|
const timerToInterval = () => {
|
||||||
|
let prefix = '0'.repeat(6 - timer.length);
|
||||||
|
let t = prefix + timer;
|
||||||
|
|
||||||
|
let hours = parseInt(t.substring(0, 2));
|
||||||
|
let minutes = parseInt(t.substring(2, 4));
|
||||||
|
let seconds = parseInt(t.substring(4, 6));
|
||||||
|
|
||||||
|
return hours * 3600 + minutes * 60 + seconds;
|
||||||
|
};
|
||||||
|
|
||||||
|
const printTimer = () => {
|
||||||
|
if (timer === '') {
|
||||||
|
return '00:00:00';
|
||||||
|
}
|
||||||
|
|
||||||
|
let prefix = '0'.repeat(6 - timer.length);
|
||||||
|
let t = prefix + timer;
|
||||||
|
|
||||||
|
return t.substring(0, 2) + ':' + t.substring(2, 4) + ':' + t.substring(4, 6);
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkToggle = (e) => {
|
||||||
|
setAsChannel(e.target.checked);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='modal-container'>
|
<>
|
||||||
<div className='modal-chat'>
|
<Modal
|
||||||
<span>hello world</span>
|
onClose={close}
|
||||||
|
show={props.show}
|
||||||
|
style={{ minWidth: '300px', maxWidth: '400px' }}
|
||||||
|
cancelButton={props.chatID === '' ? 'Cancel' : ''}
|
||||||
|
onCancel={deleteMessage}
|
||||||
|
deleteButton={props.chatID === '' ? '' : 'Delete'}
|
||||||
|
onDelete={deleteMessage}
|
||||||
|
submitButton={'Save'}
|
||||||
|
onSubmit={submit}
|
||||||
|
title={'Chat Message'}
|
||||||
|
>
|
||||||
|
<div className='stream-chat-message-modal'>
|
||||||
|
<div className='stream-chat-message'>
|
||||||
|
{error && <span className='stream-chat-message-error'>{error}</span>}
|
||||||
|
<div className='stream-chat-message-title'>
|
||||||
|
<span className='stream-chat-message-label'>Message</span>
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
className='stream-chat-message-textarea'
|
||||||
|
cols='25'
|
||||||
|
onChange={updateMessage}
|
||||||
|
rows='4'
|
||||||
|
value={message}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='chat-options'>
|
||||||
|
<div className='chat-interval'>
|
||||||
|
<span className='chat-interval-label'>Chat interval</span>
|
||||||
|
<input
|
||||||
|
className={
|
||||||
|
timer === ''
|
||||||
|
? 'chat-interval-input chat-interval-input-zero'
|
||||||
|
: 'chat-interval-input chat-interval-input-value'
|
||||||
|
}
|
||||||
|
onKeyDown={updateTimerBackspace}
|
||||||
|
onInput={updateTimer}
|
||||||
|
placeholder={printTimer()}
|
||||||
|
size='8'
|
||||||
|
type='text'
|
||||||
|
value={''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='chat-as-channel'>
|
||||||
|
<span className='chat-as-channel-label'>Chat as channel</span>
|
||||||
|
<label className='chat-as-channel-switch'>
|
||||||
|
<input onChange={checkToggle} type='checkbox' checked={asChannel} />
|
||||||
|
<span className='chat-as-channel-slider round'></span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
<SmallModal
|
||||||
|
onClose={() => setOpenDelete(false)}
|
||||||
|
show={openDelete}
|
||||||
|
style={{ minWidth: '300px', maxWidth: '200px', maxHeight: '200px' }}
|
||||||
|
cancelButton={'Cancel'}
|
||||||
|
onCancel={() => setOpenDelete(false)}
|
||||||
|
deleteButton={'Delete'}
|
||||||
|
message={
|
||||||
|
'Are you sure you want to delete this message? You cannot undo this action.'
|
||||||
|
}
|
||||||
|
onDelete={confirmDelete}
|
||||||
|
title={'Delete Message'}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,21 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Navigate, useLocation, useNavigate } from 'react-router-dom';
|
import { Navigate, useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { StartApi, StopApi } from '../../wailsjs/go/main/App';
|
import {
|
||||||
|
AddChatMessage,
|
||||||
|
ChatBotMessages,
|
||||||
|
DeleteChatMessage,
|
||||||
|
NewChatBot,
|
||||||
|
ResetChatBot,
|
||||||
|
StartApi,
|
||||||
|
StopApi,
|
||||||
|
StopChatBotMessage,
|
||||||
|
UpdateChatMessage,
|
||||||
|
} from '../../wailsjs/go/main/App';
|
||||||
|
|
||||||
import './Dashboard.css';
|
import './Dashboard.css';
|
||||||
import { EventsEmit, EventsOn } from '../../wailsjs/runtime/runtime';
|
import { EventsEmit, EventsOn } from '../../wailsjs/runtime/runtime';
|
||||||
import { Heart, Star } from '../assets/icons';
|
import { Heart, Star } from '../assets/icons';
|
||||||
|
import { ChatBotModal } from '../components/ChatBot';
|
||||||
import Highlight from '../components/Highlight';
|
import Highlight from '../components/Highlight';
|
||||||
import StreamEvent from '../components/StreamEvent';
|
import StreamEvent from '../components/StreamEvent';
|
||||||
import StreamActivity from '../components/StreamActivity';
|
import StreamActivity from '../components/StreamActivity';
|
||||||
|
@ -18,6 +29,13 @@ function Dashboard() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [refresh, setRefresh] = useState(false);
|
const [refresh, setRefresh] = useState(false);
|
||||||
const [active, setActive] = useState(false);
|
const [active, setActive] = useState(false);
|
||||||
|
const [openChatBot, setOpenChatBot] = useState(false);
|
||||||
|
const [chatBotMessages, setChatBotMessages] = useState({});
|
||||||
|
const [chatAsChannel, setChatAsChannel] = useState(false);
|
||||||
|
const [chatID, setChatID] = useState('');
|
||||||
|
const [chatInterval, setChatInterval] = useState('');
|
||||||
|
const [chatMessage, setChatMessage] = useState('');
|
||||||
|
const [openChat, setOpenChat] = useState(false);
|
||||||
const [cid, setCID] = useState(location.state.cid);
|
const [cid, setCID] = useState(location.state.cid);
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
const [channelName, setChannelName] = useState('');
|
const [channelName, setChannelName] = useState('');
|
||||||
|
@ -38,13 +56,17 @@ function Dashboard() {
|
||||||
const [streamTitle, setStreamTitle] = useState('');
|
const [streamTitle, setStreamTitle] = useState('');
|
||||||
const [watchingNow, setWatchingNow] = useState(0);
|
const [watchingNow, setWatchingNow] = useState(0);
|
||||||
const [createdOn, setCreatedOn] = useState('');
|
const [createdOn, setCreatedOn] = useState('');
|
||||||
const [modalZ, setModalZ] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('use effect start');
|
console.log('use effect start');
|
||||||
|
// TODO: catch error
|
||||||
StartApi(cid);
|
StartApi(cid);
|
||||||
setActive(true);
|
setActive(true);
|
||||||
|
|
||||||
|
ChatBotMessages(cid).then((messages) => {
|
||||||
|
setChatBotMessages(messages);
|
||||||
|
});
|
||||||
|
|
||||||
EventsOn('QueryResponse', (response) => {
|
EventsOn('QueryResponse', (response) => {
|
||||||
console.log('query response received');
|
console.log('query response received');
|
||||||
setRefresh(!refresh);
|
setRefresh(!refresh);
|
||||||
|
@ -80,11 +102,14 @@ function Dashboard() {
|
||||||
const home = () => {
|
const home = () => {
|
||||||
StopApi()
|
StopApi()
|
||||||
.then(() => setActive(false))
|
.then(() => setActive(false))
|
||||||
|
.then(() => {
|
||||||
|
ResetChatBot();
|
||||||
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
navigate(NavSignIn);
|
navigate(NavSignIn);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((error) => {
|
||||||
console.log('Stop error:', err);
|
console.log('Stop error:', error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -94,8 +119,8 @@ function Dashboard() {
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setActive(true);
|
setActive(true);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((error) => {
|
||||||
console.log('Start error:', err);
|
console.log('Start error:', error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -122,21 +147,93 @@ function Dashboard() {
|
||||||
return sorted;
|
return sorted;
|
||||||
};
|
};
|
||||||
|
|
||||||
const openModal = () => {
|
const newChat = () => {
|
||||||
setModalZ(true);
|
setChatAsChannel(false);
|
||||||
|
setChatID('');
|
||||||
|
setChatInterval('');
|
||||||
|
setChatMessage('');
|
||||||
|
setOpenChat(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeModal = () => {
|
const editChat = (id, asChannel, interval, message) => {
|
||||||
setModalZ(false);
|
setChatAsChannel(asChannel);
|
||||||
|
setChatInterval(interval);
|
||||||
|
setChatMessage(message);
|
||||||
|
setChatID(id);
|
||||||
|
setOpenChat(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteChat = (id) => {
|
||||||
|
setOpenChat(false);
|
||||||
|
if (id === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StopChatBotMessage(id, cid)
|
||||||
|
.then(() => {
|
||||||
|
DeleteChatMessage(id, cid)
|
||||||
|
.then((messages) => {
|
||||||
|
setChatBotMessages(messages);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log('Error deleting message:', error);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log('Error stopping message:', error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveChat = (id, asChannel, interval, message) => {
|
||||||
|
setOpenChat(false);
|
||||||
|
if (id === '') {
|
||||||
|
AddChatMessage(cid, asChannel, interval, message)
|
||||||
|
.then((messages) => {
|
||||||
|
setChatBotMessages(messages);
|
||||||
|
})
|
||||||
|
.catch((error) => console.log('Error saving chat:', error));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateChatMessage(id, cid, asChannel, interval, message)
|
||||||
|
.then((messages) => {
|
||||||
|
console.log(messages);
|
||||||
|
setChatBotMessages(messages);
|
||||||
|
})
|
||||||
|
.catch((error) => console.log('Error saving chat:', error));
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveChatBot = (username, password, url) => {
|
||||||
|
NewChatBot(cid, username, password, url)
|
||||||
|
.then(() => {
|
||||||
|
setOpenChatBot(false);
|
||||||
|
})
|
||||||
|
.catch((error) => console.log('Error creating new chat bot:', error));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StreamChatMessageModal />
|
{openChat && (
|
||||||
<div className='modal' style={{ zIndex: modalZ ? 10 : -10 }}>
|
<StreamChatMessageModal
|
||||||
<span>show this instead</span>
|
chatID={chatID}
|
||||||
<button onClick={closeModal}>close</button>
|
asChannel={chatAsChannel}
|
||||||
</div>
|
interval={chatInterval}
|
||||||
|
message={chatMessage}
|
||||||
|
onClose={() => setOpenChat(false)}
|
||||||
|
onDelete={deleteChat}
|
||||||
|
onSubmit={saveChat}
|
||||||
|
show={openChat}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{openChatBot && (
|
||||||
|
<ChatBotModal
|
||||||
|
cid={cid}
|
||||||
|
onClose={() => setOpenChatBot(false)}
|
||||||
|
onSubmit={saveChatBot}
|
||||||
|
show={openChatBot}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div id='Dashboard'>
|
<div id='Dashboard'>
|
||||||
<div className='header'>
|
<div className='header'>
|
||||||
<div className='header-left'></div>
|
<div className='header-left'></div>
|
||||||
|
@ -161,7 +258,14 @@ function Dashboard() {
|
||||||
<StreamActivity title={'Stream Activity'} events={activityEvents()} />
|
<StreamActivity title={'Stream Activity'} events={activityEvents()} />
|
||||||
</div>
|
</div>
|
||||||
<div className='main-right'>
|
<div className='main-right'>
|
||||||
<StreamChat title={'Stream Chat'} />
|
<StreamChat
|
||||||
|
chats={chatBotMessages}
|
||||||
|
onAdd={newChat}
|
||||||
|
onEdit={editChat}
|
||||||
|
onRefresh={() => setRefresh(!refresh)}
|
||||||
|
onSettings={() => setOpenChatBot(true)}
|
||||||
|
title={'Stream Chat'}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -176,7 +280,7 @@ function Dashboard() {
|
||||||
home={home}
|
home={home}
|
||||||
play={startQuery}
|
play={startQuery}
|
||||||
pause={stopQuery}
|
pause={stopQuery}
|
||||||
settings={openModal}
|
// settings={openModal}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -5,8 +5,10 @@ import { AddChannel, Config } from '../../wailsjs/go/main/App';
|
||||||
import { Eye, EyeSlash } from '../assets/icons';
|
import { Eye, EyeSlash } from '../assets/icons';
|
||||||
import './SignIn.css';
|
import './SignIn.css';
|
||||||
import ChannelList from '../components/ChannelList';
|
import ChannelList from '../components/ChannelList';
|
||||||
|
import { SmallModal } from '../components/Modal';
|
||||||
|
|
||||||
function SignIn() {
|
function SignIn() {
|
||||||
|
const [error, setError] = useState('');
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [config, setConfig] = useState({ channels: {} });
|
const [config, setConfig] = useState({ channels: {} });
|
||||||
const [addChannelError, setAddChannelError] = useState('');
|
const [addChannelError, setAddChannelError] = useState('');
|
||||||
|
@ -20,9 +22,10 @@ function SignIn() {
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
setConfig(response);
|
setConfig(response);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((error) => {
|
||||||
// TODO: display error to user
|
// TODO: display error to user
|
||||||
console.log('error getting config', err);
|
setError('Error loading config: ' + error);
|
||||||
|
console.log('error getting config', error);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -33,9 +36,9 @@ function SignIn() {
|
||||||
setConfig(response);
|
setConfig(response);
|
||||||
setStreamKey('');
|
setStreamKey('');
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((error) => {
|
||||||
console.log('error adding channel', err);
|
console.log('error adding channel', error);
|
||||||
setAddChannelError(err);
|
setAddChannelError(error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -44,13 +47,28 @@ function SignIn() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
{error !== '' && (
|
||||||
|
<SmallModal
|
||||||
|
onClose={() => setError('')}
|
||||||
|
show={error !== ''}
|
||||||
|
style={{ minWidth: '300px', maxWidth: '200px', maxHeight: '200px' }}
|
||||||
|
title={'Error'}
|
||||||
|
message={error}
|
||||||
|
submitButton={'OK'}
|
||||||
|
onSubmit={() => setError('')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div id='SignIn'>
|
<div id='SignIn'>
|
||||||
<div className='signin-header'>
|
<div className='signin-header'>
|
||||||
<span className='signin-title-text'>Rum Goggles</span>
|
<span className='signin-title-text'>Rum Goggles</span>
|
||||||
<span className='signin-title-subtext'>Rumble Stream Dashboard</span>
|
<span className='signin-title-subtext'>Rumble Stream Dashboard</span>
|
||||||
</div>
|
</div>
|
||||||
<div className='signin-center'>
|
<div className='signin-center'>
|
||||||
<ChannelList channels={config.channels} openStreamDashboard={openStreamDashboard} />
|
<ChannelList
|
||||||
|
channels={config.channels}
|
||||||
|
openStreamDashboard={openStreamDashboard}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='signin-input-box'>
|
<div className='signin-input-box'>
|
||||||
<label className='signin-label'>Add Channel</label>
|
<label className='signin-label'>Add Channel</label>
|
||||||
|
@ -82,6 +100,7 @@ function SignIn() {
|
||||||
</div>
|
</div>
|
||||||
<div className='signin-footer'></div>
|
<div className='signin-footer'></div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -4,7 +4,7 @@ go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909
|
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909
|
||||||
github.com/tylertravisty/rumble-livestream-lib-go v0.0.0-20231218182551-5ac1d6c01910
|
github.com/tylertravisty/rumble-livestream-lib-go v0.0.0-20240105170050-474340b082fc
|
||||||
github.com/wailsapp/wails/v2 v2.7.1
|
github.com/wailsapp/wails/v2 v2.7.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ require (
|
||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/rivo/uniseg v0.4.4 // indirect
|
github.com/rivo/uniseg v0.4.4 // indirect
|
||||||
github.com/robertkrimen/otto v0.2.1 // indirect
|
github.com/robertkrimen/otto v0.3.0 // indirect
|
||||||
github.com/samber/lo v1.38.1 // indirect
|
github.com/samber/lo v1.38.1 // indirect
|
||||||
github.com/tkrajina/go-reflector v0.5.6 // indirect
|
github.com/tkrajina/go-reflector v0.5.6 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -44,8 +44,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/robertkrimen/otto v0.2.1 h1:FVP0PJ0AHIjC+N4pKCG9yCDz6LHNPCwi/GKID5pGGF0=
|
github.com/robertkrimen/otto v0.3.0 h1:5RI+8860NSxvXywDY9ddF5HcPw0puRsd8EgbXV0oqRE=
|
||||||
github.com/robertkrimen/otto v0.2.1/go.mod h1:UPwtJ1Xu7JrLcZjNWN8orJaM5n5YEtqL//farB5FlRY=
|
github.com/robertkrimen/otto v0.3.0/go.mod h1:uW9yN1CYflmUQYvAMS0m+ZiNo3dMzRUDQJX0jWbzgxw=
|
||||||
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
@ -55,8 +55,8 @@ github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQ
|
||||||
github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
|
github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
|
||||||
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909 h1:xrjIFqzGQXlCrCdMPpW6+SodGFSlrQ3ZNUCr3f5tF1g=
|
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909 h1:xrjIFqzGQXlCrCdMPpW6+SodGFSlrQ3ZNUCr3f5tF1g=
|
||||||
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909/go.mod h1:2W31Jhs9YSy7y500wsCOW0bcamGi9foQV1CKrfvfTxk=
|
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909/go.mod h1:2W31Jhs9YSy7y500wsCOW0bcamGi9foQV1CKrfvfTxk=
|
||||||
github.com/tylertravisty/rumble-livestream-lib-go v0.0.0-20231218182551-5ac1d6c01910 h1:pu5jBae9XZDF/G8YkCN7D5TMxOCsCO5+Jn1/lNPsOUY=
|
github.com/tylertravisty/rumble-livestream-lib-go v0.0.0-20240105170050-474340b082fc h1:JaoanQiZrYIbDx0UAYTNpyjhsgx7eWlTD7KJRqnrC8A=
|
||||||
github.com/tylertravisty/rumble-livestream-lib-go v0.0.0-20231218182551-5ac1d6c01910/go.mod h1:YrfW5N6xVozOzubzfNNsy+v0MIL2GPi9Kx3mTZ/Q9zI=
|
github.com/tylertravisty/rumble-livestream-lib-go v0.0.0-20240105170050-474340b082fc/go.mod h1:YrfW5N6xVozOzubzfNNsy+v0MIL2GPi9Kx3mTZ/Q9zI=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||||
|
|
167
internal/chatbot/chatbot.go
Normal file
167
internal/chatbot/chatbot.go
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
package chatbot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tylertravisty/rum-goggles/internal/config"
|
||||||
|
rumblelivestreamlib "github.com/tylertravisty/rumble-livestream-lib-go"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChatBot struct {
|
||||||
|
ctx context.Context
|
||||||
|
client *rumblelivestreamlib.Client
|
||||||
|
Cfg config.ChatBot
|
||||||
|
logError *log.Logger
|
||||||
|
messages map[string]*message
|
||||||
|
messagesMu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
type message struct {
|
||||||
|
cancel context.CancelFunc
|
||||||
|
cancelMu sync.Mutex
|
||||||
|
asChannel bool
|
||||||
|
id string
|
||||||
|
interval time.Duration
|
||||||
|
text string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChatBot(ctx context.Context, streamUrl string, cfg config.ChatBot, logError *log.Logger) (*ChatBot, error) {
|
||||||
|
client, err := rumblelivestreamlib.NewClient("", streamUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("chatbot: error creating new client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ChatBot{ctx: ctx, client: client, Cfg: cfg, logError: logError, messages: map[string]*message{}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *ChatBot) StartMessage(id string) error {
|
||||||
|
msg, exists := cb.Cfg.Messages[id]
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("chatbot: message does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
cb.messagesMu.Lock()
|
||||||
|
defer cb.messagesMu.Unlock()
|
||||||
|
m, exists := cb.messages[id]
|
||||||
|
if exists {
|
||||||
|
m.stop()
|
||||||
|
delete(cb.messages, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
m = &message{
|
||||||
|
asChannel: msg.AsChannel,
|
||||||
|
id: msg.ID,
|
||||||
|
interval: msg.Interval,
|
||||||
|
text: msg.Text,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
m.cancelMu.Lock()
|
||||||
|
m.cancel = cancel
|
||||||
|
m.cancelMu.Unlock()
|
||||||
|
go cb.startMessage(ctx, m)
|
||||||
|
|
||||||
|
cb.messages[id] = m
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *ChatBot) startMessage(ctx context.Context, m *message) {
|
||||||
|
for {
|
||||||
|
// TODO: if error, emit error to user, stop loop?
|
||||||
|
err := cb.chat(m)
|
||||||
|
if err != nil {
|
||||||
|
cb.logError.Println("error sending chat:", err)
|
||||||
|
cb.StopMessage(m.id)
|
||||||
|
runtime.EventsEmit(cb.ctx, "ChatBotMessageError-"+m.id, m.id)
|
||||||
|
// TODO: stop this loop?
|
||||||
|
} else {
|
||||||
|
runtime.EventsEmit(cb.ctx, "ChatBotMessageActive-"+m.id, m.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
timer := time.NewTimer(m.interval * time.Second)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
timer.Stop()
|
||||||
|
return
|
||||||
|
case <-timer.C:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *ChatBot) chat(m *message) error {
|
||||||
|
if cb.client == nil {
|
||||||
|
return fmt.Errorf("client is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cb.client.Chat(m.asChannel, m.text)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error sending chat: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *ChatBot) StopAllMessages() error {
|
||||||
|
cb.messagesMu.Lock()
|
||||||
|
defer cb.messagesMu.Unlock()
|
||||||
|
|
||||||
|
for id, m := range cb.messages {
|
||||||
|
m.stop()
|
||||||
|
delete(cb.messages, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *ChatBot) StopMessage(id string) error {
|
||||||
|
cb.messagesMu.Lock()
|
||||||
|
defer cb.messagesMu.Unlock()
|
||||||
|
m, exists := cb.messages[id]
|
||||||
|
if exists {
|
||||||
|
fmt.Println("IT EXISTS!!")
|
||||||
|
m.stop()
|
||||||
|
delete(cb.messages, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *message) stop() {
|
||||||
|
m.cancelMu.Lock()
|
||||||
|
if m.cancel != nil {
|
||||||
|
m.cancel()
|
||||||
|
}
|
||||||
|
m.cancelMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *ChatBot) Login(username string, password string) error {
|
||||||
|
if cb.client == nil {
|
||||||
|
return fmt.Errorf("chatbot: client is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cb.client.Login(username, password)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("chatbot: error logging in: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *ChatBot) Logout() error {
|
||||||
|
if cb.client == nil {
|
||||||
|
return fmt.Errorf("chatbot: client is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cb.client.Logout()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("chatbot: error logging out: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -73,13 +73,14 @@ func userDir() (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatMessage struct {
|
type ChatMessage struct {
|
||||||
|
ID string `json:"id"`
|
||||||
AsChannel bool `json:"as_channel"`
|
AsChannel bool `json:"as_channel"`
|
||||||
Text string `json:"text"`
|
|
||||||
Interval time.Duration `json:"interval"`
|
Interval time.Duration `json:"interval"`
|
||||||
|
Text string `json:"text"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatBot struct {
|
type ChatBot struct {
|
||||||
Messages []ChatMessage `json:"messages"`
|
Messages map[string]ChatMessage `json:"messages"`
|
||||||
// Commands []ChatCommand
|
// Commands []ChatCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,12 +100,65 @@ func (a *App) NewChannel(url string, name string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, exists := a.Channels[id]; !exists {
|
if _, exists := a.Channels[id]; !exists {
|
||||||
a.Channels[id] = Channel{id, url, name, DefaultInterval, ChatBot{[]ChatMessage{}}}
|
a.Channels[id] = Channel{id, url, name, DefaultInterval, ChatBot{map[string]ChatMessage{}}}
|
||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) DeleteChatMessage(id string, cid string) error {
|
||||||
|
channel, exists := a.Channels[cid]
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("config: channel does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, exists = channel.ChatBot.Messages[id]
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("config: message does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(channel.ChatBot.Messages, id)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) NewChatMessage(cid string, asChannel bool, interval time.Duration, message string) (string, error) {
|
||||||
|
if _, exists := a.Channels[cid]; !exists {
|
||||||
|
return "", fmt.Errorf("config: channel does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
id, err := random.String(CIDLen)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("config: error generating ID: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := a.Channels[cid].ChatBot.Messages[id]; !exists {
|
||||||
|
a.Channels[cid].ChatBot.Messages[id] = ChatMessage{id, asChannel, interval, message}
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) UpdateChatMessage(id string, cid string, asChannel bool, interval time.Duration, text string) (string, error) {
|
||||||
|
channel, exists := a.Channels[cid]
|
||||||
|
if !exists {
|
||||||
|
return "", fmt.Errorf("config: channel does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
message, exists := channel.ChatBot.Messages[id]
|
||||||
|
if !exists {
|
||||||
|
return "", fmt.Errorf("config: message does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
message.AsChannel = asChannel
|
||||||
|
message.Interval = interval
|
||||||
|
message.Text = text
|
||||||
|
channel.ChatBot.Messages[id] = message
|
||||||
|
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
Channels map[string]Channel `json:"channels"`
|
Channels map[string]Channel `json:"channels"`
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue