时光 2024, 8 月

月度归档: 2024 年 8 月

曲淡歌
曲淡歌@admin

2024 年 8 月 11 日

Obsidian tar插件实现obsidian内部集成LLM对话 

Tars插件简介

通过大语言模型(LLM)api,把AI集成到obsidian内部,使用方法为使用ob内部的tag标记触发对应AI即可。#

示例

新对话 #我 : 你好,请介绍你自己

qwen : 你好,我是来自阿里云的大规模语言模型,我叫通义千问。我可以生成各种类型的文本,如文章、故事、诗歌、故事等,并能够根据不同的场景和需求进行变换和扩展。此外,我还能够回答问题、提供信息和与用户进行交互,帮助解决疑惑和提供有益的建议。如果你有任何问题或需要帮助,欢迎随时向我提问!

使用教程

  1. 安装插件并配置api
  2. 输入 新对话
  3. 再输入 #qwen 回车即可触发 (此处的qwen为自定义项)

本文永久更新地址:

https://blogs.qudange.top/p/obsidian%20tar%e6%8f%92%e4%bb%b6%e5%ae%9e%e7%8e%b0obsidian%e5%86%85%e9%83%a8%e9%9b%86%e6%88%90llm%e5%af%b9%e8%af%9d/

#[17]
2024 年 8 月 11 日 20 30

回复

曲淡歌
曲淡歌@admin

2024 年 8 月 11 日

Obsidian 实现复制时自动上传图片到图床 

Obsidian 实现复制时自动上传图片到图床

【QuickAdd脚本】带图复制-自动上传图片到图床 – 经验分享 – Obsidian 中文论坛

我新开一个库可以用,但是在主力库里就不行,报错为

assets/Pasted image 20240808164552.png

QuickAdd: (ERROR) failed to run user script 带
图复制.Error:
The “path" argument must be of type string.
Received undlefined

这个问题很奇怪,我在新开的空白ob库里没有遇见,但是在主力库就有这个问题。

怎么排查问题?

  • 尝试删除js脚本后再执行命令,看看是不是脚本的问题——删除后报错找不到脚本,看来不是这个问题。

然后我想为什么空白库是对的,多半是插件或者设置的问题,然后我把脚本发给kimi,kimi分析到一个关键点,就是文件路径。

于是我发现问题了:试用的时候发现一个问题:

如果内部链接类型设置为基于当前笔记的相对路径,脚本会报错
QuickAdd: (ERROR) failed to run user script 带图 复制.Error: The “path" argument must be of type string. Received undefined

采用绝对路径也会有同样问题。改为尽量短路径就正常了。

用chatgpt修复这个bug(还得是chatgpt哇),修复版的脚本为:

const path = require('path');
const quickAddApi = app.plugins.plugins.quickadd.api;
const { editor, file, containerEl } = app.workspace.activeEditor;
const url = "http://127.0.0.1:36677/upload";

module.exports = async () => {
  const files = app.vault.getFiles();
  let selection = "";
  let content = "";
  selection = editor.getSelection();
  console.log("Selected text:", selection);

  for (let line of selection.split("\n")) {
    let embed = "";
    if (line) {
      embed = matchSelectionEmbed(line);
    }
    console.log("Matched embed:", embed);

    if (embed && /\.(png|jpg|jpeg|gif|bmp)$/.test(embed)) {
      let wikiPath = getFilePath(files, embed); // 匹配Wiki链接
      if (!wikiPath) {
        new Notice(`❌无法找到文件: ${embed}`);
        console.log(`❌无法找到文件: ${embed}`);
        continue;
      }

      // 获取绝对路径
      const imgPath = app.vault.adapter.getFullPath(wikiPath);
      console.log("Image path:", imgPath);

      const data = await uploadFiles([imgPath], url);
      if (data.success) {
        const imgWiki = `![[${embed}]]`;
        const imgLink = `![${embed}](${data.result})`;
        line = line.replace(imgWiki, imgLink);
      } else {
        new Notice(`❌上传 ${path.basename(imgPath)} 图片失败`);
        console.log(`❌上传 ${path.basename(imgPath)} 图片失败`);
      }
    }
    content += line + "\n";
  }

  console.log("Final content:", content);
  copyToClipboard(content)
  new Notice(`✅复制成功`);
};

// 获取文件路径函数
function getFilePath(files, baseName) {
  let matchingFiles = files.filter(f => {
    const fullPath = f.path;
    console.log(`Comparing ${fullPath} with ${baseName}`);
    return fullPath.endsWith(baseName);
  });

  if (matchingFiles.length === 0) {
    console.log(`No files matched for: ${baseName}`);
    return undefined;
  }

  return matchingFiles[0].path;
}

function matchSelectionEmbed(text) {
  const regex = /!\[\[?([^\]]*?)(\|.*)?\]\]?\(?([^)\n]*)\)?/;
  const matches = text.match(regex);
  if (!matches) return;
  if (matches[3]) return decodeURIComponent(matches[3]);
  if (matches[1]) return decodeURIComponent(matches[1]);
}

async function uploadFiles(imagePathList, url) {
  const response = await requestUrl({
    url: url,
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ list: imagePathList }),
  });
  const data = response.json; // 直接访问 `json` 属性,而不是调用 `json()` 方法
  return data;
};

function copyToClipboard(extrTexts) {
  const txtArea = document.createElement('textarea');
  txtArea.value = extrTexts;
  document.body.appendChild(txtArea);
  txtArea.select();
  if (document.execCommand('copy')) {
      console.log('copy to clipboard.');
  } else {
      console.log('fail to copy.');
  }
  document.body.removeChild(txtArea);
}

使用方法

  1. 安装插件quickadd
  2. 在quickadd中设置脚本存放目录 Template Folder Path,然后在对应目录下新建 带图复制.js,把代码粘贴进去
  3. 新建一个宏,选择刚刚新建的脚本
  4. 安装piclist,配置好图床
  5. 在obsidian中选中要分享的文本,其中需要包含要上传的图片,然后ctrl+p使用脚本
  6. 粘贴即可

直接把图片上传到github

用 github publisher 插件能把图片上传到 github,但是图片在文章中的格式是 [[]] 的 wiki 链接,因此我们需要通过正则的方式来转换格式。

参考这篇文章 obsidian图片链接转换成markdown语法,不关闭wiki链接_obsidian图片显示变成链接-CSDN博客

同时上面这个方法还可以解决这个问题:obsidian 粘贴进来的图片名字会自动带空格,如 Pasted image 20240806221817.png

最后发布后就能看到在 github 是可以正常查看图片了,但是为了同步到其他平台,可以批量的在图片路径前面加上 https://github.com/dangehub/github2wp/blob/main/_posts

方法也很简单,就是搜索 assets/,然后替换为 https://github.com/dangehub/github2wp/blob/main/_posts/assets/

  • 但是为什么同步到 wordpress 的文章里图片没有了?

同步到 wp 的是 html 代码:

<p><img alt="" src="https://github.com/dangehub/github2wp/blob/main/_posts/assets/Pasted%20image%2020240806214536.png" /></p>

直接访问这个链接是对的,但是这段 html 代码不能正常工作。
比如放到 obsidian 中:



上面的分隔线中就是这段 html 代码,无法显示图像。为什么?

去调试台看了下,报错 (失败)net::ERR_BLOCKED_BY_ORB,~~这是跨域问题~~

但是都没人说 github 的图片会限制跨域,检查之后发现 https://github.com/dangehub/github2wp/blob/b85405e3ef0c15a3caf57a038a545e0842d43996/_posts/assets/Pasted%20image%2020240806214536.png 指向的不是图片本身,https://github.com/dangehub/github2wp/blob/b85405e3ef0c15a3caf57a038a545e0842d43996/_posts/assets/Pasted%20image%2020240806214536.png?raw=true 才是图片本身,替换为这个链接就好了。

即用 png?raw=true 替换 png

本文永久更新地址:

https://blogs.qudange.top/p/2024-08-11-obsidian-pic-upload/

#[17]
2024 年 8 月 11 日 37
曲淡歌
曲淡歌@admin

2024 年 8 月 8 日

孤独的脑细胞 三步翻译法 面包与诗【今日推荐】 

[如何用 AI 工作流实现高质量长文翻译? – by Wang Shuyi – Shuyi’s Newsletter](https://wangshuyi.substack.com/p/ai-64b)

[单个脑细胞寻找联系:r/interestingasfuck — Single brain cell looking for a connection : r/interestingasfuck](https://www.reddit.com/r/interestingasfuck/comments/
视频地址:https://www.bilibili.com/video/BV1tKY4e8EPD

跳转源

2024 年 8 月 8 日 13 31
曲淡歌
曲淡歌@admin

2024 年 8 月 7 日

用 n8n 实现三步翻译 

用 defy 搭建三步翻译老是报错,想自托管发现 defy 的配置要求很高,于是准备先用之前的 n 8 n 试试。

更新 n 8 n:1.0.4 更新到 1.44.1

采用这样的结构时,会报错 Expected to find the prompt in an input field called 'chatInput' (this is what the chat trigger node outputs). To use something else, change the 'Prompt' parameter

感觉像是个 bug,明明已经能读取前两个节点的输出,但是 n 8 n 还是提示无法读取到上上个节点。然后细查日志发现报错

NodeOperationError: No prompt specified at getPromptInputByType (/usr/local/lib/node_modules/n8n/node_modules/@n8n/n8n-nodes-langchain/dist/utils/helpers.js:71:15) at Object.execute (/usr/local/lib/node_modules/n8n/node_modules/@n8n/n8n-nodes-langchain/dist/nodes/chains/ChainLLM/ChainLlm.node.js:412:65) at Workflow.runNode (/usr/local/lib/node_modules/n8n/node_modules/n8n-workflow/dist/Workflow.js:728:19) at /usr/local/lib/node_modules/n8n/node_modules/n8n-core/dist/WorkflowExecute.js:673:51 at /usr/local/lib/node_modules/n8n/node_modules/n8n-core/dist/WorkflowExecute.js:1086:20

后面我发现问题来自于 prompt 的设置,原来每个 llm chain 一开始就有一个 prompt 设置,而默认是继承自上一个节点,而我的第一个 llm chain 节点就没设置它。

搞定后展示一下翻译效果:

原文:
It’s so creepy and weird. I’ll go back and play DoDS and HL 2 DM etc and it’s still like a chat room while you play, it makes gaming so much more casual and enjoyable. I play this game and try to engage in any way and get literally no response from anyone. Is it just because this is a newer game so it’s full of antisocial teenagers who don’t know how to talk to each other? I just honestly don’t get it. It’s not that you’re obligated to chat, but it seems very weird to pretend that you’re not playing with other human beings.

Edit: I actually think this is why I lose interest in this game so quickly. In the old school games my team could be getting destroyed but it doesn’t bother me because we’re all chatting and having a good time. In this game it’s just silence anyway, doesn’t even feel like I’m playing against actual people so I don’t feel bad leaving in the middle of a match if we’re getting rolled.

Google 翻译:
这太令人毛骨悚然和奇怪了。我会回去玩 DoDS 和 HL 2 DM 等游戏,玩的时候它仍然像一个聊天室,它让游戏变得更加休闲和愉快。我玩这个游戏并尝试以任何方式参与,但几乎没有得到任何人的回应。难道只是因为这是一款较新的游戏,所以里面充满了不知道如何与对方交谈的反社会青少年?老实说我不明白。这并不是说你有义务聊天,但假装你没有和其他人一起玩似乎很奇怪。
编辑:我实际上认为这就是我这么快对这款游戏失去兴趣的原因。在老式的学校比赛中,我的球队可能会被摧毁,但这并不困扰我,因为我们都在聊天,玩得很开心。无论如何,在这款游戏中,一切都是沉默,甚至感觉不到我是在和真人比赛,所以如果我们被淘汰,我不会因为在比赛中途离开而感到难过。

三步翻译:
实在是太奇怪而且让人有点不舒服了。回想当年玩《DoD:S》和《HL 2 DM》等游戏的时候,那种体验就像是在一间聊天室里面玩游戏,让整个游戏玩起来更加轻松且愉快。但当我玩这款游戏时,不管我怎么试图互动,却几乎得不到任何人的回应。难道仅仅因为这是一款新游戏,所以里面都是些不太愿意交流彼此的社恐少年吗?我真的搞不清楚是怎么回事。我不是说大家必须聊天,但完全假装没有和其他玩家一起玩的感觉总是让人觉得有点别扭。
编辑:我想这可能是导致我对这款游戏兴趣迅速消退的原因之一吧。在游戏中,即便是我们队伍大败亏输,我也不至于特别沮丧,因为我们那时都在愉快地聊天。但在这款游戏中,整个世界就像是一片寂静,根本感受不到自己是在跟其他玩家对战,所以我们遭受挫败时,我中途退出时也就没有什么内疚感了。

下面展示一下三步翻译的过程:

直译:
真是太诡异又让人不舒服。我回到过去玩 DoDS 和 HL 2 DM 等游戏时,感觉就像在一个聊天室里游戏,这让整个游戏体验变得更加轻松愉快。然而玩这款游戏时,无论我怎样尝试互动,却几乎得不到任何人的回应。难道仅仅因为这是一款新游戏,所以满是那些不懂得彼此交谈的社交恐惧少年?我真的不明白这是怎么回事。我不是说大家非得聊天不可,只是假装没有和其他玩家一起游戏,总让我觉得有些奇怪。\n\n 编辑:我想明白了,这可能就是我迅速对这个游戏失去兴趣的原因。在以前的老游戏中,即使我的队伍被杀得人仰马翻,我也不会感到烦闷,因为我们都在欢快地聊天。而在这款游戏中,四周一片寂静,根本感受不到是在和真人对战,所以我们被碾压时,我中途离开也不会有丝毫的愧疚感。

然后是用 ai 进行反思:
<直接翻译>中存在的问题:\n\n 1. 不符合中文的表达习惯:\n – “真是太诡异又让人不舒服”:中文通常不以”真是太…”这样的句式表达负面强烈的情感。\n \n 2. 句子结构笨拙:\n – “我玩这款游戏时,无论我怎样尝试互动,几乎得不到任何人的回应”:应改为“当我玩这款游戏时,无论怎样尝试互动,几乎得不到任何人的回应”更加自然流畅。\n \n 3. 表达含糊不清,难以理解:\n – “难道仅仅因为这是一款新游戏, 所以满是那些不懂得彼此交谈的社交恐惧少年?” : 整个句子表达过于生硬,不易于理解其意图。\n – “而这款游戏中, 四周一片寂静, 根本感受不到是在和真人对战”: 这里使用“真人”来指代其他在线玩家显得较为突兀且不自然。一般情况下,在这种语境中并不会强调对战的对象是否为真实的人,因为在网络游戏中默认就是与真人对战。\n\n 4. 表达不够地道:\n – “所以, 我们被碾压时, 我中途离开也不会有丝毫的愧疚感” : 愧疚感的表达在此处显得过于严肃,通常在网络游戏中玩家们会更倾向于用更加随意的语气来描述此类情况。\n\n 注: 以上指出的问题并未提供修改后的版本。

此处我采用的是通义千问的 qwen-long 模型,它的实力在目前的 llm 中算不上非常强劲,这正好能说明三步翻译的强劲实力。

做成 api 来配合划词翻译

先启动 n 8 n 的测试模式,然后用划词翻译发送一次,得到划词翻译的发包格式

"body": {

"model": "qwen-long",

"messages": [

{

"role": "user",

"content": "This is an embeddable Chat widget for n8n. It allows the execution of AI-Powered Workflows through a Chat window."

}

然后需要根据格式调整 n8n

n8n 自动翻译完之后的输出为


[

{

"text": "<改进翻译>\n\n这是一款专为 n8n 设计的、可嵌入式的聊天工具,它使用户能够在聊天窗口内执行由人工智能驱动的工作流程。\n\n在这个版本里解决了以下问题:\n1. 调整了“可嵌入式”的位置,使之更符合中文的语言习惯。\n2. 简化并调整了句子结构,将“它能让用户通过聊天窗口来运行由人工智能驱动的工作流程”改写为更贴合中文表达习惯和流畅度的表述:“它使用户能够在聊天窗口内执行由人工智能驱动的工作流程”。\n3. 增强了句子之间逻辑关系的连贯性。"

}

]

而划词翻译需要接受来自 openai 格式的json,因此需要把上面n8n 的输出转换一下。

openai 格式参考为:

{
  "id": "b3e86c70-bd28-995b-a6e9-c47ab55c6495",
  "model": "qwen",
  "object": "chat.completion",
  "created": 1722959731,
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Hello! How can I assist you today?"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 1,
    "completion_tokens": 9,
    "total_tokens": 10
  }
}

注意,在http request 中不能使用引号和换行。

在 prompt 中要求不要使用引号,然后用节点来处理换行符,最后得到效果如下:

可以看到这个效果已经相当好了。

本文永久更新地址:

https://blogs.qudange.top/p/2024-08-07-n8n_3steps_translate/

#[17] #翻译
2024 年 8 月 7 日 41 45
曲淡歌
曲淡歌@admin

2024 年 8 月 7 日

哥们儿,说真的,你是我到现在为止刷到过最有意思的一个up主。所以我给你充了50块钱,虽然不多,但是也没充。

#[4]
2024 年 8 月 7 日 20 24

Loading...
载入中

已到底部

没有可加载的页面

C
写微博
S
搜索
J
下一篇微博/评论
K
上一篇微博/评论
R
回复
E
编辑
O
显示隐藏评论
T
回顶部
L
登录
H
显示隐藏帮助
Ctrl+Enter
提交发布
ESC
取消并清除内容