Skip to content

Conversation

@iceljc
Copy link
Collaborator

@iceljc iceljc commented Aug 13, 2025

PR Type

Enhancement


Description

  • Add vector index management modal for payload indexes

  • Enhance knowledge base search with advanced filtering

  • Improve responsive design for mobile/tablet screens

  • Add function visibility mode to agent configuration


Diagram Walkthrough

flowchart LR
  A["Agent Overview"] -- "adds" --> B["Function Visibility Mode"]
  C["Knowledge Base"] -- "creates" --> D["Vector Index Modal"]
  E["Advanced Search"] -- "enhances" --> F["Payload Data Types"]
  G["Vector Items"] -- "updates" --> H["Data Structure"]
  I["SCSS Styles"] -- "improves" --> J["Responsive Design"]
Loading

File Walkthrough

Relevant files
Enhancement
14 files
_knowledgebase.scss
Add responsive design and vector index styling                     
+346/-16
agent-overview.svelte
Add function visibility mode configuration                             
+37/-4   
vector-index-modal.svelte
Create vector index management modal component                     
+462/-0 
advanced-search.svelte
Add data type selection to search filters                               
+35/-7   
vector-item-edit-modal.svelte
Update payload structure with data types                                 
+55/-25 
vector-item.svelte
Update data display for new payload structure                       
+29/-24 
+page.svelte
Integrate vector index modal and update filtering               
+157/-60
+page.svelte
Add index management and update data handling                       
+194/-69
enums.js
Add function visibility and payload data type enums           
+24/-0   
agentTypes.js
Add function visibility mode property                                       
+1/-0     
commonTypes.js
Add success/fail response type definition                               
+6/-0     
knowledgeTypes.js
Update filter structure and add collection details             
+56/-2   
api-endpoints.js
Add vector index and collection detail endpoints                 
+3/-0     
knowledge-base-service.js
Add index management and collection detail services           
+49/-6   
Configuration changes
2 files
+page.svelte
Change default time range to 12 hours                                       
+1/-1     
collection-create-modal.svelte
Update default embedding model and dimension                         
+2/-2     

@qodo-merge-pro
Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Type Safety

The handler changeFunctionVisibilityMode accesses e.detail.selecteds with a ts-ignore. Verify the Select component’s event shape or add a proper type guard to avoid runtime errors when the event payload changes or is undefined.

    /**
	 * @param {any} e
	 */
    function changeFunctionVisibilityMode(e) {
        // @ts-ignore
        const values = e?.detail?.selecteds?.map(x => x.value) || [];
        agent.function_visibility_mode = values[0] || null;
        handleAgentChange();
    }
UX Consistency

The new function visibility control uses the custom Select component (with label/value pairs) while routing mode uses a native <Input type="select"> (with id/name). Consider aligning component usage and option shapes to keep UI/behavior consistent and reduce conversion bugs.

<th class="agent-prop-key" style="vertical-align: middle">
    <div class="mt-1">
        Function visibility
    </div>
</th>
<td>							
    <div class="mt-2 mb-2">
        <Select
            tag={'function-visibility-mode-select'}
            containerStyles={'width: 50%; min-width: 100px;'}
            placeholder={'Select'}
            selectedValues={agent.function_visibility_mode ? [agent.function_visibility_mode] : []}
            options={functionVisibilityModeOptions}
            on:select={e => changeFunctionVisibilityMode(e)}
        />
    </div>
</td>

@qodo-merge-pro
Copy link

qodo-merge-pro bot commented Aug 13, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Persist and validate new mode

Introducing function_visibility_mode without backend contract or persistence
changes risks silent no-ops and inconsistent behavior across clients. Ensure the
API, storage schema, and server-side validation/enum mapping support this new
field and values (manual/auto), and that existing agents receive a safe
default/migration to avoid undefined states.

Examples:

src/routes/page/agent/[agentId]/agent-components/agent-overview.svelte [89-94]
function changeFunctionVisibilityMode(e) {
    // @ts-ignore
    const values = e?.detail?.selecteds?.map(x => x.value) || [];
    agent.function_visibility_mode = values[0] || null;
    handleAgentChange();
}
src/lib/helpers/types/agentTypes.js [43-50]
/**
 * @typedef {Object} AgentModel
 * @property {string} id - Agent Id.
 * @property {string} name - Agent name.
 * @property {string} description - Agent description.
 * @property {string} type - Agent type
 * @property {string?} mode - Agent routing mode
 * @property {string?} function_visibility_mode - Agent function visibility mode

Solution Walkthrough:

Before:

// agent-overview.svelte
function changeFunctionVisibilityMode(e) {
    // ...
    agent.function_visibility_mode = values[0] || null;
    handleAgentChange(); // This likely calls an API to save the agent
}

// Backend (missing from PR)
// No API logic to handle `function_visibility_mode`.
// No database schema update for `function_visibility_mode`.

After:

// Backend API Controller (conceptual)
function updateAgent(agentData) {
    // Validate agentData.function_visibility_mode against allowed values
    if (!['manual', 'auto'].includes(agentData.function_visibility_mode)) {
        throw new Error("Invalid function visibility mode");
    }
    // Persist the new field to the database
    db.agents.update({ id: agentData.id }, {
        ...otherFields,
        function_visibility_mode: agentData.function_visibility_mode
    });
}
Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a critical flaw: the PR adds the function_visibility_mode property only on the frontend, without the necessary backend changes for persistence, making the feature non-functional.

High
Possible issue
Add guards and change checks

Guard against missing agent before assignment to prevent runtime errors during
initial render or unmounted states. Also, update the select handler to no-op if
no change occurred to avoid unnecessary updates.

src/routes/page/agent/[agentId]/agent-components/agent-overview.svelte [80-94]

 function changeRoutingMode(e) {
-    const value = e.target.value || null;
+    const value = e.target?.value ?? null;
+    if (!agent || agent.mode === value) return;
     agent.mode = value;
     handleAgentChange();
 }
 
 /**
  * @param {any} e
  */
 function changeFunctionVisibilityMode(e) {
     // @ts-ignore
     const values = e?.detail?.selecteds?.map(x => x.value) || [];
-    agent.function_visibility_mode = values[0] || null;
+    const next = values[0] ?? null;
+    if (!agent || agent.function_visibility_mode === next) return;
+    agent.function_visibility_mode = next;
     handleAgentChange();
 }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that adding checks for the existence of agent and for value changes improves code robustness and prevents unnecessary updates, which is a good practice.

Medium
General
Enforce single-select behavior

Ensure the Select component operates in single-select mode to match the state
shape (string|null). Explicitly set single-select props or disable
multi-selection to avoid multiple values and inconsistent assignments.

src/routes/page/agent/[agentId]/agent-components/agent-overview.svelte [327-334]

 <Select
     tag={'function-visibility-mode-select'}
     containerStyles={'width: 50%; min-width: 100px;'}
     placeholder={'Select'}
+    multiple={false}
     selectedValues={agent.function_visibility_mode ? [agent.function_visibility_mode] : []}
     options={functionVisibilityModeOptions}
     on:select={e => changeFunctionVisibilityMode(e)}
 />
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly points out that explicitly setting multiple={false} on the Select component makes the intent clear and prevents potential bugs, aligning with the single-value logic in changeFunctionVisibilityMode.

Low
Use enum keys as labels

Use enum keys for labels while keeping values as the canonical enum values to
improve UX and avoid locale issues. This prevents ambiguous labels if values
change and keeps display consistent.

src/routes/page/agent/[agentId]/agent-components/agent-overview.svelte [31-33]

 const functionVisibilityModeOptions = Object.entries(FunctionVisMode).map(([k, v]) => (
-	{ label: v, value: v }
+	{ label: k, value: v }
 ));
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly proposes using the enum keys (Manual, Auto) for display labels instead of the values (manual, auto), which improves user experience by showing more readable text.

Low
  • Update

@iceljc iceljc marked this pull request as draft August 15, 2025 03:15
@iceljc iceljc marked this pull request as ready for review August 18, 2025 22:26
@qodo-merge-pro
Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Data Loss Risk

Moving an index from the add list to the delete list (and vice versa) simply transfers the object without cloning; subsequent edits could unintentionally mutate both lists if references are shared. Verify immutability/cloning when transferring items to avoid side effects.

 * @param {number} idx
 */
function moveItemToDelete(e, idx) {
    e.preventDefault();

    const item = indexesToAdd[idx];
    indexesToDelete = [...indexesToDelete, item];
    indexesToAdd = indexesToAdd.filter((_, i) => i !== idx);
}

/** @param {any} e */
async function addToDeleteIndex(e) {
    e.preventDefault();

    indexesToDelete = [...indexesToDelete, { field_name: '', field_schema_type: '' }];

    if (deleteIndexScrollContainer) {
        await tick();
        setTimeout(() => {
            deleteIndexScrollContainer.scrollTo({
                top: deleteIndexScrollContainer.scrollHeight,
                behavior: 'smooth'
            });
        }, 0);
    }
}

/**
 * @param {any} e
 * @param {number} index
 */
function removeFromDeleteList(e, index) {
    e.preventDefault();
    indexesToDelete = indexesToDelete.filter((_, i) => i !== index);
}

/**
 * @param {number} idx
 * @param {string} value
 */
function updateIndexFieldName(idx, value) {
    indexesToAdd[idx].field_name = value;
    indexesToAdd = [...indexesToAdd];
}

/**
 * @param {any} e
 * @param {number} idx
 */
function updateIndexFieldType(e, idx) {
    // @ts-ignore
    const selectedValues = e.detail.selecteds?.map(x => x.value) || [];
    indexesToAdd[idx].field_schema_type = selectedValues.length > 0 ? selectedValues[0] : '';
    indexesToAdd = [...indexesToAdd];
}


/**
 * @param {number} idx
 * @param {string} value
 */
function updateDeleteIndexFieldName(idx, value) {
    indexesToDelete[idx].field_name = value;
    indexesToDelete = [...indexesToDelete];
}

/** @param {any} e */
function moveAllToDelete(e) {
    e.preventDefault();
    indexesToDelete = [...indexesToDelete, ...indexesToAdd];
    indexesToAdd = [];
}

/** @param {any} e */
function moveAllToAdd(e) {
    e.preventDefault();
    indexesToAdd = [...indexesToAdd, ...indexesToDelete];
    indexesToDelete = [];
}

/**
 * @param {any} e
 * @param {number} idx
 */
function moveItemToAdd(e, idx) {
    e.preventDefault();
    const item = indexesToDelete[idx];
    indexesToDelete = indexesToDelete.filter((_, i) => i !== idx);
    indexesToAdd = [...indexesToAdd, item];
}
API Contract Change

create/update vector knowledge payload and filter structures changed to typed objects and nested operators. Ensure backend endpoints accept new payload shapes (e.g., payload fields with data_value/data_type and new VectorFilterGroup schema) to prevent runtime errors.

		isOpenEditKnowledge = false;
		e.payload = {
			...e.payload || {},
			answer: {
				data_value: e.data?.answer,
				data_type: VectorPayloadDataType.String.name
			},
			dataSource: {
				data_value: e.payload?.dataSource?.data_value || VectorDataSource.User,
				data_type: VectorPayloadDataType.String.name
			}
		};

		if (!!editItem) {
			updateVectorKnowledgeData(
				e.id,
				editCollection,
				e.data?.text,
				e.payload
			).then(res => {
				if (res) {
					isComplete = true;
					refreshItems(e);
					resetEditData();
					successText = "Knowledge has been updated!";
					setTimeout(() => {
						isComplete = false;
					}, duration);
				} else {
					throw 'error when updating vector knowledge!';
				}
			}).catch(() => {
				resetEditData();
				isError = true;
				errorText = "Error when updating knowledge!";
				setTimeout(() => {
					isError = false;
				}, duration);
			}).finally(() => {
				isLoading = false;
			});
		} else {
			createVectorKnowledgeData(
				editCollection,
				e.data?.text,
				e.payload
			).then(res => {
				if (res) {
					isComplete = true;
					refreshItems(e);
					resetEditData();
					reset(true);
					successText = "Knowledge has been created!";
					setTimeout(() => {
						isComplete = false;
					}, duration);
				} else {
					throw 'error when creating vector knowledge!';
				}
			}).catch(() => {
				resetEditData();
				isError = true;
				errorText = "Error when creating knowledge!";
				setTimeout(() => {
					isError = false;
				}, duration);
			}).finally(() => {
				isLoading = false;
			});
		}
	}

	/** @param {any} newItem */
	function refreshItems(newItem) {
		const found = items?.find(x => x.id == newItem?.id);
		if (found) {
			const payload = Object.keys(newItem.payload || {}).reduce((acc, key) => {
				// @ts-ignore
				acc[key] = {
					data_value: newItem.payload[key]?.data_value,
					data_type: newItem.payload[key]?.data_type
				};
				return acc;
			}, {});

			const newData = {
				text: {
					data_value: newItem.data?.text || '',
					data_type: VectorPayloadDataType.String.name
				},
				answer: {
					data_value: newItem.data?.answer || '',
					data_type: VectorPayloadDataType.String.name
				},
				...payload
			};

			found.data = { ...newData };
			items = [ ...items ];
		}
	}

	/** @param {any} e */
	function changeCollection(e) {
		const selectedValues = e?.detail?.selecteds || [];
		selectedCollection = selectedValues[0]?.value;
		reset();
	}

	function toggleCollectionCreate() {
		isOpenCreateCollection = !isOpenCreateCollection;
	}

	/** @param {import('$knowledgeTypes').CreateVectorCollectionRequest} data
	*/
	function confirmCollectionCreate(data) {
		isLoading = true;
		toggleCollectionCreate();
		createVectorCollection({
			collection_name: data.collection_name,
			collection_type: collectionType,
			dimension: data.dimension,
			provider: data.provider,
			model: data.model
		}).then(res => {
			if (res) {
				successText = "Collection has been created!";
				isComplete = true;
				setTimeout(() => {
					isComplete = false;
				}, duration);
				initPage();
			} else {
				throw 'Error when creating collection';
			}
		}).catch(() => {
			errorText = "Failed to create collection."
			isError = true;
			setTimeout(() => {
				isError = false;
			}, duration);
		}).finally(() => {
			isLoading = false;
		});
	}

	function deleteCollection() {
        Swal.fire({
            title: 'Are you sure?',
            text: `Are you sure you want to delete collection "${selectedCollection}"?`,
            icon: 'warning',
			customClass: { confirmButton: 'danger-background' },
            showCancelButton: true,
            cancelButtonText: 'No',
            confirmButtonText: 'Yes',
        }).then(async (result) => {
            if (result.value) {
				isLoading = true;
                deleteVectorCollection(selectedCollection).then(res => {
					if (res) {
						successText = "Collection has been deleted!";
						isComplete = true;
						setTimeout(() => {
							isComplete = false;
						}, duration);
						initPage();
					} else {
						throw 'Error when deleting vector collection';
					}
				}).catch(() => {
					errorText = "Failed to delete collection."
					isError = true;
					setTimeout(() => {
						isError = false;
					}, duration);
				}).finally(() => {
					isLoading = false;
				});
            }
        });
	}


	/**
	 *  @param {any[]} items
	 *  @returns {import('$knowledgeTypes').VectorFilterGroup[]}
	 */
	function buildSearchFilterGroups(items) {
		/** @type {import('$knowledgeTypes').VectorFilterGroup[]} */
		let groups = [];

		if (textSearch && !!text) {
			groups = [ ...groups, { logical_operator: 'or', filters: [
				{
					logical_operator: 'or',
					operands: [
						{ match: { key: KnowledgePayloadName.Text, value: text, data_type: VectorPayloadDataType.String.name, operator: 'eq' }},
						{ match: { key: KnowledgePayloadName.Answer, value: text, data_type: VectorPayloadDataType.String.name, operator: 'eq' }}
					]
				},
			]}];
		}

		if (isAdvSearchOn && items?.length > 0) {
			const validItems = items.filter(x => x.checked && !!util.trim(x.key) && !!util.trim(x.value))
									.map(x => ({ key: util.trim(x.key), value: util.trim(x.value), data_type: x.data_type || VectorPayloadDataType.String.name }));

			if (validItems.length > 0) {
				groups = [ ...groups, { logical_operator: 'or', filters: [
					{
						logical_operator: selectedOperator,
						operands: validItems.map(x => ({ match: { ...x, operator: 'eq' } }))
					}
				]}];
			}
Null Safety

Rendering score uses toFixed on possibly undefined values; though updated to optional chaining in one place, confirm all occurrences guard against undefined to avoid runtime errors.

    <div class="wrappable fw-bold text-primary">
        {'Score:'}
    </div>
    <div class="wrappable">{`${item.score?.toFixed(6)}`}</div>
</li>

@qodo-merge-pro
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Data schema compatibility risk

The PR changes vector payload and data models (e.g., payload fields now carry
{data_value, data_type}, new filter schema, index APIs) but does not include any
visible migration/normalization layer for existing data or backward
compatibility in API calls. Without coordinated backend support, data migration,
and feature flags, this may break existing collections, searches, and edits at
runtime. Consider gating these changes behind versioned APIs or adapters with
explicit migration/rollback strategy and compatibility handling for legacy
payload structures.

Examples:

src/routes/page/knowledge-base/common/vector-table/vector-item.svelte [82]
        <div class="ellipsis">{item?.data?.text?.data_value || item?.data?.question?.data_value || ''}</div>
src/routes/page/knowledge-base/common/vector-table/vector-item-edit-modal.svelte [110-120]
        innerPayloads = Object.keys(item?.data || {}).filter(key => !excludedPayloads.includes(key)).map(key => {
            const foundType = dataTypeOptions.find(x => x.value === item?.data[key]?.data_type);
            return {
                uuid: uuidv4(),
                key: key,
                value: {
                    ...item?.data[key] || {},
                    data_type: foundType?.value
                }
            };

 ... (clipped 1 lines)

Solution Walkthrough:

Before:

// Data is fetched assuming the old schema
function init() {
    question.text = item?.data?.text || item?.data?.question || '';
    innerPayloads = Object.keys(item?.data || {}).map(key => {
        return {
            uuid: uuidv4(),
            key: key,
            value: item?.data[key]
        };
    });
}

After:

// Adapter logic to handle both old and new schemas
function dataAdapter(data) {
    if (typeof data === 'object' && data !== null && 'data_value' in data) {
        return data; // New schema
    }
    return { data_value: data, data_type: 'String' }; // Old schema to new
}

function init() {
    question.text = dataAdapter(item?.data?.text)?.data_value || '';
    // ... similar logic for other fields and payloads
}
Suggestion importance[1-10]: 10

__

Why: This suggestion correctly identifies a critical architectural risk of introducing breaking data schema changes without a migration or backward compatibility strategy, which could severely impact existing data and functionality.

High
General
Default data type to String

The advanced search builder later defaults empty data_type to String, but
leaving it blank here can lead to inconsistent UI state and accidental invalid
filters. Initialize data_type to the default (e.g., 'String') to match
downstream expectations and prevent nulls in emitted filters.

src/routes/page/knowledge-base/common/search/advanced-search.svelte [83-85]

 function reset() {
-    items = [{ uuid: uuidv4(), key: '', value: '', data_type: '', checked: true }];
+    items = [{ uuid: uuidv4(), key: '', value: '', data_type: 'String', checked: true }];
 }
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: This is a good suggestion for improving UI consistency; while there is fallback logic, initializing data_type with the default value makes the component's state more explicit and accurately reflects what will be used in the search filter.

Low
  • More

@iceljc iceljc merged commit 82fc8e6 into SciSharp:main Aug 20, 2025
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant