rws-js-client

2.0.6 • Public • Published

RWS Frontend Framework README

Table of Contents

  1. Overview
  2. Getting Started
  3. Key Components: RWSClient & RoutingService
  4. Component Initialization
  5. Frontend routes
  6. Backend Imports
  7. Utilizing APIService
  8. Notifier
  9. Service Worker
  10. Example: WebChat Component
  11. Links

Overview

The RWS Frontend Framework is designed to create dynamic and responsive web applications. It integrates seamlessly with the backend and provides a robust set of tools for developing comprehensive web solutions.

Getting Started

To get started with the RWS Frontend Framework, ensure you have the necessary environment set up, including Node.js and any other dependencies specific to the framework.

from your project dir do:

yarn

Initiate cfg files:

rws-client init

to install once and then to build after preparing compionents:

yarn build

or to watch for dev

yarn watch

then start engine in the site javascript (can be inline):

window.RWSClient.start(CFG);

example config with interface:

const CFG = {
    backendUrl: 'http://localhost:1337',
    wsUrl: 'http://localhost:1338'
}
export default interface IRWSConfig {
    defaultLayout?: typeof RWSViewComponent;
    backendUrl?: string,
    wsUrl?: string,
    backendRoutes?: any[] // routes from backend
    apiPrefix?: string // f.e /api after host
    routes?: IFrontRoutes, //override front routes
    transports?: string[], //ws transports setup
    user?: any, //user data if logged
    ignoreRWSComponents?: boolean //do not register base RWS components
}

Key Components

RWSClient

RWSClient is the heart of the framework, managing configuration and initialization. It sets up routes, backend connections, and other essential framework services.

RoutingService

RoutingService handles the navigation and routing within your application. It ensures that URL changes reflect the correct component rendering.

Implementing the Framework

Main File:

The main file (index.ts) is where you initialize the RWSClient. Here, you configure your routes, backend routes, and component initializations.

Following is example of full usage of the framework

import RWSClient, { NotifyUiType, NotifyLogType } from 'rws-js-client';
//@ts-ignore
import alertify from 'alertifyjs';

import './styles/main.scss';

import routes from './routing/routes';

import { backendRoutes } from './backendImport';

import initComponents from './application/_initComponents';
import { provideFASTDesignSystem, allComponents } from '@microsoft/fast-components';

async function initializeApp() {    
    const theClient = new RWSClient();

    theClient.setBackendRoutes(backendRoutes());
    theClient.addRoutes(routes);    
    
    theClient.onInit(async () => {
        initComponents();
        provideFASTDesignSystem().register(allComponents);
    });    


    theClient.setNotifier((message: string, logType: NotifyLogType, uiType: NotifyUiType = 'notification', onConfirm: (params: any) => void) => {
        switch(uiType){
            case 'notification':
                let notifType = 'success';

                if(logType === 'error'){
                    notifType = 'error';
                }

                if(logType === 'warning'){
                    notifType = 'warning';
                }

                alertify.notify(message, notifType, 5, onConfirm);
                return;
            case 'alert':
                alertify.alert('Junction AI Notification', message, onConfirm);
                return;    
            case 'silent':
                if(logType == 'warning'){
                    console.warn(message);
                }else if(logType == 'error'){
                    console.error(message);
                }else{
                    console.log(message);
                }            
                return;    
        }
    });
    (window as any).RWSClient = theClient;    
}

initializeApp().catch(console.error);

Component Initialization

In application/_initComponents.ts, you initialize the custom components used in your application. If components added in here will include other components they dont need to be listed here. A component imported in this mode needs to be imported once.

Default component structure

component-dir/
    component.ts
    template.html
    styles/
        layout.scss

WARNING All html templates refer to variable "T" as to FASTElement templating html scope. It contains all the functions FAST templates uses in html. F.e: T.html, T.when, T.repeat

<div class="convo-area-wrap">
    <header>  
        <div class="header-inner"></div>      
        ${T.when(x => x.noChoose === 'false',  (item, index) => T.html`<div>
            <chat-convo-models :chosenModel="${x => x.chosenModel}"></chat-convo-models>
        </div>`)}
        <div>
            <h2>${ x => x.chatContext ? x.chatContext.label : 'loading...' }</h2>
            <h3><strong>${ x => x.messageList.length }</strong> messages in total</h3>
        </div>   
        <fast-divider></fast-divider>             
    </header>
    <section>
        <div class="scroll-area">
            <div class="scroll-content">
                ${T.repeat(x => x.messageList,  (item, index) => T.html`
                    <chat-convo-message :contentReturn="${item => item}" :item="${item => item}"/>
                `)}      
                
                ${T.when(x => !x.messageList.length,  (item, index) => T.html`
                    <p class="no-chat">No messages</p>
                `)}   
            </div>
        </div>
    </section>  

</div>

application/_initComponents.ts

import { ChatNav } from '../components/chat-nav/component';
import { DefaultLayout } from '../components/default-layout/component';
import { RWSIcon } from '../components/rws-icon/component';
import { Loader } from '../components/loader/component';
import { LineSplitter } from '../components/line-splitter/component';

import { registerRWSComponents } from 'rws-js-client';

export default () => {
    LineSplitter;
    DefaultLayout;
    ChatNav;
    RWSIcon;
    Loader;
    registerRWSComponents(); //register rws components like <rws-uploader> and other comfy components
}
//index.ts

 const theClient = new RWSClient();

    
    theClient.addRoutes(routes);    //routes are optional
    
    theClient.onInit(async () => {
        initComponents(); //user components from _initComponents.ts
        provideFASTDesignSystem().register(allComponents); // @microsoft/fast-components ready components init
    });    

Component needs to extend RWSViewComponent and use @RWSView decorator:

import { RWSViewComponent,  RWSView, observable, attr } from 'rws-js-client';

const options?: RWSDecoratorOptions;

@RWSView('tag-name', options)
class WebChat extends RWSViewComponent {  

The decorator options type:

interface RWSDecoratorOptions{
    template?: string, //relative path to HTML template file (default: ./template.html)
    styles?: string //relative path to SCSS file (./styles/layout.scss)
    fastElementOptions?: any //the stuff you would insert into static definition in FASTElement class.
}

Frontend routes

if you are passing routes this is example routing file for frontend:

export default {
    '/': renderRouteComponent('Home page', WebChat),    
    '/the/path': renderRouteComponent('Component title', ComponentClassName),   
}

Router tag:

    <section>
        <rws-router></rws-router>
    </section>

Backend Imports

backendImports.ts consolidates various backend interfaces, routes, and models, allowing for a synchronized frontend and backend.

import IBook from '../../backend/src/models/interfaces/IBook';

import { 
    IBookInfo,  
  } from '../../backend/src/interfaces/IBookInfo';

import backendRoutes from '../../backend/src/routing/routes';

export { 
    IBook,
    IBookInfo,
    backendRoutes
}

usage:

    import { backendRoutes} from '../../backendImport';

    //index.ts
    const theClient = new RWSClient();
    theClient.setBackendRoutes(backendRoutes());

Utilizing APIService

APIService is used for making HTTP requests to the backend. It simplifies the process of interacting with your API endpoints.

after control method we have dynamic types those are: <ResponseType, PayloadType>

Example Usage by controller route

  const apiPromise: Promise<ITalkApiResponse> = ApiService.back.post<ITalkApiResponse, IApiTalkPayload>('talk:models:prompt', {        
        message: msg,
        model: this.chosenModel,
      });

Example Usage by url

  const apiPromise: Promise<ITalkApiResponse> = ApiService.post<ITalkApiResponse, IApiTalkPayload>('/api/path/to/action', {        
        message: msg,
        model: this.chosenModel,
      });

Notifier

Overview

The Notifier feature in the RWS Client is a versatile tool for handling notifications within the application. It allows for different types of user interface interactions like alerts, notifications, and silent logging, with varying levels of visibility and user interaction. Usage

Setting the Notifier

theClient.setNotifier((message: string, logType: NotifyLogType, uiType: NotifyUiType = 'notification', onConfirm: (params: any) => void) => {
    // Implementation based on uiType
});

This function allows setting a custom notifier in the RWS Client. It handles the logic based on uiType.

Alert, Notify, and Silent

  • alert: Displays an alert dialog with the message.
  • notify: Shows a notification with the message.
  • silent: Silently logs the message to the console.

Each method can be configured with a message, logType, and an optional onConfirm callback function.

Note

Ensure that a notifier is set in the RWS Client to use the NotifyService effectively. If no notifier is set, it will default to a warning in the console.

Service Worker

If you pass {serviceWorker: 'service_worker_class_path.ts'} to RWS Webpack wrapper function param, the code will build ServiceWorker to pubDir.

Remember to have lib field set in tesconfig.json

{
 "lib": ["DOM", "ESNext", "WebWorker"]
}

example ServiceWorker class:

import SWService, { ServiceWorkerServiceInstance } from 'rws-js-client/src/services/ServiceWorkerService'
import {TimeTracker} from '../services/TimeTrackerService';
import RWSServiceWorker from 'rws-js-client/src/service_worker/src/_service_worker';
import { RWSWSService as WSService } from 'rws-js-client/src/services/WSService'

declare const self: ServiceWorkerGlobalScope;

class ServiceWorker extends RWSServiceWorker {
    ignoredUrls = [
        new RegExp('(.*(?=.[^.]*$).*)/#/login'),
        new RegExp('(.*(?=.[^.]*$).*)/#/logout'),
    ];

    protected regExTypes = {
        FLASHCARDS_VIEW: new RegExp('.*:\\/\\/.*\\/#\\/([a-z0-9].*)\\/reports\\/flashcards$')
    };

    async onInit(): Promise<void>
    {        
        type ITheUser = any;

        let THE_USER: ITheUser | null = null;        
        const toSync: TimeTracker[] = [];

        let WS_URL: string | null;
        self.addEventListener('install', () => {
            console.log('Service Worker: Installed');
        });

        self.addEventListener('activate', () => {
            console.log('[SW] Service Worker: Activated'); 

            return self.clients.claim();
        });


        // Send a message to the client page
        const sendMessageToClient = (clientId: string, payload: any) => {
            return self.clients.get(clientId)
                .then((client: any) => {
                    if (client) {
                        client.postMessage(payload);
                    }
                });
        };

        interface MSGEvent{
        data?: {
            command: string,
            asset_type?: string,
            params: any
        }
        }

        const checkWs = (): void => {
            if(!WSService.socket() && WS_URL){
                WSService.init(WS_URL, THE_USER);
            }
        };

        // Listen for messages from the client page
        self.addEventListener('message', (event: MSGEvent) => {
            if(!event.data){
                return;
            }  

            if (event.data.command){
                console.log('[SW] OP Message:', event.data);
            
                switch (event.data.command) {
                case 'SET_WS_URL':
                    WS_URL = event.data.params.url;
                    break;
                case 'SET_USER':      
                    THE_USER = event.data.params;
                    checkWs();
                    break;
                case 'START_TRACKING':
                    checkWs();
                    if(!WSService.socket() && THE_USER){
                        break;
                    }
                    SWService.trackActivity(event.data.asset_type, event.data.params.page_location, event.data.params, toSync);
                    break;
                case 'TRACKER_SAVED':
                    const { clientId, tracker } = event.data.params;
        
                    sendMessageToClient(clientId, { message: 'TRACKER_SAVED_RESPONSE', data: tracker });
                    break;  
                }
            }
        });
    }
}

ServiceWorker.create();

Example: WebChat Component

The WebChat component demonstrates a practical use of APIService in a real-world scenario. It shows how to send and receive data from the backend.

WebChat Component Implementation

import { RWSViewComponent, ApiService, NotifyService, RWSView, WSService } from 'rws-js-client';
import { observable, css  } from '@microsoft/fast-element';

import './children/convo-footer/component';

import WebChatEvents from './events';
import { IContext } from './children/left-bar/component';
import { IMessage } from '../chat-message/component';
import { ITalkApiResponse, BedrockBaseModel, IHyperParameter, 

@RWSView('web-chat')
class WebChat extends RWSViewComponent {  

  @observable chatContext: IContext = null;
  @observable chosenModel: BedrockBaseModel = null;
  @observable injectMessages: IMessage[] = [];
  @observable hyperParameters: { key: string, value: any }[] = [];

  connectedCallback() {
    super.connectedCallback();
    
    this.on<{ item: IContext }>(WebChatEvents.item.click, (event: CustomEvent<{ item: IContext }>) => {           
      this.chatContext = event.detail.item;      
    });  

    this.on<{ item: BedrockBaseModel }>(WebChatEvents.model.set, (event: CustomEvent<{ item: BedrockBaseModel }>) => {
      if(!event.detail.item){
        this.chosenModel = null;
        return;
      }      

      this.chosenModel = {...event.detail.item};    

      this.setupModel();
    });  

    if(!this.chosenModel){
      this.chosenModel = ClaudeModel;
      this.setupModel();
    }

    this.on<{ item: IMessage }>(WebChatEvents.message.send, (event: CustomEvent<{ item: IMessage }>) => {          
      this.injectMessages = [event.detail.item];      
      // this.callStreamApi(event.detail.item);
      this.callTalkApi(event.detail.item);
    });    
   
  }  
  setupModel() {
    // other code
  }

  setHyperParam(key: string, value: any): void
  {
   // other code
  }

  
    
    this.hyperParameters = [
      ...this.hyperParameters,
      {
        key,
        value
      }
    ];
  }

  private getDefaultParams(provider: string | null)
  {
   // other code
  }


  private async callTalkApi(msg: IMessage): Promise<void>
  {

    type IApiTalkPayload = {
      message: IMessage;
      model: any;
    }    

    try {
      const apiPromise: Promise<ITalkApiResponse> = ApiService.back.post<ITalkApiResponse, IApiTalkPayload>('talk:models:prompt', {        
        message: msg,
        model: this.chosenModel,
      });
      
      this.injectMessages = [msg, {
        _promise: apiPromise,
        me: false,
        author: this.chosenModel.modelName, 
        content: null,
        model: this.chosenModel,
        created_at: new Date()
      }];        

    } catch(e: Error | any) {
      console.error(e);
    }      
  }

  private async callStreamApi(msg: IMessage): Promise<void>
  {

    type IApiTalkPayload = {
      message: IMessage;
      model: any;
    }    

    const llmStream = new ReadableStream();

    const sendMsg: IMessage = {       
      me: false,
      author: this.chosenModel.modelName, 
      content: null,
      model: this.chosenModel,
      created_at: new Date()
    };   

    WSService.sendMessage('send_msg', {
      modelId: this.chosenModel.modelId,
      prompt: msg.content
    });

    try {      
      this.injectMessages = [msg, {          
          ...sendMsg,
          _stream: llmStream,
        }];        

    } catch(e: Error | any) {
      console.error(e);
    }      
  }
}

WebChat.defineComponent();


export { WebChat }

Controller route

The route ApiService.back.get|post|put|delete methods can be found in backend controllers:

 @Route('talk:models:prompt', 'POST')
    public async modelTalkAction(params: IRequestParams): Promise<ITalkApiResponse>
    {
        // (...)
    }       

and src/config/config

    const http_routes = [    
        {
            prefix: '/prefix',
            routes: [
                {
                    name: 'action:route:name',
                    path: '/path/to/action'
                },
                 {
                    name: 'action:route:name',
                    path: '/path/to/action'
                }       
            ]
        },        
        {
            name: 'home:index',
            path: '/*', //if no routes detected pass request to frontend
            noParams: true, //do not read params from the request leave it to the front
        },       
    ]

Socket route

Socket route from

 WSService.sendMessage('send_msg', {
      modelId: this.chosenModel.modelId,
      prompt: msg.content
    });

are defined in backend/src/config/config

    const ws_routes = {
        'send_msg' : ChatSocket,
        'process_book' : TrainSocket,
    }

Links

Package Sidebar

Install

npm i rws-js-client

Weekly Downloads

45

Version

2.0.6

License

ISC

Unpacked Size

2.23 MB

Total Files

116

Last publish

Collaborators

  • papablack