Skip to content
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
149 changes: 75 additions & 74 deletions src/lib/common/RemoteSearchInput.svelte
Original file line number Diff line number Diff line change
@@ -1,84 +1,85 @@
<script>
import { Input, Dropdown, DropdownMenu, DropdownItem, Spinner, DropdownToggle } from '@sveltestrap/sveltestrap';
import { debounce } from 'lodash';

/** @type {{id: string, name: string} | null} */
export let selectedValue;
export let disabled = false;
export let placeholder = '';
/**
import {
Input,
Dropdown,
DropdownMenu,
DropdownItem,
Spinner,
DropdownToggle
} from '@sveltestrap/sveltestrap';
import { debounce } from 'lodash';

* @type {(arg0: any) => any[] | PromiseLike<any[]>}
*/
export let onSearch;
export let loading = false;
/** @type {string} */
export let value;
export let disabled = false;
export let placeholder = '';
/**
* @type {(query: string) => Promise<({id: string; name: string} | string)[]>}
*/
export let onSearch;
export let loading = false;

/** @type {any[]} */
let searchResults = [];
let isOpen = false;
/** @type {({id: string; name: string} | string)[]} */
let searchResults = [];
let isOpen = false;

// @ts-ignore
const debouncedSearch = debounce(async (query) => {
if (query.length) {
loading = true;
searchResults = await onSearch(query);
loading = false;
isOpen = true;
} else {
searchResults = [];
isOpen = false;
}
}, 500);
// @ts-ignore
const debouncedSearch = debounce(async (query) => {
if (query.length) {
loading = true;
searchResults = await onSearch(query);
loading = false;
} else {
searchResults = [];
isOpen = false;
}
}, 500);

/**
* @param {any} e
*/
async function handleInput(e) {
const query = e.target.value;
selectedValue = { id: query, name: query };
await debouncedSearch(query);
}
/**
* @param {any} e
*/
async function handleInput(e) {
const query = e.target.value;
isOpen = true;
value = query;
await debouncedSearch(query);
}

/**
* @param {{ id: string; name: string; }} result
*/
function selectResult(result) {
selectedValue = result;
}

export function clearSearchResults() {
searchResults = [];
}
/**
* @param { {id: string; name: string} | string } result
*/
function selectResult(result) {
value = typeof result === 'string' ? result : result?.id;
}

export function clearSearchResults() {
searchResults = [];
}
</script>

<div class="position-relative">
<Dropdown class="scrollable-dropdown" isOpen={isOpen && (searchResults.length > 0 || loading)} toggle={() => isOpen = !isOpen}>
<DropdownToggle tag="div">
<Input
type="text"
value={selectedValue?.name}
on:input={handleInput}
{disabled}
{placeholder}
/>
</DropdownToggle>
<DropdownMenu class="w-100">
{#if loading}
<DropdownItem>
<Spinner size="sm" />
</DropdownItem>
{:else}
{#each searchResults as result, index}
<DropdownItem
active={selectedValue?.id === result.id}
on:click={() => selectResult(result)}
title={result.name}
>
{result.name}
</DropdownItem>
{/each}
{/if}
</DropdownMenu>
</Dropdown>
</div>
<Dropdown
class="scrollable-dropdown"
isOpen={isOpen && (searchResults.length > 0 || loading)}
toggle={() => (isOpen = !isOpen)}
>
<DropdownToggle tag="div">
<Input type="text" {value} on:input={handleInput} {disabled} {placeholder} />
</DropdownToggle>
<DropdownMenu class="w-100">
{#if loading}
<li class="text-center"><Spinner size="sm" /></li>
{:else}
{#each searchResults as result, index}
<DropdownItem
active={value === (typeof result === 'string' ? result : result?.id)}
on:click={() => selectResult(result)}
title={typeof result === 'string' ? result : result?.name}
>
{typeof result === 'string' ? result : result?.name}
</DropdownItem>
{/each}
{/if}
</DropdownMenu>
</Dropdown>
</div>
2 changes: 1 addition & 1 deletion src/lib/helpers/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ function skipLoader(config) {
new RegExp('http(s*)://(.*?)/role/(.*?)/details', 'g'),
new RegExp('http(s*)://(.*?)/user/(.*?)/details', 'g'),
new RegExp('http(s*)://(.*?)/agent/labels', 'g'),
new RegExp('http(s*)://(.*?)/conversation/state-search', 'g'),
new RegExp('http(s*)://(.*?)/conversation/state-key', 'g'),
];

if (config.method === 'post' && postRegexes.some(regex => regex.test(config.url || ''))) {
Expand Down
1 change: 0 additions & 1 deletion src/lib/services/api-endpoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ export const endpoints = {
conversationTagsUpdateUrl: `${host}/conversation/{conversationId}/update-tags`,
fileUploadUrl: `${host}/agent/{agentId}/conversation/{conversationId}/upload`,
pinConversationUrl: `${host}/agent/{agentId}/conversation/{conversationId}/dashboard`,
conversationStateValueUrl: `${host}/conversation/state-search`,
conversationStateKeyListUrl: `${host}/conversation/state-key`,

// LLM provider
Expand Down
28 changes: 8 additions & 20 deletions src/lib/services/conversation-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,36 +290,24 @@ export async function getAddressOptions(text) {
return response.data;
}

/**
* get conversation state key list
* @returns {Promise<{id: string, name: string, description: string}[]>}
*/
export async function getConversationStateKey() {
let url = endpoints.conversationStateKeyListUrl;
const response = await axios.get(url);
return response.data;
}

/** @type {import('axios').CancelTokenSource | null} */
let getConversationStateValueCancelToken = null;
let getConversationStatKeyCancelToken = null;
/**
* get conversation state value
* @param {string} key
* get conversation state key list
* @param {string} query
* @returns {Promise<{id: string, name: string}[]>}
*/
export async function getConversationStateValue(key, query) {
let url = endpoints.conversationStateValueUrl;
if (getConversationStateValueCancelToken) {
getConversationStateValueCancelToken.cancel();
export async function getConversationStateKey(query) {
let url = endpoints.conversationStateKeyListUrl;
if (getConversationStatKeyCancelToken) {
getConversationStatKeyCancelToken.cancel();
}
getConversationStateValueCancelToken = axios.CancelToken.source();
getConversationStatKeyCancelToken = axios.CancelToken.source();
const response = await axios.get(url, {
params: {
conversatinFilterType: key,
searchKey: query
},
cancelToken: getConversationStateValueCancelToken.token
cancelToken: getConversationStatKeyCancelToken.token
});
return response.data;
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
class="form-check-input"
type="checkbox"
bind:checked={agent.is_public}
on:input={handleAgentChange}
on:change={handleAgentChange}
id="is_public"
/>
<label class="form-check-label" for="is_public"> Public </label>
Expand All @@ -151,7 +151,7 @@
class="form-check-input"
type="checkbox"
bind:checked={agent.allow_routing}
on:input={handleAgentChange}
on:change={handleAgentChange}
id="allow_routing"
/>
<label class="form-check-label" for="allow_routing">Allow</label>
Expand Down Expand Up @@ -244,7 +244,7 @@
class="form-check-input"
type="checkbox"
bind:checked={agent.disabled}
on:input={handleAgentChange}
on:change={handleAgentChange}
id="disabled"
/>
<label class="form-check-label" for="disabled">Disabled</label>
Expand Down
70 changes: 21 additions & 49 deletions src/routes/page/conversation/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import LoadingToComplete from '$lib/common/LoadingToComplete.svelte';
import { onMount } from 'svelte';
import { getAgents } from '$lib/services/agent-service';
import { getConversations, deleteConversation, getConversationStateKey, getConversationStateValue } from '$lib/services/conversation-service.js';
import { getConversations, deleteConversation, getConversationStateKey } from '$lib/services/conversation-service.js';
import { utcToLocal } from '$lib/helpers/datetime';
import Swal from 'sweetalert2';
import lodash from "lodash";
Expand Down Expand Up @@ -70,27 +70,17 @@
tags: []
};

/** @type {any[]} */
let stateOptions = [];
/** @type {string} */
let stateKey = "";

/** @type {string | null} */
let selectedStateKey = null;

/** @type {{id: string, name: string} | null} */
let selectedStateValue;

/** @type {boolean} */
let isValueEditable = false;

/** @type {RemoteSearchInput} */
let remoteSearchInput;
let stateValue = null;

onMount(async () => {
isLoading = true;
Promise.all([
loadAgentOptions(),
loadSearchOption(),
loadStates(),
loadConversations()])
.finally(() => {
isLoading = false
Expand Down Expand Up @@ -229,12 +219,12 @@
* @param {any} e
*/
function handleConfirmStateModal(e) {
if (selectedStateKey && selectedStateValue?.id) {
if (stateKey && stateValue) {
searchOption.states = [
{
key: { data: stateOptions.find(x => Number(x.id) === Number(selectedStateKey))?.description, isValid: true },
value: {data: selectedStateValue.id, isValid: true },
active_rounds: {data: 0, isValid: true},
key: { data: stateKey, isValid: true },
value: {data: stateValue, isValid: true },
active_rounds: {data: -1, isValid: true},
}
];
} else {
Expand Down Expand Up @@ -348,28 +338,12 @@
}
}

async function loadStates() {
const response = await getConversationStateKey();
stateOptions = response;
}

/**
* @param { any } e
*/
function handleStateChange(e) {
selectedStateKey = e.target.value;
isValueEditable = !!selectedStateKey;
selectedStateValue = null;
remoteSearchInput?.clearSearchResults();
}

/**
* @param {any} query
*/
async function handleValueSearch(query) {
if (!selectedStateKey) return [];
const response = await getConversationStateValue(selectedStateKey, query);
return response;
async function handleStateSearch(query) {
const response = await getConversationStateKey(query);
return response || [];
}
</script>

Expand All @@ -386,20 +360,18 @@
<div style="width: 100%;">
<div class="state-search-container">
<div>
<select class="form-select" on:change={handleStateChange}>
<option value={null}>Select State</option>
{#each stateOptions as state}
<option value={state.id}>{state.name}</option>
{/each}
</select>
<RemoteSearchInput
bind:value={stateKey}
onSearch={handleStateSearch}
placeholder="Search States"
/>
</div>
<div>
<RemoteSearchInput
bind:selectedValue={selectedStateValue}
disabled={!isValueEditable}
onSearch={handleValueSearch}
placeholder="Enter a value"
bind:this={remoteSearchInput}
<Input
type="text"
bind:value={stateValue}
disabled={!stateKey}
placeholder="Enter a value"
/>
</div>
<div>
Expand Down