Skip to content

Commit 185bca8

Browse files
committed
refac
1 parent b16a4c4 commit 185bca8

20 files changed

Lines changed: 693 additions & 156 deletions

backend/open_webui/models/chats.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -424,11 +424,27 @@ async def get_internal_chat_by_note_id(
424424
Chat.meta['type'].as_string() == 'note',
425425
Chat.meta['note_id'].as_string() == note_id,
426426
)
427-
.order_by(Chat.created_at.asc())
427+
.order_by(Chat.updated_at.desc(), Chat.created_at.desc())
428428
)
429429
chat = result.scalars().first()
430430
return ChatModel.model_validate(chat) if chat else None
431431

432+
async def get_internal_chats_by_note_id(
433+
self, note_id: str, user_id: str, db: AsyncSession | None = None
434+
) -> list[ChatModel]:
435+
async with get_async_db_context(db) as session:
436+
result = await session.execute(
437+
select(Chat)
438+
.where(
439+
Chat.user_id == user_id,
440+
Chat.meta['internal'].as_boolean().is_(True),
441+
Chat.meta['type'].as_string() == 'note',
442+
Chat.meta['note_id'].as_string() == note_id,
443+
)
444+
.order_by(Chat.updated_at.desc(), Chat.created_at.desc())
445+
)
446+
return [ChatModel.model_validate(chat) for chat in result.scalars().all()]
447+
432448
def _chat_import_form_to_chat_model(self, user_id: str, form_data: ChatImportForm) -> ChatModel:
433449
id = str(uuid.uuid4())
434450
chat = ChatModel(
@@ -1545,14 +1561,12 @@ async def get_chats_by_user_id_and_search_text(
15451561

15461562
# Check if there are any tags to filter
15471563
if 'none' in tag_ids:
1548-
stmt = stmt.filter(
1549-
text("""
1564+
stmt = stmt.filter(text("""
15501565
NOT EXISTS (
15511566
SELECT 1
15521567
FROM json_each(Chat.meta, '$.tags') AS tag
15531568
)
1554-
""")
1555-
)
1569+
"""))
15561570
elif tag_ids:
15571571
stmt = stmt.filter(
15581572
and_(
@@ -1595,14 +1609,12 @@ async def get_chats_by_user_id_and_search_text(
15951609
).params(title_key=f'%{search_text}%', content_key=search_text.lower())
15961610

15971611
if 'none' in tag_ids:
1598-
stmt = stmt.filter(
1599-
text("""
1612+
stmt = stmt.filter(text("""
16001613
NOT EXISTS (
16011614
SELECT 1
16021615
FROM json_array_elements_text(Chat.meta->'tags') AS tag
16031616
)
1604-
""")
1605-
)
1617+
"""))
16061618
elif tag_ids:
16071619
stmt = stmt.filter(
16081620
and_(

backend/open_webui/routers/notes.py

Lines changed: 112 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,6 @@ def _truncate_note_data(data: Optional[dict], max_length: int = 1000) -> Optiona
4747
return {'content': {'md': md[:max_length]}}
4848

4949

50-
def _note_chat_system_prompt(note_id: str) -> str:
51-
return (
52-
f'You are chatting with note {note_id}. Use view_note with this note id to read the current note. '
53-
'For edits, use replace_note_content for whole-note changes or replace_note_text '
54-
'for targeted exact text replacement.'
55-
)
56-
57-
5850
async def _normalize_note_chat_payload(chat: ChatResponse, note_id: str, db: AsyncSession) -> ChatResponse:
5951
payload = {**(chat.chat or {})}
6052
params = {**(payload.get('params') or {})}
@@ -63,7 +55,11 @@ async def _normalize_note_chat_payload(chat: ChatResponse, note_id: str, db: Asy
6355
if params.pop('note_id', None) is not None:
6456
changed = True
6557

66-
system = _note_chat_system_prompt(note_id)
58+
system = (
59+
f'You are chatting with note {note_id}. Use view_note with this note id to read the current note. '
60+
'For edits, use replace_note_content for whole-note changes or replace_note_text '
61+
'for targeted exact text replacement.'
62+
)
6763
if params.get('system') != system:
6864
params['system'] = system
6965
changed = True
@@ -375,7 +371,6 @@ async def get_note_chat_by_id(
375371
log.info('[note-chat] reusing hidden chat note_id=%s chat_id=%s user_id=%s', note.id, chat.id, user.id)
376372
return await _normalize_note_chat_payload(chat, note.id, db)
377373

378-
meta = {'internal': True, 'type': 'note', 'note_id': note.id}
379374
chat_id = str(uuid4())
380375
chat = await Chats.insert_new_chat(
381376
chat_id,
@@ -385,18 +380,123 @@ async def get_note_chat_by_id(
385380
'id': chat_id,
386381
'title': 'Chat',
387382
'models': [''],
388-
'params': {'system': _note_chat_system_prompt(note.id)},
383+
'params': {
384+
'system': (
385+
f'You are chatting with note {note.id}. Use view_note with this note id to read the current note. '
386+
'For edits, use replace_note_content for whole-note changes or replace_note_text '
387+
'for targeted exact text replacement.'
388+
)
389+
},
389390
'history': {'messages': {}, 'currentId': None},
390391
'messages': [],
391392
'tags': [],
392393
}
393394
),
394395
db=db,
395-
internal_meta=meta,
396+
internal_meta={'internal': True, 'type': 'note', 'note_id': note.id},
396397
)
397398
if not chat:
398399
log.error('[note-chat] failed creating hidden chat note_id=%s user_id=%s', note.id, user.id)
399400
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT())
401+
402+
log.info('[note-chat] created hidden chat note_id=%s chat_id=%s user_id=%s', note.id, chat.id, user.id)
403+
return chat
404+
405+
406+
@router.get('/{id}/chats', response_model=list[ChatResponse])
407+
async def get_note_chats_by_id(
408+
request: Request,
409+
id: str,
410+
user=Depends(get_verified_user),
411+
db: AsyncSession = Depends(get_async_session),
412+
):
413+
if user.role != 'admin' and not await has_permission(
414+
user.id, 'features.notes', await Config.get('user.permissions'), db=db
415+
):
416+
raise HTTPException(
417+
status_code=status.HTTP_401_UNAUTHORIZED,
418+
detail=ERROR_MESSAGES.UNAUTHORIZED,
419+
)
420+
421+
note = await Notes.get_note_by_id(id, db=db)
422+
if not note:
423+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
424+
425+
if user.role != 'admin' and (
426+
user.id != note.user_id
427+
and not await AccessGrants.has_access(
428+
user_id=user.id,
429+
resource_type='note',
430+
resource_id=note.id,
431+
permission='read',
432+
db=db,
433+
)
434+
):
435+
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
436+
437+
chats = await Chats.get_internal_chats_by_note_id(note.id, user.id, db=db)
438+
return [await _normalize_note_chat_payload(chat, note.id, db) for chat in chats]
439+
440+
441+
@router.post('/{id}/chat', response_model=ChatResponse)
442+
async def create_note_chat_by_id(
443+
request: Request,
444+
id: str,
445+
user=Depends(get_verified_user),
446+
db: AsyncSession = Depends(get_async_session),
447+
):
448+
if user.role != 'admin' and not await has_permission(
449+
user.id, 'features.notes', await Config.get('user.permissions'), db=db
450+
):
451+
raise HTTPException(
452+
status_code=status.HTTP_401_UNAUTHORIZED,
453+
detail=ERROR_MESSAGES.UNAUTHORIZED,
454+
)
455+
456+
note = await Notes.get_note_by_id(id, db=db)
457+
if not note:
458+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
459+
460+
if user.role != 'admin' and (
461+
user.id != note.user_id
462+
and not await AccessGrants.has_access(
463+
user_id=user.id,
464+
resource_type='note',
465+
resource_id=note.id,
466+
permission='read',
467+
db=db,
468+
)
469+
):
470+
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
471+
472+
chat_id = str(uuid4())
473+
chat = await Chats.insert_new_chat(
474+
chat_id,
475+
user.id,
476+
ChatForm(
477+
chat={
478+
'id': chat_id,
479+
'title': 'Chat',
480+
'models': [''],
481+
'params': {
482+
'system': (
483+
f'You are chatting with note {note.id}. Use view_note with this note id to read the current note. '
484+
'For edits, use replace_note_content for whole-note changes or replace_note_text '
485+
'for targeted exact text replacement.'
486+
)
487+
},
488+
'history': {'messages': {}, 'currentId': None},
489+
'messages': [],
490+
'tags': [],
491+
}
492+
),
493+
db=db,
494+
internal_meta={'internal': True, 'type': 'note', 'note_id': note.id},
495+
)
496+
if not chat:
497+
log.error('[note-chat] failed creating hidden chat note_id=%s user_id=%s', note.id, user.id)
498+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT())
499+
400500
log.info('[note-chat] created hidden chat note_id=%s chat_id=%s user_id=%s', note.id, chat.id, user.id)
401501
return chat
402502

src/app.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ textarea::-webkit-scrollbar-corner {
122122
}
123123

124124
.markdown-prose {
125-
@apply prose dark:prose-invert max-w-none break-words font-normal leading-relaxed prose-p:my-0 prose-p:font-normal prose-p:leading-relaxed prose-headings:my-1 prose-headings:font-normal prose-headings:leading-snug prose-strong:font-medium prose-code:before:content-none prose-code:after:content-none prose-ul:-my-0 prose-ol:-my-0 prose-li:-my-0 prose-li:font-normal prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-blockquote:font-normal prose-hr:my-4 prose-img:my-1 [&>:first-child]:mt-0 [&>:last-child]:mb-0;
125+
@apply prose prose-sm dark:prose-invert max-w-none break-words font-normal leading-relaxed prose-p:mt-0 prose-p:mb-2 prose-p:font-normal prose-p:leading-relaxed prose-headings:mt-2 prose-headings:mb-1 prose-headings:font-normal prose-headings:leading-snug prose-h1:text-xl prose-h2:text-lg prose-h3:text-base prose-strong:font-medium prose-code:before:content-none prose-code:after:content-none prose-ul:my-2 prose-ol:my-2 prose-li:my-0.5 prose-li:font-normal prose-pre:my-3 prose-table:my-0 prose-blockquote:my-3 prose-blockquote:font-normal prose-hr:my-4 prose-hr:border-gray-50 prose-hr:dark:border-gray-850/30 prose-img:my-2 [&>:first-child]:mt-0 [&>:last-child]:mb-0 [&_p:first-child]:mt-0 [&_h1:first-child]:mt-0 [&_h2:first-child]:mt-0 [&_h3:first-child]:mt-0 [&_h4:first-child]:mt-0 [&_h5:first-child]:mt-0 [&_h6:first-child]:mt-0;
126126
}
127127

128128
.markdown-prose-sm {

src/lib/apis/notes/index.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,62 @@ export const getNoteChatById = async (token: string, id: string) => {
254254
return res;
255255
};
256256

257+
export const getNoteChatsById = async (token: string, id: string) => {
258+
let error = null;
259+
const url = `${WEBUI_API_BASE_URL}/notes/${id}/chats`;
260+
261+
const res = await fetch(url, {
262+
method: 'GET',
263+
headers: {
264+
Accept: 'application/json',
265+
'Content-Type': 'application/json',
266+
authorization: `Bearer ${token}`
267+
}
268+
})
269+
.then(async (res) => {
270+
if (!res.ok) throw await res.json();
271+
return res.json();
272+
})
273+
.catch((err) => {
274+
error = err.detail;
275+
return null;
276+
});
277+
278+
if (error) {
279+
throw error;
280+
}
281+
282+
return res;
283+
};
284+
285+
export const createNoteChatById = async (token: string, id: string) => {
286+
let error = null;
287+
const url = `${WEBUI_API_BASE_URL}/notes/${id}/chat`;
288+
289+
const res = await fetch(url, {
290+
method: 'POST',
291+
headers: {
292+
Accept: 'application/json',
293+
'Content-Type': 'application/json',
294+
authorization: `Bearer ${token}`
295+
}
296+
})
297+
.then(async (res) => {
298+
if (!res.ok) throw await res.json();
299+
return res.json();
300+
})
301+
.catch((err) => {
302+
error = err.detail;
303+
return null;
304+
});
305+
306+
if (error) {
307+
throw error;
308+
}
309+
310+
return res;
311+
};
312+
257313
export const updateNoteById = async (token: string, id: string, note: NoteItem) => {
258314
let error = null;
259315

src/lib/components/channel/Messages/Message.svelte

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -525,16 +525,18 @@
525525
</div>
526526
</div>
527527
{:else}
528-
<div class=" min-w-full markdown-prose {pending ? 'opacity-50' : ''}">
528+
<div class="min-w-full {pending ? 'opacity-50' : ''}">
529529
{#if (message?.content ?? '').trim() === '' && message?.meta?.model_id}
530530
<Skeleton />
531531
{:else}
532-
<Markdown
533-
id={renderedMessageId}
534-
content={message.content}
535-
paragraphTag="span"
536-
allowEmbeds={!!message?.meta?.model_id}
537-
/>{#if message.created_at !== message.updated_at && (message?.meta?.model_id ?? null) === null}<span
532+
<span class="markdown-prose">
533+
<Markdown
534+
id={renderedMessageId}
535+
content={message.content}
536+
paragraphTag="span"
537+
allowEmbeds={!!message?.meta?.model_id}
538+
/>
539+
</span>{#if message.created_at !== message.updated_at && (message?.meta?.model_id ?? null) === null}<span
538540
class="text-gray-500 text-[10px] pl-1 self-center">({$i18n.t('edited')})</span
539541
>{/if}
540542
{/if}

0 commit comments

Comments
 (0)