Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/playground #366

Merged
merged 7 commits into from
Oct 29, 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
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ services:
networks:
- ten_agent_network
ten_agent_playground:
image: ghcr.io/ten-framework/ten_agent_playground:0.5.0-56-g0536dbb
image: ghcr.io/ten-framework/ten_agent_playground:0.5.0-66-g830dd72
container_name: ten_agent_playground
restart: always
ports:
Expand Down
1 change: 1 addition & 0 deletions playground/src/common/constant.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { IOptions, ColorItem, LanguageOptionItem, VoiceOptionItem, GraphOptionItem } from "@/types"
export const GITHUB_URL = "https://github.com/TEN-framework/TEN-Agent"
export const OPTIONS_KEY = "__options__"
export const OVERRIDEN_PROPERTIES_KEY = "__overriden__"
export const DEFAULT_OPTIONS: IOptions = {
channel: "",
userName: "",
Expand Down
22 changes: 19 additions & 3 deletions playground/src/common/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client"

import { IMicrophoneAudioTrack } from "agora-rtc-sdk-ng"
import { normalizeFrequencies } from "./utils"
import { deepMerge, normalizeFrequencies } from "./utils"
import { useState, useEffect, useMemo, useRef } from "react"
import type { AppDispatch, AppStore, RootState } from "../store"
import { useDispatch, useSelector, useStore } from "react-redux"
Expand Down Expand Up @@ -132,13 +132,29 @@ export const usePrevious = (value: any) => {
export const useGraphExtensions = () => {
const graphName = useAppSelector(state => state.global.graphName);
const nodes = useAppSelector(state => state.global.extensions);
const overridenProperties = useAppSelector(state => state.global.overridenProperties);
const [graphExtensions, setGraphExtensions] = useState<Record<string, any>>({});

useEffect(() => {
if (nodes && nodes[graphName]) {
setGraphExtensions(nodes[graphName]);
let extensions:Record<string, any> = {}
let extensionsByGraph = JSON.parse(JSON.stringify(nodes[graphName]));
let overriden = overridenProperties[graphName] || {};
for (const key of Object.keys(extensionsByGraph)) {
if (!overriden[key]) {
extensions[key] = extensionsByGraph[key];
continue;
}
extensions[key] = {
addon: extensionsByGraph[key].addon,
name: extensionsByGraph[key].name,
};
extensions[key].property = deepMerge(extensionsByGraph[key].property, overriden[key]);
}
setGraphExtensions(extensions);
}
}, [graphName, nodes]);

}, [graphName, nodes, overridenProperties]);

return graphExtensions;
};
17 changes: 15 additions & 2 deletions playground/src/common/storage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IOptions } from "@/types"
import { OPTIONS_KEY, DEFAULT_OPTIONS } from "./constant"
import { OPTIONS_KEY, DEFAULT_OPTIONS, OVERRIDEN_PROPERTIES_KEY } from "./constant"

export const getOptionsFromLocal = () => {
if (typeof window !== "undefined") {
Expand All @@ -11,11 +11,24 @@ export const getOptionsFromLocal = () => {
return DEFAULT_OPTIONS
}

export const getOverridenPropertiesFromLocal = () => {
if (typeof window !== "undefined") {
const data = localStorage.getItem(OVERRIDEN_PROPERTIES_KEY)
if (data) {
return JSON.parse(data)
}
}
return {}
}

export const setOptionsToLocal = (options: IOptions) => {
if (typeof window !== "undefined") {
localStorage.setItem(OPTIONS_KEY, JSON.stringify(options))
}
}


export const setOverridenPropertiesToLocal = (properties: Record<string, any>) => {
if (typeof window !== "undefined") {
localStorage.setItem(OVERRIDEN_PROPERTIES_KEY, JSON.stringify(properties))
}
}
10 changes: 10 additions & 0 deletions playground/src/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,14 @@ export const genUUID = () => {

export const isMobile = () => {
return /Mobile|iPhone|iPad|Android|Windows Phone/i.test(navigator.userAgent)
}

export const deepMerge = (target: Record<string, any>, source: Record<string, any>): Record<string, any> => {
for (const key of Object.keys(source)) {
if (source[key] instanceof Object && key in target) {
Object.assign(source[key], deepMerge(target[key], source[key]));
}
}
// Merge source into target
return { ...target, ...source };
}
8 changes: 5 additions & 3 deletions playground/src/components/authInitializer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"use client"

import { ReactNode, useEffect } from "react"
import { useAppDispatch, getOptionsFromLocal, getRandomChannel, getRandomUserId } from "@/common"
import { setOptions, reset } from "@/store/reducers/global"
import { useAppDispatch, getOptionsFromLocal, getRandomChannel, getRandomUserId, getOverridenPropertiesFromLocal } from "@/common"
import { setOptions, reset, setOverridenProperties } from "@/store/reducers/global"

interface AuthInitializerProps {
children: ReactNode;
Expand All @@ -15,16 +15,18 @@ const AuthInitializer = (props: AuthInitializerProps) => {
useEffect(() => {
if (typeof window !== "undefined") {
const options = getOptionsFromLocal()
const overridenProperties = getOverridenPropertiesFromLocal()
if (options && options.channel) {
dispatch(reset())
dispatch(setOptions(options))
} else {
dispatch(reset())
dispatch(setOptions({
dispatch(setOptions({
channel: getRandomChannel(),
userId: getRandomUserId(),
}))
}
dispatch(setOverridenProperties(overridenProperties))
}
}, [dispatch])

Expand Down
21 changes: 14 additions & 7 deletions playground/src/platform/pc/chat/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
useGraphExtensions,
apiGetExtensionMetadata,
} from "@/common"
import { setExtensionMetadata, setGraphName, setGraphs, setLanguage, setExtensions } from "@/store/reducers/global"
import { setExtensionMetadata, setGraphName, setGraphs, setLanguage, setExtensions, setOverridenPropertiesByGraph, setOverridenProperties } from "@/store/reducers/global"
import { Button, Modal, Select, Tabs, TabsProps, } from 'antd';
import PdfSelect from "@/components/pdfSelect"

Expand All @@ -31,6 +31,7 @@ const Chat = () => {
const [modal2Open, setModal2Open] = useState(false)
const graphExtensions = useGraphExtensions()
const extensionMetadata = useAppSelector(state => state.global.extensionMetadata)
const overridenProperties = useAppSelector(state => state.global.overridenProperties)


// const chatItems = genRandomChatList(10)
Expand Down Expand Up @@ -93,9 +94,14 @@ const Chat = () => {
open={modal2Open}
onCancel={() => setModal2Open(false)}
footer={
<Button type="primary" onClick={() => setModal2Open(false)}>
Close
</Button>
<>
<Button type="default" onClick={() => { dispatch(setOverridenProperties({})) }}>
Clear Settings
</Button>
<Button type="primary" onClick={() => setModal2Open(false)}>
Close
</Button>
</>
}
>
<p>You can adjust extension properties here, the values will be overridden when the agent starts using "Connect." Note that this won't modify the property.json file.</p>
Expand All @@ -109,9 +115,10 @@ const Chat = () => {
initialData={node["property"] || {}}
metadata={metadata ? metadata.api.property : {}}
onUpdate={(data) => {
let nodesMap = JSON.parse(JSON.stringify(graphExtensions))
nodesMap[key]["property"] = data
dispatch(setExtensions({ graphName, nodesMap }))
// clone the overridenProperties
let nodesMap = JSON.parse(JSON.stringify(overridenProperties[graphName] || {}))
nodesMap[key] = data
dispatch(setOverridenPropertiesByGraph({ graphName, nodesMap }))
}}
></EditableTable>
}
Expand Down
29 changes: 19 additions & 10 deletions playground/src/platform/pc/chat/table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,18 @@ const convertToType = (value: any, type: string) => {
};

const EditableTable: React.FC<EditableTableProps> = ({ initialData, onUpdate, metadata }) => {
const [dataSource, setDataSource] = useState<DataType[]>(
Object.entries(initialData).map(([key, value]) => ({ key, value }))
);
const [dataSource, setDataSource] = useState<DataType[]>([]);
const [editingKey, setEditingKey] = useState<string>('');
const [form] = Form.useForm();
const inputRef = useRef<InputRef>(null); // Ref to manage focus
const updatedValuesRef = useRef<Record<string, string | number | boolean | null>>({});

// Update dataSource whenever initialData changes
useEffect(() => {
setDataSource(
Object.entries(initialData).map(([key, value]) => ({ key, value }))
);
}, [initialData]);

// Function to check if the current row is being edited
const isEditing = (record: DataType) => record.key === editingKey;
Expand All @@ -65,16 +71,17 @@ const EditableTable: React.FC<EditableTableProps> = ({ initialData, onUpdate, me
setDataSource(newData);
setEditingKey('');

// Notify the parent component of the update
const updatedData = Object.fromEntries(newData.map(({ key, value }) => [key, value]));
onUpdate(updatedData);
// Store the updated value in the ref
updatedValuesRef.current[key] = updatedValue;

// Notify the parent component of only the updated value
onUpdate({ [key]: updatedValue });
}
} catch (errInfo) {
console.log('Validation Failed:', errInfo);
}
};


// Toggle the checkbox for boolean values directly in the table cell
const handleCheckboxChange = (key: string, checked: boolean) => {
const newData = [...dataSource];
Expand All @@ -83,9 +90,11 @@ const EditableTable: React.FC<EditableTableProps> = ({ initialData, onUpdate, me
newData[index].value = checked; // Update the boolean value
setDataSource(newData);

// Notify the parent component of the update
const updatedData = Object.fromEntries(newData.map(({ key, value }) => [key, value]));
onUpdate(updatedData);
// Store the updated value in the ref
updatedValuesRef.current[key] = checked;

// Notify the parent component of only the updated value
onUpdate({ [key]: checked });
}
};

Expand Down
13 changes: 4 additions & 9 deletions playground/src/platform/pc/description/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { setAgentConnected } from "@/store/reducers/global"
import {
useAppDispatch, useAppSelector, apiPing, genUUID,
apiStartService, apiStopService,
useGraphExtensions
apiStartService, apiStopService
} from "@/common"
import { Select, Button, message, Upload } from "antd"
import { useEffect, useState, MouseEventHandler } from "react"
Expand All @@ -20,7 +19,7 @@ const Description = () => {
const voiceType = useAppSelector(state => state.global.voiceType)
const [loading, setLoading] = useState(false)
const graphName = useAppSelector(state => state.global.graphName)
const graphNodes = useGraphExtensions()
const overridenProperties = useAppSelector(state => state.global.overridenProperties)

useEffect(() => {
if (channel) {
Expand All @@ -47,18 +46,14 @@ const Description = () => {
message.success("Agent disconnected")
stopPing()
} else {
let properties: Record<string, any> = {}
Object.keys(graphNodes).forEach(extensionName => {
properties[extensionName] = {}
properties[extensionName] = graphNodes[extensionName].property
})
let properties: Record<string, any> = overridenProperties[graphName] || {}
const res = await apiStartService({
channel,
userId,
graphName,
language,
voiceType,
properties: properties
properties
})
const { code, msg } = res || {}
if (code != 0) {
Expand Down
15 changes: 13 additions & 2 deletions playground/src/store/reducers/global.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IOptions, IChatItem, Language, VoiceType } from "@/types"
import { createSlice, PayloadAction } from "@reduxjs/toolkit"
import { DEFAULT_OPTIONS, COLOR_LIST, setOptionsToLocal, genRandomChatList } from "@/common"
import { DEFAULT_OPTIONS, COLOR_LIST, setOptionsToLocal, genRandomChatList, setOverridenPropertiesToLocal, deepMerge } from "@/common"

export interface InitialState {
options: IOptions
Expand All @@ -13,6 +13,7 @@ export interface InitialState {
graphName: string,
graphs: string[],
extensions: Record<string, any>,
overridenProperties: Record<string, any>,
extensionMetadata: Record<string, any>
}

Expand All @@ -28,6 +29,7 @@ const getInitialState = (): InitialState => {
graphName: "camera_va_openai_azure",
graphs: [],
extensions: {},
overridenProperties: {},
extensionMetadata: {},
}
}
Expand Down Expand Up @@ -100,6 +102,15 @@ export const globalSlice = createSlice({
let { graphName, nodesMap } = action.payload
state.extensions[graphName] = nodesMap
},
setOverridenProperties: (state, action: PayloadAction<Record<string, any>>) => {
state.overridenProperties = action.payload
setOverridenPropertiesToLocal(state.overridenProperties)
},
setOverridenPropertiesByGraph: (state, action: PayloadAction<Record<string, any>>) => {
let { graphName, nodesMap } = action.payload
state.overridenProperties[graphName] = deepMerge(state.overridenProperties[graphName] || {}, nodesMap)
setOverridenPropertiesToLocal(state.overridenProperties)
},
setExtensionMetadata: (state, action: PayloadAction<Record<string, any>>) => {
state.extensionMetadata = action.payload
},
Expand All @@ -115,7 +126,7 @@ export const globalSlice = createSlice({

export const { reset, setOptions,
setRoomConnected, setAgentConnected, setVoiceType,
addChatItem, setThemeColor, setLanguage, setGraphName, setGraphs, setExtensions, setExtensionMetadata } =
addChatItem, setThemeColor, setLanguage, setGraphName, setGraphs, setExtensions, setExtensionMetadata, setOverridenProperties, setOverridenPropertiesByGraph } =
globalSlice.actions

export default globalSlice.reducer