mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-29 19:29:43 +02:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abc44258d8 | ||
|
|
0ab9770056 | ||
|
|
8ca5e56f52 | ||
|
|
0733ec65ad |
@@ -38,6 +38,7 @@ jobs:
|
||||
prebuilt
|
||||
scheduler-kafka
|
||||
sdk-py
|
||||
docs
|
||||
requireScope: false
|
||||
ignoreLabels: |
|
||||
ignore-lint-pr-title
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
"""
|
||||
Copy page functionality hooks for MkDocs.
|
||||
|
||||
This module provides hooks to inject original markdown content into HTML pages
|
||||
for the copy page functionality, allowing users to copy clean markdown content
|
||||
optimized for LLMs.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from mkdocs.config.defaults import MkDocsConfig
|
||||
from mkdocs.structure.pages import Page
|
||||
|
||||
|
||||
def _process_includes(content: str, docs_dir: Path) -> str:
|
||||
"""Process MkDocs includes like {!../README.md!}."""
|
||||
include_pattern = r'\{!([^!]+)!\}'
|
||||
|
||||
def replace_include(match):
|
||||
include_path = match.group(1)
|
||||
# Resolve relative path
|
||||
if include_path.startswith('../'):
|
||||
# Go up from docs dir
|
||||
include_file = docs_dir.parent / include_path[3:]
|
||||
else:
|
||||
include_file = docs_dir / include_path
|
||||
|
||||
try:
|
||||
with open(include_file, 'r', encoding='utf-8') as f:
|
||||
included_content = f.read()
|
||||
# Remove frontmatter from included content to avoid duplication
|
||||
included_content = re.sub(r'^---\n.*?\n---\n', '', included_content, flags=re.DOTALL)
|
||||
return included_content
|
||||
except:
|
||||
return f"[Content from {include_path}]"
|
||||
|
||||
return re.sub(include_pattern, replace_include, content)
|
||||
|
||||
|
||||
def _clean_markdown(content: str) -> str:
|
||||
"""Clean up markdown content by removing MkDocs artifacts."""
|
||||
# Remove frontmatter
|
||||
content = re.sub(r'^---\n.*?\n---\n', '', content, flags=re.DOTALL)
|
||||
|
||||
# Remove script tags (handle malformed closing tags)
|
||||
content = re.sub(r'<script[^>]*>.*?</script[^>]*>', '', content, flags=re.DOTALL | re.IGNORECASE)
|
||||
|
||||
# Remove style tags (handle spaces in closing tags)
|
||||
content = re.sub(r'<style[^>]*>.*?</style\s*>', '', content, flags=re.DOTALL | re.IGNORECASE)
|
||||
|
||||
# Remove HTML comments
|
||||
content = re.sub(r'<!--.*?-->', '', content, flags=re.DOTALL)
|
||||
|
||||
# Remove all HTML tags (more aggressive cleaning)
|
||||
content = re.sub(r'<[^>]+>', '', content)
|
||||
|
||||
# Remove markdown image references that might be logos
|
||||
content = re.sub(r'!\[[^\]]*\]\([^)]*logo[^)]*\)', '', content, flags=re.IGNORECASE)
|
||||
|
||||
# Clean up line by line
|
||||
lines = content.split('\n')
|
||||
cleaned_lines = []
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
# Skip completely empty lines for now
|
||||
if line:
|
||||
cleaned_lines.append(line)
|
||||
|
||||
# Join lines and then clean up spacing
|
||||
content = '\n'.join(cleaned_lines)
|
||||
|
||||
# Add proper paragraph breaks by looking for markdown patterns
|
||||
# Add double newline before headers
|
||||
content = re.sub(r'\n(#{1,6}\s)', r'\n\n\1', content)
|
||||
|
||||
# Add double newline before list items
|
||||
content = re.sub(r'\n(\*\s|-\s|\d+\.\s)', r'\n\n\1', content)
|
||||
|
||||
# Add double newline before code blocks
|
||||
content = re.sub(r'\n(```)', r'\n\n\1', content)
|
||||
|
||||
# Clean up any triple+ newlines
|
||||
content = re.sub(r'\n{3,}', '\n\n', content)
|
||||
|
||||
content = content.strip()
|
||||
|
||||
return content
|
||||
|
||||
|
||||
def inject_markdown_content(html: str, page: Page, config: MkDocsConfig) -> str:
|
||||
"""
|
||||
Inject the original markdown content into the HTML for copy page functionality.
|
||||
|
||||
Args:
|
||||
html: The HTML content to inject into
|
||||
page: The MkDocs page object
|
||||
config: The MkDocs configuration
|
||||
|
||||
Returns:
|
||||
Modified HTML with markdown content injected as JSON
|
||||
"""
|
||||
if not hasattr(page, 'file') or not page.file:
|
||||
return html
|
||||
|
||||
# Get the original markdown file path
|
||||
docs_dir = Path(config.get('docs_dir', 'docs'))
|
||||
src_path = page.file.src_path
|
||||
|
||||
# Handle different file types
|
||||
if src_path.endswith('.ipynb'):
|
||||
# For notebook files, we might want to use the converted markdown
|
||||
# For now, just return the HTML as-is
|
||||
return html
|
||||
|
||||
markdown_file = docs_dir / src_path
|
||||
|
||||
if not markdown_file.exists():
|
||||
return html
|
||||
|
||||
try:
|
||||
# Read the original markdown content
|
||||
with open(markdown_file, 'r', encoding='utf-8') as f:
|
||||
markdown_content = f.read()
|
||||
|
||||
# Special handling for index page - use relative path to the actual README.md
|
||||
if src_path == 'index.md':
|
||||
# Relative path to the repository README.md file (go up two levels from docs/docs)
|
||||
readme_path = docs_dir.parent.parent / 'README.md'
|
||||
|
||||
try:
|
||||
with open(readme_path, 'r', encoding='utf-8') as f:
|
||||
readme_content = f.read()
|
||||
# Remove frontmatter if present
|
||||
processed_markdown = re.sub(r'^---\n.*?\n---\n', '', readme_content, flags=re.DOTALL)
|
||||
processed_markdown = processed_markdown.strip()
|
||||
except Exception as e:
|
||||
# If we can't read the README, fallback to original behavior
|
||||
processed_markdown = _process_includes(markdown_content, docs_dir)
|
||||
processed_markdown = re.sub(r'^---\n.*?\n---\n', '', processed_markdown, flags=re.DOTALL)
|
||||
processed_markdown = processed_markdown.strip()
|
||||
else:
|
||||
# Process any includes in the markdown to get the full content
|
||||
processed_markdown = _process_includes(markdown_content, docs_dir)
|
||||
# Clean up the processed markdown normally for other pages
|
||||
processed_markdown = _clean_markdown(processed_markdown)
|
||||
|
||||
# Create the JSON data
|
||||
markdown_data = {
|
||||
'markdown': processed_markdown,
|
||||
'title': page.title or 'Page Content',
|
||||
'url': page.url or ''
|
||||
}
|
||||
|
||||
# Properly escape the JSON for HTML
|
||||
json_content = json.dumps(markdown_data, ensure_ascii=False)
|
||||
json_content = json_content.replace('</', '\\u003c/')
|
||||
json_content = json_content.replace('<script', '\\u003cscript')
|
||||
json_content = json_content.replace('</script', '\\u003c/script')
|
||||
|
||||
script_content = f'<script id="page-markdown-content" type="application/json">{json_content}</script>'
|
||||
|
||||
# Insert before </head> if it exists, otherwise before </body>
|
||||
if '</head>' in html:
|
||||
html = html.replace('</head>', f'{script_content}</head>')
|
||||
elif '</body>' in html:
|
||||
html = html.replace('</body>', f'{script_content}</body>')
|
||||
|
||||
except Exception as e:
|
||||
# If anything goes wrong, just return the original HTML
|
||||
# Could log the error here if needed
|
||||
pass
|
||||
|
||||
return html
|
||||
|
||||
|
||||
def on_post_page(output: str, page: Page, config: MkDocsConfig) -> str:
|
||||
"""
|
||||
MkDocs hook to inject markdown content into HTML pages.
|
||||
|
||||
This hook is called after each page is rendered and injects the original
|
||||
markdown content as JSON for the copy page functionality.
|
||||
|
||||
Args:
|
||||
output: The HTML output of the page
|
||||
page: The MkDocs page object
|
||||
config: The MkDocs configuration
|
||||
|
||||
Returns:
|
||||
Modified HTML with markdown content injected
|
||||
"""
|
||||
return inject_markdown_content(output, page, config)
|
||||
@@ -0,0 +1,142 @@
|
||||
# LangGraph Server Changelog
|
||||
|
||||
[LangGraph Server](../../concepts/langgraph_server.md) is an API platform for creating and managing agent-based applications. It provides built-in persistence, a task queue, and supports deploying, configuring, and running assistants (agentic workflows) at scale. This changelog documents all notable updates, features, and fixes to LangGraph Server releases.
|
||||
|
||||
---
|
||||
|
||||
## v0.2.83 (2025-07-09)
|
||||
- Reduced the `default` Time-To-Live (TTL) for `resumable` `streams` to 2 `minutes`.
|
||||
- Updated `data` `submission` `logic` to `handle` `various` `deployment` `modes`, `ensuring` `correct` `endpoints` (`beacon` or `LangSmith`) are `used` `based` on `license` `type` and `configuration`.
|
||||
- Added `functionality` to `submit` `self`-`hosted` `data` to a Langsmith `instance` when the `endpoint` is `configured`.
|
||||
|
||||
## v0.2.82 (2025-07-03)
|
||||
- Resolved a `race` `condition` in `background` `jobs` by `using` a `join` to `lock` `Runs.`next``, `ensuring` `consistent` `execution` `without` `affecting` `performance`.
|
||||
|
||||
## v0.2.81 (2025-07-03)
|
||||
- Added a `placeholder` `/`ok`` `endpoint` for `deployments` with ``disable_meta`=True` to `ensure` `they` are `marked` as `successful`.
|
||||
- Adjusted the `initial` `wait` `time` for `run` `streams` to be `shorter`, `improving` `responsiveness` for `runs` that are more than 60 `seconds` `old` or `non`-`existent`.
|
||||
|
||||
## v0.2.80 (2025-07-03)
|
||||
- Fixed an `issue` where the ``logger`.`ainfo`()` `method` `incorrectly` `passed` `multiple` `values` to the ``event`` `argument`.
|
||||
|
||||
## v0.2.79 (2025-07-02)
|
||||
- Resolved an `issue` `causing` `JsonDecodeError` `during` `checkpointing` with `remote` `graphs`, `ensuring` `proper` JSON `handling` when `falling` `back` to `sanitization`.
|
||||
- Introduced the ``disable_webhooks`` `configuration` `flag` to `prevent` `webhooks` `across` all `routes`.
|
||||
|
||||
## v0.2.78 (2025-07-02)
|
||||
- Implemented `automatic` `retries` for `webhook` `calls` that `experience` `timeouts`.
|
||||
- Added ``lg_api_http_requests_total`` `counter` and ``lg_api_http_requests_latency_seconds`` `histogram` `metrics` to `enable` `request` `rate` and `latency` `charting`.
|
||||
|
||||
## v0.2.77 (2025-07-02)
|
||||
- Added HTTP `metrics` for `better` `performance` `tracking`.
|
||||
- Changed the Redis `cache` `delimiter` to `reduce` `conflicts` with `subgraph` `messages`.
|
||||
|
||||
## v0.2.76 (2025-07-01)
|
||||
- Updated Redis `cache` `delimiter` to `prevent` `conflicts` with `subgraph` `messages`.
|
||||
|
||||
## v0.2.74 (2025-06-30)
|
||||
- Scheduled `webhooks` `using` an `isolated` `loop` to `ensure` `thread` `safety` and `prevent` `event` `loop` `errors`.
|
||||
|
||||
## v0.2.73 (2025-06-27)
|
||||
- Fixed an `issue` `causing` `infinite` `frame` `loops` and `removed` the ``dict_parser`` `due` to `structlog` `conflicts`.
|
||||
- Now `responds` with a 409 `error` when a `deadlock` `occurs` `during` `cancellation` `attempts`.
|
||||
|
||||
## v0.2.72 (2025-06-27)
|
||||
- Return a 409 `error` `code` when a `deadlock` `occurs` `during` `cancellation` `requests`.
|
||||
|
||||
## v0.2.71 (2025-06-26)
|
||||
- Improved `logging` by `updating` the `log` `type` for `better` `clarity` and `consistency`.
|
||||
|
||||
## v0.2.70 (2025-06-26)
|
||||
- Improved `error` `handling` to `better` `distinguish` `between` `user` `TimeoutError` and `system` `timeouts`, `ensuring` `clearer` `logging` and `encapsulation` of `user` `errors`.
|
||||
|
||||
## v0.2.69 (2025-06-26)
|
||||
- Enhanced the `crons` API with `sorting`, `pagination`, and `updated` `schema` `definitions` for `improved` `accuracy`.
|
||||
|
||||
## v0.2.66 (2025-06-26)
|
||||
- Fixed a 404 error when creating multiple runs with the same thread_id using `on_not_exist="create"`.
|
||||
|
||||
## v0.2.65 (2025-06-25)
|
||||
- Ensured that only fields from `assistant_versions` are returned when necessary.
|
||||
- Ensured consistent data types for in-memory and PostgreSQL users, improving internal authentication handling.
|
||||
|
||||
## v0.2.64 (2025-06-24)
|
||||
- Added descriptions to version entries for better clarity.
|
||||
|
||||
## v0.2.62 (2025-06-23)
|
||||
- Improved user handling for custom authentication in the JS Studio.
|
||||
- Added Prometheus-format run statistics to the metrics endpoint for better monitoring.
|
||||
- Added run statistics in Prometheus format to the metrics endpoint.
|
||||
|
||||
## v0.2.61 (2025-06-20)
|
||||
- Set a maximum idle time for Redis connections to prevent unnecessary open connections.
|
||||
|
||||
## v0.2.60 (2025-06-20)
|
||||
- Enhanced error logging to include traceback details for dictionary operations.
|
||||
- Added a `/metrics` endpoint to expose queue worker metrics for monitoring.
|
||||
|
||||
## v0.2.57 (2025-06-18)
|
||||
- Removed CancelledError from retriable exceptions to allow local interrupts while maintaining retriability for workers.
|
||||
- Introduced middleware to gracefully shut down the server after completing in-flight requests upon receiving a SIGINT.
|
||||
- Reduced metadata stored in checkpoint to only include necessary information.
|
||||
- Improved error handling in join runs to return error details when present.
|
||||
|
||||
## v0.2.56 (2025-06-17)
|
||||
- Improved application stability by adding a handler for SIGTERM signals.
|
||||
|
||||
## v0.2.55 (2025-06-17)
|
||||
- Improved the handling of cancellations in the queue entrypoint.
|
||||
- Improved cancellation handling in the queue entry point.
|
||||
|
||||
## v0.2.54 (2025-06-16)
|
||||
- Enhanced error message for LuaLock timeout during license validation.
|
||||
- Fixed the $contains filter in custom auth by requiring an explicit ::text cast and updated tests accordingly.
|
||||
- Ensured project and tenant IDs are formatted as UUIDs for consistency.
|
||||
|
||||
## v0.2.53 (2025-06-13)
|
||||
- Resolved a timing issue to ensure the queue starts only after the graph is registered.
|
||||
- Improved performance by setting thread and run status in a single query and enhanced error handling during checkpoint writes.
|
||||
- Reduced the default background grace period to 3 minutes.
|
||||
|
||||
## v0.2.52 (2025-06-12)
|
||||
- Now logging expected graphs when one is omitted to improve traceability.
|
||||
- Implemented a time-to-live (TTL) feature for resumable streams.
|
||||
- Improved query efficiency and consistency by adding a unique index and optimizing row locking.
|
||||
|
||||
## v0.2.51 (2025-06-12)
|
||||
- Handled `CancelledError` by marking tasks as ready to retry, improving error management in worker processes.
|
||||
- Added LG API version and request ID to metadata and logs for better tracking.
|
||||
- Added LG API version and request ID to metadata and logs to improve traceability.
|
||||
- Improved database performance by creating indexes concurrently.
|
||||
- Ensured postgres write is committed only after the Redis running marker is set to prevent race conditions.
|
||||
- Enhanced query efficiency and reliability by adding a unique index on thread_id/running, optimizing row locks, and ensuring deterministic run selection.
|
||||
- Resolved a race condition by ensuring Postgres updates only occur after the Redis running marker is set.
|
||||
|
||||
## v0.2.46 (2025-06-07)
|
||||
- Introduced a new connection for each operation while preserving transaction characteristics in Threads state `update()` and `bulk()` commands.
|
||||
|
||||
## v0.2.45 (2025-06-05)
|
||||
- Enhanced streaming feature by incorporating tracing contexts.
|
||||
- Removed an unnecessary query from the Crons.search function.
|
||||
- Resolved connection reuse issue when scheduling next run for multiple cron jobs.
|
||||
- Removed an unnecessary query in the Crons.search function to improve efficiency.
|
||||
- Resolved an issue with scheduling the next cron run by improving connection reuse.
|
||||
|
||||
## v0.2.44 (2025-06-04)
|
||||
- Enhanced the worker logic to exit the pipeline before continuing when the Redis message limit is reached.
|
||||
- Introduced a ceiling for Redis message size with an option to skip messages larger than 128 MB for improved performance.
|
||||
- Ensured the pipeline always closes properly to prevent resource leaks.
|
||||
|
||||
## v0.2.43 (2025-06-04)
|
||||
- Improved performance by omitting logs in metadata calls and ensuring output schema compliance in value streaming.
|
||||
- Ensured the connection is properly closed after use.
|
||||
- Aligned output format to strictly adhere to the specified schema.
|
||||
- Stopped sending internal logs in metadata requests to improve privacy.
|
||||
|
||||
## v0.2.42 (2025-06-04)
|
||||
- Added timestamps to track the start and end of a request's run.
|
||||
- Added tracer information to the configuration settings.
|
||||
- Added support for streaming with tracing contexts.
|
||||
|
||||
## v0.2.41 (2025-06-03)
|
||||
- Added locking mechanism to prevent errors in pipelined executions.
|
||||
@@ -5,17 +5,17 @@ The pages in this section provide end-to-end examples for the following topics:
|
||||
## General
|
||||
|
||||
- [Template Applications](../concepts/template_applications.md): Create a LangGraph application from a template.
|
||||
- [Agentic RAG](./rag/langgraph_agentic_rag.md): Build a retrieval agent that can decide when to use a retriever tool.
|
||||
- [Agent Supervisor](./multi_agent/agent_supervisor.md): Build a supervisor agent that can manage a team of agents.
|
||||
- [SQL agent](./sql/sql-agent.md): Build a SQL agent that can execute SQL queries and return the results.
|
||||
- [Agentic RAG](../tutorials/rag/langgraph_agentic_rag.md): Build a retrieval agent that can decide when to use a retriever tool.
|
||||
- [Agent Supervisor](../tutorials/multi_agent/agent_supervisor.md): Build a supervisor agent that can manage a team of agents.
|
||||
- [SQL agent](../tutorials/sql/sql-agent.md): Build a SQL agent that can execute SQL queries and return the results.
|
||||
- [Prebuilt chat UI](../agents/ui.md): Use a prebuilt chat UI to interact with any LangGraph agent.
|
||||
- [Graph runs in LangSmith](../how-tos/run-id-langsmith.md): Use LangSmith to track and analyze graph runs.
|
||||
|
||||
## LangGraph Platform
|
||||
|
||||
- [Set up custom authentication](./auth/getting_started.md): Set up custom authentication for your LangGraph application.
|
||||
- [Make conversations private](./auth/resource_auth.md): Make conversations private by using resource-based authentication.
|
||||
- [Connect an authentication provider](./auth/add_auth_server.md): Connect an authentication provider to your LangGraph application.
|
||||
- [Set up custom authentication](../tutorials/auth/getting_started.md): Set up custom authentication for your LangGraph application.
|
||||
- [Make conversations private](../tutorials/auth/resource_auth.md): Make conversations private by using resource-based authentication.
|
||||
- [Connect an authentication provider](../tutorials/auth/add_auth_server.md): Connect an authentication provider to your LangGraph application.
|
||||
- [Rebuild graph at runtime](../cloud/deployment/graph_rebuild.md): Rebuild a graph at runtime.
|
||||
- [Use RemoteGraph](../how-tos/use-remote-graph.md): Use RemoteGraph to deploy your LangGraph application to a remote server.
|
||||
- [Deploy CrewAI, AutoGen, and other frameworks](../how-tos/autogen-integration.md): Deploy CrewAI, AutoGen, and other frameworks with LangGraph.
|
||||
|
||||
+1
-1
@@ -261,6 +261,7 @@ nav:
|
||||
- MCP Adapters: reference/mcp.md
|
||||
- LangGraph Platform:
|
||||
- Server API: cloud/reference/api/api_ref.md
|
||||
- Server changelog: cloud/reference/langgraph_server_changelog.md
|
||||
- Control Plane API: cloud/reference/api/api_ref_control_plane.md
|
||||
- CLI: cloud/reference/cli.md
|
||||
- SDK (Python): cloud/reference/sdk/python_sdk_ref.md
|
||||
@@ -361,7 +362,6 @@ markdown_extensions:
|
||||
- github-callouts
|
||||
hooks:
|
||||
- _scripts/notebook_hooks.py
|
||||
- _scripts/copy_page_hooks.py
|
||||
extra:
|
||||
social:
|
||||
- icon: fontawesome/brands/js
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
/* Minimal CSS for copy page button */
|
||||
.copy-page-btn {
|
||||
background: transparent;
|
||||
border: 1px solid var(--md-default-fg-color--lightest);
|
||||
padding: 6px 12px;
|
||||
margin-right: 8px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: var(--md-default-fg-color);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.copy-page-btn:hover {
|
||||
background: var(--md-default-fg-color--lightest);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// Simple copy page functionality - just copy the markdown content
|
||||
function copyPageAsMarkdown() {
|
||||
const markdownScript = document.getElementById('page-markdown-content');
|
||||
if (!markdownScript) {
|
||||
alert('Markdown content not available for this page');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = JSON.parse(markdownScript.textContent);
|
||||
const content = `# ${data.title}\n\nSource: ${window.location.href}\n\n${data.markdown}`;
|
||||
|
||||
navigator.clipboard.writeText(content).then(() => {
|
||||
// Simple notification
|
||||
const notification = document.createElement('div');
|
||||
notification.textContent = 'Page content copied to clipboard';
|
||||
notification.style.cssText = 'position:fixed;top:20px;right:20px;background:#4CAF50;color:white;padding:10px;border-radius:4px;z-index:9999;';
|
||||
document.body.appendChild(notification);
|
||||
setTimeout(() => notification.remove(), 3000);
|
||||
}).catch(() => {
|
||||
alert('Failed to copy content');
|
||||
});
|
||||
} catch (e) {
|
||||
alert('Failed to parse page content');
|
||||
}
|
||||
}
|
||||
|
||||
// Add button to header - simpler approach
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const headerSource = document.querySelector('.md-header__source');
|
||||
if (headerSource) {
|
||||
const button = document.createElement('button');
|
||||
button.textContent = 'Copy page';
|
||||
button.onclick = copyPageAsMarkdown;
|
||||
button.style.cssText = 'background:none;border:1px solid #ddd;padding:6px 12px;margin-right:8px;border-radius:4px;cursor:pointer;';
|
||||
headerSource.parentNode.insertBefore(button, headerSource);
|
||||
}
|
||||
});
|
||||
@@ -13,121 +13,6 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
|
||||
{% block extrahead %}
|
||||
<meta name="algolia-site-verification" content="165B7E7C89E49946" />
|
||||
<script>
|
||||
// Simple copy page functionality - uses original markdown source
|
||||
function copyPageAsMarkdown() {
|
||||
const markdownScript = document.getElementById('page-markdown-content');
|
||||
if (!markdownScript) {
|
||||
alert('Markdown content not available for this page');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let rawContent = markdownScript.textContent;
|
||||
|
||||
// Import a library for decoding HTML entities
|
||||
import { decode } from 'he';
|
||||
|
||||
// Always decode HTML entities using a safe library
|
||||
rawContent = decode(rawContent);
|
||||
|
||||
|
||||
const data = JSON.parse(rawContent);
|
||||
const content = `# ${data.title}\n\nSource: ${window.location.href}\n\n${data.markdown}`;
|
||||
|
||||
navigator.clipboard.writeText(content).then(() => {
|
||||
// Simple notification
|
||||
const notification = document.createElement('div');
|
||||
notification.textContent = 'Page content copied to clipboard';
|
||||
notification.style.cssText = 'position:fixed;top:20px;right:20px;background:#4CAF50;color:white;padding:10px 16px;border-radius:4px;z-index:9999;box-shadow:0 2px 10px rgba(0,0,0,0.2);';
|
||||
document.body.appendChild(notification);
|
||||
setTimeout(() => notification.remove(), 3000);
|
||||
}).catch(() => {
|
||||
alert('Failed to copy content');
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Failed to parse page content:', e);
|
||||
alert('Failed to parse page content: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Add dropdown button to header when page loads
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const headerSource = document.querySelector('.md-header__source');
|
||||
if (headerSource) {
|
||||
// Create dropdown container
|
||||
const dropdownContainer = document.createElement('div');
|
||||
dropdownContainer.style.cssText = 'position:relative;display:inline-block;margin-left:8px;';
|
||||
|
||||
// Create main button
|
||||
const button = document.createElement('button');
|
||||
button.innerHTML = 'Copy page <span style="margin-left:8px;font-size:12px;color:#9ca3af;">▾</span>';
|
||||
button.style.cssText = 'background:transparent;border:1px solid #d1d5db;padding:6px 12px;border-radius:4px;cursor:pointer;font-size:14px;color:#374151;transition:all 0.2s ease;white-space:nowrap;display:flex;align-items:center;';
|
||||
|
||||
// Create dropdown menu
|
||||
const dropdown = document.createElement('div');
|
||||
dropdown.className = 'copy-page-dropdown';
|
||||
dropdown.style.cssText = 'position:absolute;top:100%;left:0;background:white;border:1px solid #e5e7eb;border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,0.15);z-index:1000;min-width:180px;display:none;padding:4px 0;';
|
||||
|
||||
// Create dropdown options
|
||||
const option1 = document.createElement('div');
|
||||
option1.textContent = 'Copy as Markdown for LLMs';
|
||||
option1.className = 'copy-page-option';
|
||||
option1.style.cssText = 'padding:8px 16px;cursor:pointer;font-size:14px;color:#374151;margin:2px 0;';
|
||||
option1.onmouseover = function() {
|
||||
this.style.background = document.documentElement.getAttribute('data-md-color-scheme') === 'slate' ? '#4a5568' : '#f8fafc';
|
||||
};
|
||||
option1.onmouseout = function() { this.style.background = 'transparent'; };
|
||||
option1.onclick = function() {
|
||||
copyPageAsMarkdown();
|
||||
dropdown.style.display = 'none';
|
||||
};
|
||||
|
||||
const option2 = document.createElement('div');
|
||||
option2.textContent = "View LangGraph's llms.txt";
|
||||
option2.className = 'copy-page-option';
|
||||
option2.style.cssText = 'padding:8px 16px;cursor:pointer;font-size:14px;color:#374151;margin:2px 0;';
|
||||
option2.onmouseover = function() {
|
||||
this.style.background = document.documentElement.getAttribute('data-md-color-scheme') === 'slate' ? '#4a5568' : '#f8fafc';
|
||||
};
|
||||
option2.onmouseout = function() { this.style.background = 'transparent'; };
|
||||
option2.onclick = function() {
|
||||
window.open('/llms-txt-overview/', '_blank');
|
||||
dropdown.style.display = 'none';
|
||||
};
|
||||
|
||||
// Add options to dropdown
|
||||
dropdown.appendChild(option1);
|
||||
dropdown.appendChild(option2);
|
||||
|
||||
// Button hover effects
|
||||
button.onmouseover = function() {
|
||||
this.style.background = '#f3f4f6';
|
||||
this.style.borderColor = '#9ca3af';
|
||||
};
|
||||
button.onmouseout = function() {
|
||||
this.style.background = 'transparent';
|
||||
this.style.borderColor = '#d1d5db';
|
||||
};
|
||||
|
||||
// Toggle dropdown
|
||||
button.onclick = function(e) {
|
||||
e.stopPropagation();
|
||||
dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none';
|
||||
};
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', function() {
|
||||
dropdown.style.display = 'none';
|
||||
});
|
||||
|
||||
// Assemble dropdown
|
||||
dropdownContainer.appendChild(button);
|
||||
dropdownContainer.appendChild(dropdown);
|
||||
headerSource.parentNode.insertBefore(dropdownContainer, headerSource.nextSibling);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
@import url("https://fonts.googleapis.com/css2?family=Public+Sans&display=swap");
|
||||
:root {
|
||||
@@ -313,17 +198,6 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
/* Copy page dropdown dark mode support */
|
||||
[data-md-color-scheme="slate"] .copy-page-dropdown {
|
||||
background: #1f2937 !important;
|
||||
border-color: #374151 !important;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.5) !important;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .copy-page-option {
|
||||
color: #e5e7eb !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
+1
-2
@@ -9,8 +9,7 @@
|
||||
"@langchain/core": "^0.3.38",
|
||||
"@langchain/openai": "^0.4.2",
|
||||
"msgpack-lite": "^0.1.26",
|
||||
"nock": "^14.0.1",
|
||||
"he": "^1.2.0"
|
||||
"nock": "^14.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/recommended": "^1.0.8",
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "langgraph"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
description = "Building stateful, multi-actor applications with LLMs"
|
||||
authors = []
|
||||
requires-python = ">=3.9"
|
||||
|
||||
Generated
+2
-2
@@ -670,7 +670,7 @@ name = "importlib-metadata"
|
||||
version = "8.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "zipp", marker = "python_full_version < '3.11'" },
|
||||
{ name = "zipp", marker = "python_full_version < '3.10'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" }
|
||||
wheels = [
|
||||
@@ -1192,7 +1192,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
|
||||
Generated
+1
-1
@@ -316,7 +316,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
source = { editable = "../langgraph" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
|
||||
Reference in New Issue
Block a user