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
24 changes: 18 additions & 6 deletions api/Assistant/assistant_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from api.db.pg_database import SessionLocal
from api.Assistant.assistant_repository import get_all_assistants, get_assistant_by_id_repository, delete_assistant_repository, update_assistant_repository
from api.Assistant.assistant_response_model import AssistantRequest, AssistantResponse, AssistantInfoResponse, AssistantListItemResponse, ContextResponse, UpdateAssistantRequest
from api.search.search_service import get_search_texts_details
from api.Assistant.assistant_repository import create_assistant_repository
from typing import List
from api.Assistant.assistant_model import Assistant, Context
Expand Down Expand Up @@ -75,10 +76,14 @@ async def create_assistant_service(token: str, assistant_request: AssistantReque
current_user_email = validate_and_extract_user_email(token=token)
contexts_list = []
for ctx in assistant_request.contexts:
if ctx.pecha_text_id:
search_details = await get_search_texts_details(ctx.pecha_text_id)
content = "\n\n".join([detail.content for detail in search_details]) if search_details else ""
else:
content = ctx.content
contexts_list.append(
Context(content=ctx.content, pecha_title=ctx.pecha_title, pecha_text_id=ctx.pecha_text_id)
Context(content=content, pecha_title=ctx.pecha_title, pecha_text_id=ctx.pecha_text_id)
)

if files:
for file in files:
if file.filename:
Expand Down Expand Up @@ -159,10 +164,17 @@ async def update_assistant_service(assistant_id: UUID, update_request: UpdateAss
if update_request.contexts is not None:
for context in assistant.contexts:
db_session.delete(context)
assistant.contexts = [
Context(content=ctx.content, pecha_title=ctx.pecha_title, pecha_text_id=ctx.pecha_text_id)
for ctx in update_request.contexts
]
new_contexts = []
for ctx in update_request.contexts:
if ctx.pecha_text_id:
search_details = await get_search_texts_details(ctx.pecha_text_id)
content = "\n\n".join([detail.content for detail in search_details]) if search_details else ""
else:
content = ctx.content
new_contexts.append(
Context(content=content, pecha_title=ctx.pecha_title, pecha_text_id=ctx.pecha_text_id)
)
assistant.contexts = new_contexts

assistant.updated_at = datetime.now(timezone.utc)

Expand Down
1 change: 0 additions & 1 deletion api/search/search_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
tags=["search"]
)


@search_router.get("/{text_id}", status_code=status.HTTP_200_OK, response_model=list[SearchTextsDetailsResponse])
async def read_texts_details(
text_id: str
Expand Down
114 changes: 12 additions & 102 deletions api/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -927,20 +927,6 @@ async def serve_ui():
renderFileField(fieldArea, '');
} else if (selectedType === 'search') {
renderSearchField(fieldArea, data.pecha_title || '', data.pecha_text_id || '');
// If editing and has content, create a mock search result with the content
if (data.content && data.pecha_title && data.pecha_text_id) {
const contentDataScript = document.createElement('script');
contentDataScript.type = 'application/json';
contentDataScript.className = 'ctx-pecha-content-data';
// Create a single item array with the content
contentDataScript.textContent = JSON.stringify([{
id: data.pecha_text_id,
content: data.content,
type: 'text',
source: 'pecha'
}]);
fieldArea.appendChild(contentDataScript);
}
}
}

Expand Down Expand Up @@ -1090,74 +1076,27 @@ async def serve_ui():
const titleText = el.textContent.trim();
const displayTitle = titleText.length > 80 ? titleText.slice(0,80) + '...' : titleText;

// Show loading state
el.innerHTML = '<span class="spinner" style="width:12px;height:12px;border-width:2px"></span> Loading content...';
el.style.pointerEvents = 'none';
titleInput.value = displayTitle;
idInput.value = id;

try {
// Call the search endpoint to fetch content
const r = await fetch(API_BASE + '/search/' + encodeURIComponent(id));
if (!r.ok) throw new Error('Failed to fetch content');
const searchData = await r.json();

// Store both the title and the fetched content
titleInput.value = displayTitle;
idInput.value = id;

// Store content data as JSON in a script tag to avoid HTML escaping issues
let contentScript = fieldArea.querySelector('.ctx-pecha-content-data');
if (!contentScript) {
contentScript = document.createElement('script');
contentScript.type = 'application/json';
contentScript.className = 'ctx-pecha-content-data';
fieldArea.appendChild(contentScript);
}
contentScript.textContent = JSON.stringify(searchData || []);

resultsDiv.style.display = 'none';
tagsDiv.innerHTML = '';
addPechaTag(tagsDiv, fieldArea, displayTitle, id, searchData);

toast('Content loaded successfully! (' + (searchData?.length || 0) + ' items)', 'success');
} catch(e) {
toast('Error loading content: ' + e.message, 'error');
el.innerHTML = displayTitle;
el.style.pointerEvents = '';
}
resultsDiv.style.display = 'none';
tagsDiv.innerHTML = '';
addPechaTag(tagsDiv, fieldArea, displayTitle, id, null);

toast('Pecha text selected!', 'success');
}

function addPechaTag(tagsDiv, fieldArea, title, id, searchData) {
const tag = document.createElement('div');
tag.style.cssText = 'margin-top:8px';

// Add main tag
tag.innerHTML = `
<div class="pecha-tag">
<span class="pecha-tag-text">${esc(title)}</span>
<button class="pecha-tag-remove" onclick="removePechaTag(this)">&times;</button>
</div>
`;

// If we have search data, show the content items
if (searchData && searchData.length > 0) {
const contentList = document.createElement('div');
contentList.style.cssText = 'margin-top:8px;display:flex;flex-direction:column;gap:8px';

searchData.forEach((item, idx) => {
const contentPreview = (item.content || '').substring(0, 150);
const itemDiv = document.createElement('div');
itemDiv.style.cssText = 'padding:8px 10px;background:var(--bg-input);border:1px solid var(--border);border-radius:var(--radius-xs);font-size:12px';
itemDiv.innerHTML = `
<div style="color:var(--text-secondary);line-height:1.5">
${esc(contentPreview)}${contentPreview.length >= 150 ? '...' : ''}
</div>
`;
contentList.appendChild(itemDiv);
});

tag.appendChild(contentList);
}

tagsDiv.appendChild(tag);
}

Expand All @@ -1167,12 +1106,6 @@ async def serve_ui():
fieldArea.querySelector('.ctx-pecha-title').value = '';
fieldArea.querySelector('.ctx-pecha-text-id').value = '';

// Remove the content data script if it exists
const contentDataScript = fieldArea.querySelector('.ctx-pecha-content-data');
if (contentDataScript) {
contentDataScript.remove();
}

// Clear the pecha tags div
const tagsDiv = fieldArea.querySelector('.pecha-tags');
if (tagsDiv) {
Expand All @@ -1191,36 +1124,13 @@ async def serve_ui():
} else if (type === 'search') {
const pecha_title = e.querySelector('.ctx-pecha-title')?.value.trim();
const pecha_text_id = e.querySelector('.ctx-pecha-text-id')?.value.trim();
const contentDataScript = e.querySelector('.ctx-pecha-content-data');

if (pecha_title && pecha_text_id) {
// Parse the stored JSON data
let searchData = [];
if (contentDataScript) {
try {
searchData = JSON.parse(contentDataScript.textContent || '[]');
} catch(err) {
console.error('Failed to parse search data', err);
}
}

// Create separate context entries for each search result
if (searchData.length > 0) {
searchData.forEach(item => {
contexts.push({
content: item.content || null,
pecha_title: pecha_title,
pecha_text_id: pecha_text_id
});
});
} else {
// No content loaded yet, just store the metadata
contexts.push({
content: null,
pecha_title: pecha_title,
pecha_text_id: pecha_text_id
});
}
contexts.push({
content: null,
pecha_title: pecha_title,
pecha_text_id: pecha_text_id
});
}
}
// Note: 'file' type contexts are handled separately via getFilesFromForm()
Expand Down