- Introduced `card_language` attribute in ProjectRecord and Workspace classes to handle multiple languages for project cards. - Updated project creation and renaming methods to accept and store the card language. - Enhanced ZoteroReader to read and return attachment metadata, including language-specific summaries and claims. - Modified LLM client to generate card content based on the specified language, supporting both English and Chinese. - Updated tests to cover new functionality, ensuring correct handling of multilingual card generation and retrieval. - Adjusted UI tests to verify the presence of language selection options and proper rendering of multilingual content.
99 lines
4.1 KiB
Python
99 lines
4.1 KiB
Python
import json
|
|
from pathlib import Path
|
|
|
|
from zotero_kb.cards import CardBuilder
|
|
from zotero_kb.zotero_reader import ZoteroItemRecord
|
|
|
|
|
|
class FakeLlmClient:
|
|
def generate_card(self, source_bundle: dict[str, object]) -> dict[str, object]:
|
|
language = str(source_bundle.get("card_language", "en"))
|
|
return {
|
|
"summary": "中文卡片摘要。" if language == "zh" else "Merged metadata and attachment text improve drafting support.",
|
|
"core_claims": [
|
|
"中文卡片 claim。" if language == "zh" else "Project-scoped cards reduce irrelevant retrieval.",
|
|
],
|
|
"methods": [
|
|
"Combines notes, metadata, and full text.",
|
|
],
|
|
"evidence": [
|
|
"The merged pipeline improved citation precision.",
|
|
],
|
|
"citations": [
|
|
{
|
|
"claim": "Project-scoped cards reduce irrelevant retrieval.",
|
|
"quote": "Project-scoped card retrieval improves citation precision during drafting.",
|
|
"paraphrase": "Scoped retrieval improved citation precision during drafting.",
|
|
"quote_source": "attachment_texts",
|
|
"use_case": "direct_quote",
|
|
}
|
|
],
|
|
"quotable_passages": [
|
|
"Project-scoped card retrieval improves citation precision during drafting.",
|
|
],
|
|
"writing_hints": [
|
|
"Use when arguing for scoped retrieval during drafting.",
|
|
],
|
|
"keywords": ["retrieval", "writing"],
|
|
}
|
|
|
|
|
|
def test_build_card_writes_markdown_and_indexes(tmp_path: Path) -> None:
|
|
item = ZoteroItemRecord(
|
|
item_key="PAPER0001",
|
|
title="Card Pipelines for Research Writing",
|
|
creators=["Alice Smith", "Bob Li"],
|
|
year="2024",
|
|
item_type="journalArticle",
|
|
abstract="Merged metadata and notes improve drafting support.",
|
|
tags=["llm", "writing"],
|
|
collection_paths=[["Theory", "Drafting"]],
|
|
notes=["Merged notes matter."],
|
|
attachments=[
|
|
{
|
|
"path": str(tmp_path / "zotero" / "storage" / "ATTACH01" / "paper.txt"),
|
|
"filename": "paper.txt",
|
|
"content_type": "text/plain",
|
|
"is_pdf": False,
|
|
}
|
|
],
|
|
attachment_texts=["This paper studies card pipelines for research writing."],
|
|
)
|
|
builder = CardBuilder(workspace_dir=tmp_path, llm_client=FakeLlmClient())
|
|
|
|
result = builder.build_or_update(item, "en")
|
|
|
|
expected_path = (
|
|
tmp_path
|
|
/ "library"
|
|
/ "collections"
|
|
/ "Theory"
|
|
/ "Drafting"
|
|
/ "Card Pipelines for Research Writing [PAPER0001][en].md"
|
|
)
|
|
assert result.card_path == expected_path
|
|
card_text = result.card_path.read_text(encoding="utf-8")
|
|
assert card_text.startswith("---")
|
|
assert "# Citations" in card_text
|
|
assert "quote_source: attachment_texts" in card_text
|
|
assert "locator:" not in card_text
|
|
|
|
cards_index = json.loads((tmp_path / "library" / "index" / "cards.json").read_text(encoding="utf-8"))
|
|
assert cards_index["PAPER0001"]["en"]["title"] == "Card Pipelines for Research Writing"
|
|
assert cards_index["PAPER0001"]["en"]["language"] == "en"
|
|
assert cards_index["PAPER0001"]["en"]["citations"][0]["use_case"] == "direct_quote"
|
|
assert "locator" not in cards_index["PAPER0001"]["en"]["citations"][0]
|
|
assert cards_index["PAPER0001"]["en"]["attachments"][0]["filename"] == "paper.txt"
|
|
assert cards_index["PAPER0001"]["en"]["attachments"][0]["is_pdf"] is False
|
|
|
|
items_index = json.loads((tmp_path / "library" / "index" / "items.json").read_text(encoding="utf-8"))
|
|
assert items_index["PAPER0001"]["card_path"] == str(expected_path)
|
|
assert items_index["PAPER0001"]["card_paths"]["en"] == str(expected_path)
|
|
|
|
source_bundle = json.loads(
|
|
(tmp_path / "library" / "cache" / "source-bundles" / "PAPER0001.en.json").read_text(encoding="utf-8")
|
|
)
|
|
assert source_bundle["item_key"] == "PAPER0001"
|
|
assert source_bundle["card_language"] == "en"
|
|
assert source_bundle["attachments"][0]["filename"] == "paper.txt"
|