From 556b73ecb5ba3534f99af59fbfc2f24037ac2b80 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sat, 7 Feb 2026 02:35:09 +0000 Subject: [PATCH] Regenerate summaries at will --- ai/__pycache__/local_ai_api.cpython-311.pyc | Bin 19874 -> 18543 bytes ai/local_ai_api.py | 64 +--------- core/__pycache__/tasks.cpython-311.pyc | Bin 4842 -> 8991 bytes core/__pycache__/urls.cpython-311.pyc | Bin 2226 -> 2769 bytes core/__pycache__/views.cpython-311.pyc | Bin 9189 -> 12069 bytes core/tasks.py | 120 +++++++++++++++---- core/templates/core/bookmark_detail.html | 123 +++++++++++++++----- core/urls.py | 8 +- core/views.py | 49 ++++++-- 9 files changed, 242 insertions(+), 122 deletions(-) diff --git a/ai/__pycache__/local_ai_api.cpython-311.pyc b/ai/__pycache__/local_ai_api.cpython-311.pyc index ae12bda473e74a8f0f7bfeb28e8b8b8abcaaf6d9..bd2f0d5e65ea53a5308353703cf741a3f4e0e36d 100644 GIT binary patch delta 2473 zcmZ`*du&@*89(P<``Xv>D~S`wu@gHfi{mA6(lm9`G>@fCns!aoh9qipH!ZJo)4A~@ zJ$6Z&I**M~x@kxopAec>Mj29pwG|Wt(vScZ4On4_@eov&S_u$HgFouOD^U5)O*$G; zj_u#~`@ZwI=lst3&h8rOcw2F>5zIRU$)#t*aLZvt5-4xrV>ZqLs5&*2Y{H%$f(;PF9Z0iL9MfBC9Z% z7g;6S#i}ouG%xF5ezaBS2hjT1ZdQY~n#EWUt)J~-b!Y>ulQpBQVS8B=Z7q{bV{&%*Y%5)@u+xaTBH{m2RoD#A@?&PQ3 zZ&ImBhDBd6?<{Z)jCz4(4Po=ERuW_h@w_XJ)0WEN+60az8DH%jHSx z0h=c%dsqd`43LpepCe*>cvLdR0N0@V8lT=Drc=s`x!PD+kse z*d{VMZ|Og!r?L;yN7F0uL}I3OceE|kcp{!+>7`6)WIVL9E!ws(gn?MczR+^4BNFOg zTukUE^w}fvZ0qh_d!oByp~fSJ#*Yp+hZ6AxJv5}xEu516;*Rk*%&I?;f<=hbSw%gQO$443Fa>`Igq0aV zXMxs~yXORD0(1fM@Tr=o?8`_q)0=CDiJI@LpDZ0NRlSmj+VgmX;CTHDGzs6Xk0JiO z{!jKdMX%dir|I?%mt^K*{ore8vj0rfxe+$VT{zw_X(3}oC0QB%e828* z!-WY7m%?HAu*qfRB0!iKejdKH8#^qKWqFb;QHDICj196=A(umCWqbVpnljZS6{t}$)Y_y#g zjRg4NTIVZHv4I68sb^nIGkz++f3E|tIym0dLM2%1I!a~uuxrV0V&?10_WNknj)LL` zUf6e|?uqr2Si9<2bEI0ZRdcsbmY4+&b=TBcFUxb%quQ~pR!j81n*tZRM+eEObCRS? zpG4JM$(*z-EuiqY0)^QY1R>m!k|;^pSV^uV=X^=ZIa4%eXZFi7j_iOy&mgAAY)=GJ z-e!ABG}wzU&eL$L zH;Siqt@rli^CG+^U|m4JfM)~<^&@UBBsRw7rvx(+OAK!$U_s{knXQZwu^R{l6W23~ zJPB9&s_19oy}lOjl&BN11DBtUkyybZ|p8p#>7mcg6jJfuS|_~+oL&)C}S8!p~n zV+0oWRpKiUk_Ua^#}X-GAK`#c;jbgCNYUtuDXOPFQ=lBRyMM5myG7RnhJW zR1WPe**wg`*FYO;>$rflATQ|W_&lSDvkC`~~`vV8J#uHGejHmg`efTqGekcGj5g z8R7l_{_<=~$xj3o{}=d8s6NtvOeFDUni~0N|MtOhiP#?eVW!3$rIotK5a*lYzM_h-9@q;VinX`B--ap^hT?KVXRWABKxez5oCK delta 3861 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 AO#lD@ diff --git a/ai/local_ai_api.py b/ai/local_ai_api.py index bcff732..99252e5 100644 --- a/ai/local_ai_api.py +++ b/ai/local_ai_api.py @@ -1,37 +1,3 @@ -""" -LocalAIApi — lightweight Python client for the Flatlogic AI proxy. - -Usage (inside the Django workspace): - - from ai.local_ai_api import LocalAIApi - - response = LocalAIApi.create_response({ - "input": [ - {"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": "Summarise this text in two sentences."}, - ], - "text": {"format": {"type": "json_object"}}, - }) - - if response.get("success"): - data = LocalAIApi.decode_json_from_response(response) - # ... - -# Typical successful payload (truncated): -# { -# "id": "resp_xxx", -# "status": "completed", -# "output": [ -# {"type": "reasoning", "summary": []}, -# {"type": "message", "content": [{"type": "output_text", "text": "Your final answer here."}]} -# ], -# "usage": { "input_tokens": 123, "output_tokens": 456 } -# } - -The helper automatically injects the project UUID header and falls back to -reading executor/.env if environment variables are missing. -""" - from __future__ import annotations import json @@ -52,10 +18,8 @@ __all__ = [ "decode_json_from_response", ] - _CONFIG_CACHE: Optional[Dict[str, Any]] = None - class LocalAIApi: """Static helpers mirroring the PHP implementation.""" @@ -76,9 +40,7 @@ class LocalAIApi: def decode_json_from_response(response: Dict[str, Any]) -> Optional[Dict[str, Any]]: return decode_json_from_response(response) - def create_response(params: Dict[str, Any], options: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: - """Signature compatible with the OpenAI Responses API.""" options = options or {} payload = dict(params) @@ -111,9 +73,7 @@ def create_response(params: Dict[str, Any], options: Optional[Dict[str, Any]] = return initial - def request(path: Optional[str], payload: Dict[str, Any], options: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: - """Perform a raw request to the AI proxy.""" cfg = _config() options = options or {} @@ -145,6 +105,7 @@ def request(path: Optional[str], payload: Dict[str, Any], options: Optional[Dict "Content-Type": "application/json", "Accept": "application/json", cfg["project_header"]: project_uuid, + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", } extra_headers = options.get("headers") if isinstance(extra_headers, Iterable): @@ -156,9 +117,7 @@ def request(path: Optional[str], payload: Dict[str, Any], options: Optional[Dict body = json.dumps(payload, ensure_ascii=False).encode("utf-8") return _http_request(url, "POST", body, headers, timeout, verify_tls) - def fetch_status(ai_request_id: Any, options: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: - """Fetch status for a queued AI request.""" cfg = _config() options = options or {} @@ -180,6 +139,7 @@ def fetch_status(ai_request_id: Any, options: Optional[Dict[str, Any]] = None) - headers: Dict[str, str] = { "Accept": "application/json", cfg["project_header"]: project_uuid, + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", } extra_headers = options.get("headers") if isinstance(extra_headers, Iterable): @@ -190,9 +150,7 @@ def fetch_status(ai_request_id: Any, options: Optional[Dict[str, Any]] = None) - return _http_request(url, "GET", None, headers, timeout, verify_tls) - def await_response(ai_request_id: Any, options: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: - """Poll status endpoint until the request is complete or timed out.""" options = options or {} timeout = int(options.get("timeout", 300)) interval = int(options.get("interval", 5)) @@ -236,14 +194,10 @@ def await_response(ai_request_id: Any, options: Optional[Dict[str, Any]] = None) } time.sleep(interval) - def extract_text(response: Dict[str, Any]) -> str: - """Public helper to extract plain text from a Responses payload.""" return _extract_text(response) - def decode_json_from_response(response: Dict[str, Any]) -> Optional[Dict[str, Any]]: - """Attempt to decode JSON emitted by the model (handles markdown fences).""" text = _extract_text(response) if text == "": return None @@ -270,7 +224,6 @@ def decode_json_from_response(response: Dict[str, Any]) -> Optional[Dict[str, An return None return None - def _extract_text(response: Dict[str, Any]) -> str: payload = response.get("data") if response.get("success") else response.get("response") if isinstance(payload, dict): @@ -294,9 +247,8 @@ def _extract_text(response: Dict[str, Any]) -> str: return payload return "" - def _config() -> Dict[str, Any]: - global _CONFIG_CACHE # noqa: PLW0603 + global _CONFIG_CACHE if _CONFIG_CACHE is not None: return _CONFIG_CACHE @@ -320,7 +272,6 @@ def _config() -> Dict[str, Any]: } return _CONFIG_CACHE - def _build_url(path: str, base_url: str) -> str: trimmed = path.strip() if trimmed.startswith("http://") or trimmed.startswith("https://"): @@ -329,7 +280,6 @@ def _build_url(path: str, base_url: str) -> str: return f"{base_url}{trimmed}" return f"{base_url}/{trimmed}" - def _resolve_status_path(ai_request_id: Any, cfg: Dict[str, Any]) -> str: base_path = (cfg.get("responses_path") or "").rstrip("/") if not base_path: @@ -338,12 +288,8 @@ def _resolve_status_path(ai_request_id: Any, cfg: Dict[str, Any]) -> str: base_path = f"{base_path}/ai-request" return f"{base_path}/{ai_request_id}/status" - def _http_request(url: str, method: str, body: Optional[bytes], headers: Dict[str, str], timeout: int, verify_tls: bool) -> Dict[str, Any]: - """ - Shared HTTP helper for GET/POST requests. - """ req = urlrequest.Request(url, data=body, method=method.upper()) for name, value in headers.items(): req.add_header(name, value) @@ -361,7 +307,7 @@ def _http_request(url: str, method: str, body: Optional[bytes], headers: Dict[st except urlerror.HTTPError as exc: status = exc.getcode() response_body = exc.read().decode("utf-8", errors="replace") - except Exception as exc: # pylint: disable=broad-except + except Exception as exc: return { "success": False, "error": "request_failed", @@ -395,9 +341,7 @@ def _http_request(url: str, method: str, body: Optional[bytes], headers: Dict[st "response": decoded if decoded is not None else response_body, } - def _ensure_env_loaded() -> None: - """Populate os.environ from executor/.env if variables are missing.""" if os.getenv("PROJECT_UUID") and os.getenv("PROJECT_ID"): return diff --git a/core/__pycache__/tasks.cpython-311.pyc b/core/__pycache__/tasks.cpython-311.pyc index 7074f2030d6f55a46b92e7947e5b18e9b141e7b4..3eba445bfce33dd883b28761db2bcf5c20e26ac7 100644 GIT binary patch literal 8991 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 delta 386 zcmca8x=B!DIWI340}#BDZp##9Wng#=;=lkul=1lv(?pFZDMkh+hE$eiK*7~86%0{A zsX_~cC$7+C22nT68AT=wFdBnN@yXGQGEi1OoOKw?dcbJNESLpSD?VA9Ns5^t%8m!J z1)%K7KsE=Ey@AP)l_^yyOLTG*vnnT7lvs*fFoUN2j2x3WSY}Or%yMUPJlh1I zSs&QiC%3WZaVY~G$Oy#6E|dSU2i3DMGJRm6Ey2UUCEg+1Dc>XCz;=Uyt)0J#e*({p g{2hD^{1+#`<+waqlWQiwBriWx12+g3DFR&w0F%U3FaQ7m diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 61d1f79bd4db2b49e5f767ad366e30525e4a640f..9c74dc96743fd821c0311c5b1c0c33b27899c018 100644 GIT binary patch delta 4836 zcmd5Q-Y%73>-dBt*_G^`&@3 zGH&8dTs!o{mo}L+(;2&Vod-|dhfM1Szx6Q^qZ!D~c-j|tCewBXPuw=+r~dz0iHi`a zAA4ARoIU@4_WXO!_h0t$ftzpHe_LH$#X)O6@m_MkH*5Ft|Cnt*)}OA4)$p9iiKbL- z+8J}QzB%PeyJPNjU91l7EuuB$Nqb}7v@hmM`(yreAQnj1$Lg7^D%Fr~j5W^wv561P zs*QZp?6-|H!<(|T7dX)=RZF&829CQ6Kdr@@b=(0?bb-_jQfrws%%pW7^?=k^CT(F- zFGzhAq^(Tq2Wdd6roZBAvu$jo9!45K-(2>z4NTey(qIKk+L^Qoq@fCHb%@PJxk&hH ze5r_DH4$k-l!$5}l9(jYh@#exN(#;K$w8ee5b4Jfk)?Plaqg^YF5X3Wl|LK->7bmM zI3mfDnF(1^-TjI(S-Lw?eAw4Ku41I3uFI;Lk4~l233B%S72SZIchRqm!9WwpRZE6| z3Gv~xs{X*fH&i_sKpeEz)Xulv-){Q5uInYxBJBtr2oVIFNumhb091VJ3t1T%)KdN*xm!#JzDIBP^orQ-2B zJn4f`8DFDMn;d{H3D7UAlBO+M_lnbh|ClYKhI%)U#x9LZbie5ZxXvkf5XC0)qHdZSTN)YDl_W|2 zbNwy2ySp?3%NT~9IfjetL`6wSBvYzxay-J5-7qhrCwBNph7nQ#aPqkKumhvO9d`7p z1Be-yr$$C3VAT{!oeQruyr)bDcUwlT&Tx+$o}9PiUh5Zz2et?H+@=FLM}OYYUvTv2 zEd68){b$YTO+j4Rs2xuw!lyNxN+yxb^m=V8zn?y+?L3NGJruF7d(`A4D3E;!69|m- z+p)(EiMd7+ZxL`)W2;dM_P(;63JMSg=(nyu%@T#?8^9CYXL&wI^L2sOm*WJ62(k;o zg|NJ{4!n&5M0D)D)v75OLXjdR(z1;732A2!8@7d)w3}Y4BgQ&=Y!y$h0tV{wS=KoNoN$27*Ud$2{I0ky~C@91R0eh z0>RL(9V$yD=2Hndp(upRz_@A>rIe&d+yO*C%HHvN9%q zOd6W+8zwWda<0De0rj8|qq2M*06g8*dh-YO!e4BC(3^Ah=Ux2;SN}!JW1I8CZ8v_H zx3w2+?ch$<4GV(1AT-PiEptLkUT7-_Z8@Rs5mU<^&)Yf+wocTGFukUEVZ)rTAun_k zgpQoh@z~-kSb{gseDagLrK@1+%2~P|Ia+QW$aU{}c;w;UTb*x%_XA zzSOu=ub<}WuLB|aJAJLyFl`_$3RHgdp}r%F%$_!^4eY=MjthFMBV$AqQL(@pM)ZxAeDgC zm*6cJk%~OZu1_Qkp<05vjJ@SBRhc^!lqzz^Uum}(d!`_|aq@~p_AQ_#`n3hfYrY_VfsXsy)=S`Se8OIY z?@r)XQ3WAOzw#9m_*E!z|5A_PpWHxLyL??BWMe$|CCazl?+cjuLl|}=X0ii^b|PQ| zTT!2|dbAUjidhQlGV(G)A3!Nd&6GZDuGP2lSLmPWPq1nb0@f?2x(DG^gmqt$Enczk z{}|^Xyidm)ex#KvAK%Y6e85{-=!D8CMaP4IEXGap3Bu0-_P?Q;#h)p^D7=4M+&WRT>U;3ygJmI~0k~Sm;`oRSjy5IF^_g&Gd-F zJ?RY888X3gNwpP+Pr=tPdkN;js;Q{Mj+Y$*^K-Tbm^xIWl90z`tw>>?nCyECV<#(f zS-r^eG1-NYk$DE=AUhgnZdn9=X6c1Rr&7{x@&}lKWtzE-6BY&6GJxE8ozZuPam z?Z{QS7P$Sn=eriTmfY%V(Ga4O;b_dk!$;&=$oac&$DW|z>9RCzY@J$$FfyCRw$J**SPVX`w0_1U8&-;vPqRO=j*(^_X#X>kjKSX{e$RP(O(`9VR;1N STz&MmQfU@F-14CTsr6rImOya; delta 2513 zcma)7TW=Fb6yEXu65FvI$Id-UOx#V3WUT9wB>ph{IL@q|#-b7oTl4iR1Lewsa(neUuA zbLRT7KPJQ9he81Xo-5}rmP7IRaEg39pE)!}1VvChTB{x{METgO#q@X~4r8C<*V^<% zA)&Vy+Vx~1siz7lE)QrOdS{_?{!ZVKE{8BFD4|P&5>`X1?}kedeuU3{3f){A0c|U2 z15MfVB>AF>F3%cXj7o|vph+#Yb50zP`aiCv74nF zpNtTzg{r4iYN(PXpPdtlMfL-<>@jN9cU7Zas~YM<0)#~ud*~Qh=yC3Lk|-;AdON#8 zYWZqZQK>XDXE`T#9I%`v)udr|&66Pm3%_{&bR6FRRy2*U4q*rZJ?SvQMgYqxYnte$ zBRKX{PRq1pSl(i-YN}^UivE^gl8Tya7?NbT;lCfA3p58Kx|ziSKao7U9T)~$OM$Uh zN4XfiLd#x7RnsKtCZStFYG5$qwsR6qlk8})?8)=t!oA?9j=tAn3^{NcCN0-RhvmrS zEn*rD5cl`f95ccbWQ_eB7W>faG0BSImAEWlxoVhlwW!if?4NLZN(39rWy%ngQ&tq1 zwyu>;nqb*T$0(24HCv;)<)h2#rbocT=GqkOVP-}$C@&}>*ip0;ZGoqJbK8y+;e1%VgFN;bnN z!?P$l>=PJh7;vCXRo0ECC66|v?0Pay4zgd9_1!P!&a?U!cSC4OXdPt;``N|Rn9aS8 z+}W)}hKU1%WFy-6q+C$VwxD9fOcizlAGfr2pv z-ltqJdbZDHhSH?U-X1zSjoDva*K0<}7k}XaKZlDUKm`vSxA);3?89pdS2Ksmd2D_4 z3h&lX93O$5<<(Vv25t+>Z=8^+szBoxo%92o=R?%{6m?c^Xr{6AfaS4GL_ftgW{1x^ zKWEy8GAu(Y1b2p1qf(Ko&^Burj5x^30-cz7Sj)d;*1D z1`opz0O_+I-W)7fy=ItayZ>uG(-^^lTB)S*4wOS}16{8GTo4|x-$l03u5?%PDg7Kw znydOE$`DM(hO;f`$$zKBL3Vfe>UpbGIVo35wVX1O(`yi=LGkce!DaCY)2!17N<7PI zUNJmdahwcOX1Pt-s2DVX0)Bt-F3npMZwYv;coFlB}z<_V

Failed to retrieve content from {bookmark.url} and its base domain.

" + status_code = status_code or 0 soup = BeautifulSoup(html_content, 'html.parser') @@ -46,8 +84,9 @@ def process_bookmark(self, bookmark_id): 'content_html': html_content, 'content_text': text_content, 'metadata': { - 'status_code': response.status_code, - 'content_type': response.headers.get('content-type'), + 'status_code': status_code, + 'content_type': content_type, + 'used_backup': used_backup, } } ) @@ -62,30 +101,69 @@ def generate_summary(bookmark_id): try: bookmark = Bookmark.objects.get(id=bookmark_id) extraction = bookmark.extraction - except (Bookmark.DoesNotExist, Extraction.DoesNotExist): + except Bookmark.DoesNotExist: + return + except Extraction.DoesNotExist: + # If extraction doesn't exist yet, we might want to wait or just return + # But in EAGER mode it should be there. return - if not extraction.content_text: + content_to_summarize = extraction.content_text.strip() + used_backup = extraction.metadata.get('used_backup', False) + + if not content_to_summarize or len(content_to_summarize) < 50: + Summary.objects.update_or_create( + bookmark=bookmark, + defaults={'content': f"Insufficient content extracted from {bookmark.url} to generate a meaningful AI summary."} + ) return # Prepare prompt for AI - prompt = f"Summarize the following content from the webpage '{bookmark.title or bookmark.url}' in 2-3 concise sentences. Focus on the main points for a researcher.\n\nContent:\n{extraction.content_text[:4000]}" + if used_backup: + prompt = f"The specific page '{bookmark.url}' could not be reached. Summarize the main domain front page content instead to describe what this website is about.\n\nContent:\n{content_to_summarize[:4000]}" + else: + prompt = f"Summarize the following content from the webpage '{bookmark.title or bookmark.url}' in 2-3 concise sentences. Focus on the main points for a researcher.\n\nContent:\n{content_to_summarize[:4000]}" - response = LocalAIApi.create_response({ - "input": [ - {"role": "system", "content": "You are a helpful assistant that summarizes web content for researchers and knowledge workers. Be concise and professional."}, - {"role": "user", "content": prompt}, - ], - }) + try: + response = LocalAIApi.create_response({ + "input": [ + {"role": "system", "content": "You are a helpful assistant that summarizes web content for researchers and knowledge workers. Be concise and professional."}, + {"role": "user", "content": prompt}, + ], + }) - if response.get("success"): - summary_text = LocalAIApi.extract_text(response) - if summary_text: + summary_text = None + if response.get("success"): + summary_text = LocalAIApi.extract_text(response) + + if summary_text and len(summary_text.strip()) > 10: Summary.objects.update_or_create( bookmark=bookmark, - defaults={'content': summary_text} + defaults={'content': summary_text.strip()} ) return f"Generated summary for bookmark {bookmark_id}" - - logger.error(f"Failed to generate summary for bookmark {bookmark_id}: {response.get('error')}") - return f"Failed to generate summary for bookmark {bookmark_id}" \ No newline at end of file + else: + error_msg = response.get('error') or "Empty response from AI" + logger.error(f"Failed to generate summary for bookmark {bookmark_id}: {error_msg}") + + # Create a fallback summary to stop the spinner + fallback_content = "AI summary could not be generated at this time. " + if used_backup: + fallback_content += "The original page was unreachable, and the home page content was insufficient for a summary." + elif bookmark.title: + fallback_content += f"The page appears to be titled '{bookmark.title}'." + else: + fallback_content += f"Please visit the link directly: {bookmark.url}" + + Summary.objects.update_or_create( + bookmark=bookmark, + defaults={'content': fallback_content} + ) + return f"Failed to generate summary for bookmark {bookmark_id}, created fallback." + except Exception as e: + logger.exception(f"Unexpected error in generate_summary for bookmark {bookmark_id}: {e}") + Summary.objects.update_or_create( + bookmark=bookmark, + defaults={'content': "An unexpected error occurred while generating the AI summary."} + ) + return f"Error in generate_summary for bookmark {bookmark_id}" \ No newline at end of file diff --git a/core/templates/core/bookmark_detail.html b/core/templates/core/bookmark_detail.html index bd2c8cb..e0a8904 100644 --- a/core/templates/core/bookmark_detail.html +++ b/core/templates/core/bookmark_detail.html @@ -16,20 +16,28 @@

{{ bookmark.title|default:bookmark.url }}

{% if bookmark.user == request.user %} - @@ -47,19 +55,37 @@
{% endif %} - {% if bookmark.summary %} -
-
AI Summary
-
+
+
+
AI Summary
+ {% if bookmark.user == request.user and bookmark.summary %} + + {% endif %} +
+ + {% if bookmark.summary %} +
{{ bookmark.summary.content }}
-
- {% else %} -
-
- AI Summary is being generated... -
- {% endif %} + {% if bookmark.user == request.user %} +
+
+ {% csrf_token %} + +
+ + +
+
+
+ {% endif %} + {% else %} +
+
+ AI Summary is being generated... +
+ {% endif %} +
{% for tag in bookmark.tags.all %} @@ -68,14 +94,37 @@
- {% if bookmark.extraction %} -
-
Extracted Text Content
-
+
+
+
Extracted Text Content
+ {% if bookmark.user == request.user and bookmark.extraction %} + + {% endif %} +
+ + {% if bookmark.extraction %} +
{{ bookmark.extraction.content_text|linebreaks }}
-
- {% endif %} + {% if bookmark.user == request.user %} +
+
+ {% csrf_token %} + +
+ + +
+
+
+ {% endif %} + {% else %} +
+
+ Content is being extracted... +
+ {% endif %} +
@@ -121,6 +170,18 @@ {% block extra_js %}