@klarkxy/dsh-zhihu 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [简体中文](docs/README.zh-CN.md)
6
6
 
7
- Use Zhihu as a research source for the model: site search, web search, hot list, Zhida answers, knowledge bases, question discovery, and creator statistics. Every request uses your own Zhihu Access Secret; results are reference material, not conclusions.
7
+ Use Zhihu as a research source for the model: site search, web search, hot list, Zhida answers, knowledge bases, question discovery, and creator statistics. The `zhihu_sync` tool also saves and validates article, question, or pin drafts and uploads images. Every request uses your own Zhihu Access Secret; results are reference material, not conclusions.
8
8
 
9
9
  ## Install
10
10
 
@@ -25,6 +25,10 @@ Open **Plugins → Zhihu** and enter your Access Secret under **Settings**. Othe
25
25
 
26
26
  With the Web Search plugin installed, Zhihu web search also appears in its provider list.
27
27
 
28
+ ## Drafts and media
29
+
30
+ In **Plugins → Zhihu → Settings**, configure the publishing App Key and App Secret obtained from the [Zhihu publishing assistant](https://www.zhihu.com/playground/zhihu-publisher). These are separate from the search Access Secret. Zhihu’s authorization text can be pasted into the App Secret field when both credentials appear as `ZHIHU_OPENAPI_APP_KEY:"…"` and `ZHIHU_OPENAPI_APP_SECRET:"…"`; the plugin then keeps those two values only. The `zhihu_sync` tool then lets the model save, list, read, and validate drafts for articles, questions, or pins, and upload images for them. Drafts persist across DSH restarts. This version has no publishing interface: neither the model nor the plugin UI can publish a draft.
31
+
28
32
  ## Give the tools to the model
29
33
 
30
34
  These model tools are enabled by default; no agent preset changes needed:
@@ -38,10 +42,13 @@ These model tools are enabled by default; no agent preset changes needed:
38
42
  | `zhihu_user_content_detail`, `zhihu_user_content_comments` | Read your published content and its comments |
39
43
  | `zhihu_creator_account_stats`, `zhihu_creator_content_stats` | Your creator statistics |
40
44
  | `zhihu_quota` | Check official quota |
45
+ | `zhihu_sync` | Save, list, read, and validate drafts or upload images |
46
+
47
+ Site search and web search return an empty list when nothing matches, with an explanation when the service provides one.
41
48
 
42
49
  ## Cost protection
43
50
 
44
- By default the plugin uses only Zhihu's free allowance: before each request it checks the official quota and skips the request if none is left. It also stops after 100 requests per day (Beijing time). Both are configurable:
51
+ By default research requests use only Zhihu's free allowance: before each request the plugin checks the official quota and skips the request if none is left. It also stops after 100 requests per day (Beijing time). Both are configurable:
45
52
 
46
53
  ```yaml
47
54
  config:
@@ -54,9 +61,11 @@ Paid use is allowed only when you set `allowPaid: true` yourself.
54
61
  ## Limits
55
62
 
56
63
  - Zero cost is not guaranteed: other clients on the same account can use up the free allowance first.
57
- - Uploaded files are stored on Zhihu's servers; do not upload unpublished manuscripts. Each file is limited to 20 MB.
64
+ - Knowledge-base uploads are stored on Zhihu's servers and limited to 20 MB per file. Draft images are also uploaded to Zhihu.
65
+ - Drafts support articles, questions, and pins, not answers or videos. Supported images: JPEG, PNG, GIF, WebP, and BMP; GIF is limited to 15 MB and other images to 30 MB. Convert HEIC/AVIF before uploading.
66
+ - Draft and image operations use Zhihu's publishing authorization; search quota and paid-search settings do not govern them.
58
67
  - Requests are never retried automatically.
59
- - Not included: OAuth account switching, top-ups, or wallet queries.
68
+ - Not included: publishing drafts, OAuth account switching, top-ups, or wallet queries.
60
69
 
61
70
  ## License
62
71
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  [English](../README.md)
6
6
 
7
- 把知乎交给模型当调研信源:站内搜索、全网搜索、热榜、知乎直答、知识库、问题发现和创作数据。每次请求只用你自己的知乎 Access Secret;结果是参考资料,不是定论。
7
+ 把知乎交给模型当调研信源:站内搜索、全网搜索、热榜、知乎直答、知识库、问题发现和创作数据。`zhihu_sync` 工具还可以保存、校验文章、提问或想法草稿并上传配图。每次请求只用你自己的知乎 Access Secret;结果是参考资料,不是定论。
8
8
 
9
9
  ## 安装
10
10
 
@@ -25,6 +25,10 @@ dsh plugin --profile web add @klarkxy/dsh-zhihu
25
25
 
26
26
  安装网页搜索插件后,「知乎全网搜索」也会出现在它的服务列表中。
27
27
 
28
+ ## 草稿与配图
29
+
30
+ 在「插件 → 知乎 → 设置」填写发布用 App Key 和 App Secret,可从[知乎同步助手](https://www.zhihu.com/playground/zhihu-publisher)申请;它们与检索 Access Secret 分开配置。知乎的授权说明可以整段粘进 App Secret 输入框,条件是两项凭据都写成 `ZHIHU_OPENAPI_APP_KEY:"…"` 与 `ZHIHU_OPENAPI_APP_SECRET:"…"` 的形式;插件只保存其中的两项凭据。`zhihu_sync` 工具随后可让模型保存、列出、读取并校验文章、提问或想法草稿,并为其上传配图;草稿在重启 DSH 后仍可继续处理。本版本没有发布入口:模型与插件界面都不能发布草稿。
31
+
28
32
  ## 让模型使用
29
33
 
30
34
  以下模型工具默认启用,无需修改 Agent 预设:
@@ -38,10 +42,13 @@ dsh plugin --profile web add @klarkxy/dsh-zhihu
38
42
  | `zhihu_user_content_detail`、`zhihu_user_content_comments` | 读取自己已发布的内容及评论 |
39
43
  | `zhihu_creator_account_stats`、`zhihu_creator_content_stats` | 自己的创作数据 |
40
44
  | `zhihu_quota` | 查询官方额度 |
45
+ | `zhihu_sync` | 保存、列出、读取并校验草稿,上传配图 |
46
+
47
+ 站内搜索和全网搜索没有匹配内容时返回空列表;服务提供原因时会一并显示。
41
48
 
42
49
  ## 费用保护
43
50
 
44
- 默认只用知乎的免费额度:每次请求前先查官方额度,额度不足就不发送。另外每天(北京时间)最多 100 次请求。两项都可在插件配置中修改:
51
+ 检索与创作查询默认只用知乎的免费额度:每次请求前先查官方额度,额度不足就不发送。另外每天(北京时间)最多 100 次请求。两项都可在插件配置中修改:
45
52
 
46
53
  ```yaml
47
54
  config:
@@ -54,9 +61,11 @@ config:
54
61
  ## 限制
55
62
 
56
63
  - 不能保证绝对零费用:同一账号的其他客户端可能先用掉免费额度。
57
- - 上传的文件保存在知乎云端,请勿上传未发表手稿;单个文件最大 20 MB。
64
+ - 知识库文件保存在知乎云端,单个文件最大 20 MB;草稿配图也会上传到知乎。
65
+ - 草稿支持文章、提问和想法,不支持回答或视频。图片支持 JPEG、PNG、GIF、WebP、BMP;GIF 最大 15 MB,其他图片最大 30 MB。HEIC/AVIF 请先转换格式。
66
+ - 草稿与图片操作使用知乎提供的发布授权;搜索额度与付费搜索开关不控制它们。
58
67
  - 所有请求都不会自动重试。
59
- - 不提供 OAuth 账号切换、充值或钱包查询。
68
+ - 不提供草稿发布、OAuth 账号切换、充值或钱包查询。
60
69
 
61
70
  ## 许可证
62
71
 
package/lib/client.js CHANGED
@@ -1723,6 +1723,358 @@ function ZhihuQuotaSection({ rpc }) {
1723
1723
  });
1724
1724
  }
1725
1725
  //#endregion
1726
+ //#region src/client-sync-credentials.tsx
1727
+ /**
1728
+ * 知乎发布助手的应用凭据设置。
1729
+ *
1730
+ * 只有插件设置页(plugins.bundle.config)里才有这段表单:右侧栏工作台不发凭据,
1731
+ * 也不重复提供入口。凭据本身由宿主管理(remote.credentials),插件只读写引用。
1732
+ */
1733
+ const ZHIHU_APP_KEY_REF = "ZHIHU_OPENAPI_APP_KEY";
1734
+ const ZHIHU_APP_SECRET_REF = "ZHIHU_OPENAPI_APP_SECRET";
1735
+ /** 申请发布助手应用凭据的官方入口。 */
1736
+ const ZHIHU_PUBLISHER_URL = "https://www.zhihu.com/playground/zhihu-publisher";
1737
+ /** 成对保存时的挂起标记;它与凭据引用名不同,不会撞上字段的「保存中…」。 */
1738
+ const PAIR_PENDING = "pair";
1739
+ const GRANT_KEY = /ZHIHU_OPENAPI_APP_KEY\s*[:=]\s*"([^"\r\n]+)"/;
1740
+ const GRANT_SECRET = /ZHIHU_OPENAPI_APP_SECRET\s*[:=]\s*"([^"\r\n]+)"/;
1741
+ const GRANT_MARKER = /ZHIHU_OPENAPI_APP_(?:KEY|SECRET)/;
1742
+ /**
1743
+ * 知乎发布助手把授权说明和两项凭据放在同一段文字里。只在两项都用引号写出时拆开;
1744
+ * 单独粘贴的 Key 或 Secret 保持原样,避免把普通文本误存成另一项。
1745
+ */
1746
+ function parseZhihuPublisherGrant(value) {
1747
+ const key = GRANT_KEY.exec(value);
1748
+ const secret = GRANT_SECRET.exec(value);
1749
+ if (!key || !secret) return null;
1750
+ const appKey = key[1].trim();
1751
+ const appSecret = secret[1].trim();
1752
+ if (!appKey || !appSecret || appKey === appSecret) return null;
1753
+ return {
1754
+ appKey,
1755
+ appSecret
1756
+ };
1757
+ }
1758
+ /**
1759
+ * 文字里出现了凭据标记,但没能按受支持的格式读出两项凭据。这样的文本不能当凭据存:
1760
+ * 整段授权说明一旦落进密钥框,格式稍有出入就会变成一个坏凭据,而且要到发布时才报错。
1761
+ */
1762
+ function looksLikeZhihuPublisherGrant(value) {
1763
+ return GRANT_MARKER.test(value);
1764
+ }
1765
+ const FIELD_COPY = [{
1766
+ ref: ZHIHU_APP_SECRET_REF,
1767
+ label: "App Secret",
1768
+ zh: "知乎发布助手的应用密钥",
1769
+ en: "Publisher app secret",
1770
+ hintZh: "知乎发布助手签发的应用密钥。整段授权说明粘贴在这里时,下面会自动填入用户名。",
1771
+ hintEn: "The app secret issued by the Zhihu publisher. Pasting the authorization text here fills in the username below."
1772
+ }, {
1773
+ ref: ZHIHU_APP_KEY_REF,
1774
+ label: "App Key",
1775
+ zh: "知乎主页用户名",
1776
+ en: "Profile username",
1777
+ hintZh: "知乎个人主页地址里的用户名,例如 www.zhihu.com/people/<你的用户名>。这不是密钥,以明文显示。",
1778
+ hintEn: "The username in your Zhihu profile URL, e.g. www.zhihu.com/people/<your username>. This is not a secret and is shown in plain text.",
1779
+ plain: true
1780
+ }];
1781
+ /** 只有 http(s) 可以成为链接;其余一律降级为文本。 */
1782
+ function safeUrl$1(url) {
1783
+ return /^https?:\/\//i.test(url) ? url : null;
1784
+ }
1785
+ function PublisherLink(props) {
1786
+ const url = safeUrl$1(props.url);
1787
+ if (!url) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: props.children });
1788
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1789
+ className: "zhihu-link",
1790
+ href: url,
1791
+ target: "_blank",
1792
+ rel: "noreferrer noopener",
1793
+ children: props.children
1794
+ });
1795
+ }
1796
+ function ZhihuSyncCredentials(props) {
1797
+ const t = useT();
1798
+ const { credentials } = props;
1799
+ const alive = (0, react.useRef)(false);
1800
+ const [state, setState] = (0, react.useState)({ status: "loading" });
1801
+ const [drafts, setDrafts] = (0, react.useState)({});
1802
+ const [paired, setPaired] = (0, react.useState)(false);
1803
+ const [pending, setPending] = (0, react.useState)(null);
1804
+ const [confirmRef, setConfirmRef] = (0, react.useState)(null);
1805
+ const [failure, setFailure] = (0, react.useState)("");
1806
+ const [note, setNote] = (0, react.useState)("");
1807
+ const views = state.status === "ready" ? state.credentials : {};
1808
+ const writable = (ref) => views[ref]?.writable !== false;
1809
+ (0, react.useEffect)(() => {
1810
+ alive.current = true;
1811
+ return () => {
1812
+ alive.current = false;
1813
+ };
1814
+ }, []);
1815
+ const load = (0, react.useCallback)(async () => {
1816
+ setState((current) => current.status === "ready" ? current : { status: "loading" });
1817
+ try {
1818
+ const result = await credentials.describe({ refs: FIELD_COPY.map((field) => field.ref) });
1819
+ if (!alive.current) return;
1820
+ if (!result.ok) {
1821
+ setState({
1822
+ status: "error",
1823
+ error: result.error.message
1824
+ });
1825
+ return;
1826
+ }
1827
+ setState({
1828
+ status: "ready",
1829
+ credentials: result.value.credentials
1830
+ });
1831
+ } catch (cause) {
1832
+ if (!alive.current) return;
1833
+ setState({
1834
+ status: "error",
1835
+ error: cause instanceof Error ? cause.message : String(cause)
1836
+ });
1837
+ }
1838
+ }, [credentials]);
1839
+ (0, react.useEffect)(() => {
1840
+ load();
1841
+ }, [load]);
1842
+ const save = async (refs) => {
1843
+ if (pending !== null) return;
1844
+ const writes = refs.map((ref) => ({
1845
+ ref,
1846
+ value: (drafts[ref] ?? "").trim()
1847
+ })).filter((item) => item.value && writable(item.ref));
1848
+ if (!writes.length) return;
1849
+ setPending(refs.length > 1 ? PAIR_PENDING : refs[0]);
1850
+ setFailure("");
1851
+ setNote("");
1852
+ try {
1853
+ for (const write of writes) {
1854
+ const result = await credentials.set(write);
1855
+ if (!alive.current) return;
1856
+ if (!result.ok) {
1857
+ setFailure(result.error.message);
1858
+ return;
1859
+ }
1860
+ }
1861
+ setDrafts((current) => {
1862
+ const next = { ...current };
1863
+ for (const write of writes) delete next[write.ref];
1864
+ return next;
1865
+ });
1866
+ setPaired(false);
1867
+ setNote(writes.length > 1 ? ["已保存用户名与密钥。", "Saved the username and the secret."] : ["已保存。", "Saved."]);
1868
+ await load();
1869
+ } catch (cause) {
1870
+ if (!alive.current) return;
1871
+ setFailure(cause instanceof Error ? cause.message : String(cause));
1872
+ } finally {
1873
+ if (alive.current) setPending(null);
1874
+ }
1875
+ };
1876
+ const clear = async (ref) => {
1877
+ setConfirmRef(null);
1878
+ if (pending !== null) return;
1879
+ setPending(ref);
1880
+ setFailure("");
1881
+ setNote("");
1882
+ try {
1883
+ const result = await credentials.unset({ ref });
1884
+ if (!alive.current) return;
1885
+ if (!result.ok) {
1886
+ setFailure(result.error.message);
1887
+ return;
1888
+ }
1889
+ setDrafts((current) => ({
1890
+ ...current,
1891
+ [ref]: ""
1892
+ }));
1893
+ setPaired(false);
1894
+ setNote(["已清除。", "Cleared."]);
1895
+ await load();
1896
+ } catch (cause) {
1897
+ if (!alive.current) return;
1898
+ setFailure(cause instanceof Error ? cause.message : String(cause));
1899
+ } finally {
1900
+ if (alive.current) setPending(null);
1901
+ }
1902
+ };
1903
+ if (state.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
1904
+ className: "dsh-ui-stack",
1905
+ "aria-label": t("知乎发布助手凭据", "Zhihu publisher credentials"),
1906
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1907
+ className: "dsh-ui-loading",
1908
+ role: "status",
1909
+ children: t("正在读取凭据状态…", "Loading credential status…")
1910
+ })
1911
+ });
1912
+ if (state.status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1913
+ className: "dsh-ui-stack",
1914
+ "aria-label": t("知乎发布助手凭据", "Zhihu publisher credentials"),
1915
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1916
+ className: "dsh-ui-error",
1917
+ role: "alert",
1918
+ children: clientText(state.error, t)
1919
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1920
+ className: "dsh-ui-actions",
1921
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
1922
+ onClick: () => void load(),
1923
+ children: t("重试", "Retry")
1924
+ })
1925
+ })]
1926
+ });
1927
+ const confirmed = confirmRef ? FIELD_COPY.find((field) => field.ref === confirmRef) : void 0;
1928
+ const pairReady = paired && (drafts["ZHIHU_OPENAPI_APP_KEY"] ?? "").trim() !== "" && (drafts["ZHIHU_OPENAPI_APP_SECRET"] ?? "").trim() !== "";
1929
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1930
+ className: "dsh-ui-stack",
1931
+ "aria-label": t("知乎发布助手凭据", "Zhihu publisher credentials"),
1932
+ children: [
1933
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
1934
+ className: "dsh-ui-heading",
1935
+ children: t("知乎发布助手", "Zhihu publisher")
1936
+ }),
1937
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1938
+ className: "dsh-ui-help",
1939
+ children: [
1940
+ t("发布助手使用另一组应用凭据,与搜索用的 Access Secret 无关。把知乎给出的授权说明粘贴到上面的密钥框即可,两项凭据要写成 ZHIHU_OPENAPI_APP_KEY:\"…\" 与 ZHIHU_OPENAPI_APP_SECRET:\"…\" 的形式,用户名会自动填到下面。", "The publisher uses its own app credentials, separate from the search Access Secret. Paste Zhihu’s authorization text into the secret field above — both credentials must be written as ZHIHU_OPENAPI_APP_KEY:\"…\" and ZHIHU_OPENAPI_APP_SECRET:\"…\" — and the username fills in below."),
1941
+ " ",
1942
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PublisherLink, {
1943
+ url: ZHIHU_PUBLISHER_URL,
1944
+ children: t("申请凭据", "Get credentials")
1945
+ })
1946
+ ]
1947
+ }),
1948
+ paired ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1949
+ className: "dsh-ui-hint",
1950
+ children: t("这两项来自同一段授权说明,可以一起保存。", "These two came from the same authorization text and can be saved together.")
1951
+ }) : null,
1952
+ FIELD_COPY.map((field) => {
1953
+ const view = state.credentials[field.ref];
1954
+ const locked = view?.writable === false;
1955
+ const configured = view?.configured === true;
1956
+ const draft = drafts[field.ref] ?? "";
1957
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1958
+ className: "dsh-ui-field",
1959
+ children: [
1960
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1961
+ className: "dsh-ui-label-row",
1962
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1963
+ className: "dsh-ui-label",
1964
+ id: `zhihu-sync-cred-${field.ref}`,
1965
+ children: field.label
1966
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1967
+ className: "dsh-ui-row",
1968
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: locked ? "warning" : configured ? "done" : "idle" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1969
+ className: "dsh-ui-meta",
1970
+ children: locked ? t("由环境变量提供(只读)", "Provided by an environment variable (read-only)") : configured ? t("已保存", "Saved") : t("未配置", "Not set")
1971
+ })]
1972
+ })]
1973
+ }),
1974
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1975
+ className: "dsh-ui-hint",
1976
+ children: t(field.hintZh, field.hintEn)
1977
+ }),
1978
+ renderInput({
1979
+ type: field.plain ? "text" : "password",
1980
+ value: draft,
1981
+ disabled: locked || pending !== null,
1982
+ "aria-label": field.label,
1983
+ "aria-describedby": `zhihu-sync-cred-${field.ref}`,
1984
+ placeholder: locked ? t("由环境变量提供", "Set by an environment variable") : t("粘贴凭据", "Paste the credential"),
1985
+ onChange: (value) => {
1986
+ const grant = parseZhihuPublisherGrant(value);
1987
+ if (grant) {
1988
+ const next = {};
1989
+ if (writable("ZHIHU_OPENAPI_APP_KEY")) next[ZHIHU_APP_KEY_REF] = grant.appKey;
1990
+ if (writable("ZHIHU_OPENAPI_APP_SECRET")) next[ZHIHU_APP_SECRET_REF] = grant.appSecret;
1991
+ setDrafts((current) => ({
1992
+ ...current,
1993
+ ...next
1994
+ }));
1995
+ setPaired(true);
1996
+ setFailure("");
1997
+ setNote(Object.keys(next).length === 2 ? ["已从授权说明读出用户名与密钥。", "Read the username and the secret from the authorization text."] : ["已从授权说明读出一项凭据;另一项当前是只读的,不会被改动。", "Read one credential from the authorization text; the other is read-only and is left alone."]);
1998
+ return;
1999
+ }
2000
+ if (looksLikeZhihuPublisherGrant(value)) {
2001
+ setFailure(["这段文字里有知乎凭据标记,但没能读出 App Key 与 App Secret。请确认两项都写成 ZHIHU_OPENAPI_APP_KEY:\"…\" 与 ZHIHU_OPENAPI_APP_SECRET:\"…\" 的形式。", "This text contains Zhihu credential markers, but no App Key and App Secret could be read from it. Both must be written as ZHIHU_OPENAPI_APP_KEY:\"…\" and ZHIHU_OPENAPI_APP_SECRET:\"…\"."]);
2002
+ setNote("");
2003
+ return;
2004
+ }
2005
+ setDrafts((current) => ({
2006
+ ...current,
2007
+ [field.ref]: value
2008
+ }));
2009
+ if (!value.trim()) setPaired(false);
2010
+ setFailure("");
2011
+ setNote("");
2012
+ }
2013
+ }),
2014
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2015
+ className: "dsh-ui-actions",
2016
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
2017
+ variant: "primary",
2018
+ disabled: locked || pending !== null || draft.trim() === "",
2019
+ onClick: () => void save([field.ref]),
2020
+ children: pending === field.ref ? t("保存中…", "Saving…") : t("保存", "Save")
2021
+ }), locked ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
2022
+ variant: "danger",
2023
+ disabled: pending !== null || !configured,
2024
+ onClick: () => setConfirmRef(field.ref),
2025
+ children: pending === field.ref ? t("清除中…", "Clearing…") : t("清除", "Clear")
2026
+ })]
2027
+ })
2028
+ ]
2029
+ }, field.ref);
2030
+ }),
2031
+ pairReady ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2032
+ className: "dsh-ui-actions",
2033
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
2034
+ variant: "primary",
2035
+ disabled: pending !== null,
2036
+ onClick: () => void save([ZHIHU_APP_KEY_REF, ZHIHU_APP_SECRET_REF]),
2037
+ children: pending === PAIR_PENDING ? t("保存中…", "Saving…") : t("保存两项", "Save both")
2038
+ })
2039
+ }) : null,
2040
+ note ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2041
+ className: "dsh-ui-notice",
2042
+ role: "status",
2043
+ children: clientText(note, t)
2044
+ }) : null,
2045
+ failure ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2046
+ className: "dsh-ui-error",
2047
+ role: "alert",
2048
+ children: clientText(failure, t)
2049
+ }) : null,
2050
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
2051
+ open: confirmed !== void 0,
2052
+ onClose: () => setConfirmRef(null),
2053
+ title: t("清除发布助手凭据?", "Clear the publisher credentials?"),
2054
+ closeLabel: t("关闭", "Close"),
2055
+ className: "zhihu-panel",
2056
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2057
+ className: "dsh-ui-actions",
2058
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
2059
+ onClick: () => setConfirmRef(null),
2060
+ children: t("取消", "Cancel")
2061
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuButton, {
2062
+ variant: "danger",
2063
+ onClick: () => {
2064
+ if (confirmed) clear(confirmed.ref);
2065
+ },
2066
+ children: t("清除", "Clear")
2067
+ })]
2068
+ }),
2069
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2070
+ className: "dsh-ui-help",
2071
+ children: t("清除后,发布与预览会失败,直到重新填写凭据。", "Publishing and previewing fail until you enter the credentials again.")
2072
+ })
2073
+ })
2074
+ ]
2075
+ });
2076
+ }
2077
+ //#endregion
1726
2078
  //#region src/client.tsx
1727
2079
  const name = "dsh-zhihu-client";
1728
2080
  const inject = [
@@ -3226,7 +3578,11 @@ function ZhihuSettings(props) {
3226
3578
  "aria-label": t("知乎", "Zhihu"),
3227
3579
  children: [
3228
3580
  tablist,
3229
- panel("settings", /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsSection, { credentials }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuCallPolicySection, { rpc })] })),
3581
+ panel("settings", /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [
3582
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsSection, { credentials }),
3583
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuSyncCredentials, { credentials }),
3584
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuCallPolicySection, { rpc })
3585
+ ] })),
3230
3586
  panel("usage", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageSection, { rpc })),
3231
3587
  panel("quota", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZhihuQuotaSection, { rpc })),
3232
3588
  panel("knowledge", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KnowledgeSection, { rpc })),
package/lib/index.d.ts CHANGED
@@ -58,6 +58,7 @@ type ZhihuService = {
58
58
  toolOptions: ZhihuClientOptions & {
59
59
  onExecuted: (event: ZhihuSearchExecuted) => void;
60
60
  };
61
+ syncCall?: (endpoint: string, payload: unknown, signal: AbortSignal, caller: 'ui' | 'tool') => Promise<unknown>;
61
62
  };
62
63
  /** One shared service for direct UI RPC and optional Tool wrappers. */
63
64
  declare function createZhihuService(options: ZhihuClientOptions, usage: ZhihuUsageRecorder, notify?: (event: ZhihuSearchExecuted) => void): ZhihuService;