V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
hamsterbase
V2EX  ›  分享创造

文字直播用 chatgpt 开发一个笔记软件。 希望可以在睡觉前部署上线。

  •  
  •   hamsterbase ·
    carytrivett · 314 天前 · 3578 次点击
    这是一个创建于 314 天前的主题,其中的信息可能已经有所发展或是发生改变。

    下面是之前的聊天记录

    Q: 我现在希望开发一个卡片笔记软件。UI 界面类似于 imessage 。 请问叫什么名字比较好?

    A 为了取一个好名字,需要考虑软件的特点和目标用户。考虑到你的软件类似于 iMessage ,那么你可以考虑使用类似的命名方式。以下是一些可能的建议:

    CardMessage NoteChat CardNote SnapNote MemoTalk NoteMessage 在考虑具体的名字时,可以考虑软件的品牌形象,功能特点等方面,以便从众多同类软件中脱颖而出

    1. 搜了一下,memo talk 之前没人开发过,就叫这个名字吧。

    2. 建了一个 GitHub 仓库 https://github.com/hamsterbase/memo-talk

    Q: sha256 转化为字符串,长度是多少。

    A:

    SHA256 是一种加密算法,它生成的哈希值( hash value )通常表示为 64 个字符的十六进制字符串( hexadecimal string ),因此 SHA256 转化为字符串后的长度是 64 个字符。每个字符是 0-9 和 A-F 之间的一个 16 进制数字,因此它们可以表示 256 个可能的值( 16 的 4 次方)。

    5

    Q:

    我希望你能帮我编写一下服务端的代码。

    需要有以下四个接口接口为

    1. 获取文件夹列表

    2. 根据文件夹名字,文件名获取文件内容

    3. 根据文件夹名字,文件名删除文件

    4. 根据文件夹名字,文件名删除文件

    要求。

    1. 基于 koa 编写
    2. 使用 typescript 编写
    3. 文件名,文件名必须为 64 个字符,每个字符是 0-9 和 A-F 之间的一个 16 进制数字
    4. 文件夹名和文件名,文件内容都通过 body 传递。

    A: 以下是一个基于 Koa 和 TypeScript 的简单实现,其中包含了四个接口:

    import Koa from "koa";
    import bodyParser from "koa-bodyparser";
    
    const app = new Koa();
    
    // 定义文件夹列表
    let folders: string[] = [];
    
    // 定义文件内容
    let files: { [key: string]: string } = {};
    
    // 获取文件夹列表
    app.use(async (ctx, next) => {
      if (ctx.path === "/folders" && ctx.method === "GET") {
        ctx.body = folders;
        ctx.status = 200;
      } else {
        await next();
      }
    });
    
    // 获取文件内容
    app.use(async (ctx, next) => {
      if (ctx.path === "/files" && ctx.method === "GET") {
        const folderName = ctx.request.body.folderName;
        const fileName = ctx.request.body.fileName;
        const filePath = `${folderName}/${fileName}`;
        if (files[filePath]) {
          ctx.body = files[filePath];
          ctx.status = 200;
        } else {
          ctx.body = "File not found";
          ctx.status = 404;
        }
      } else {
        await next();
      }
    });
    
    // 添加文件
    app.use(bodyParser());
    app.use(async (ctx, next) => {
      if (ctx.path === "/files" && ctx.method === "POST") {
        const folderName = ctx.request.body.folderName;
        const fileName = ctx.request.body.fileName;
        const fileContent = ctx.request.body.fileContent;
        const filePath = `${folderName}/${fileName}`;
        if (files[filePath]) {
          ctx.body = "File already exists";
          ctx.status = 409;
        } else {
          files[filePath] = fileContent;
          ctx.status = 201;
        }
      } else {
        await next();
      }
    });
    
    // 删除文件
    app.use(async (ctx, next) => {
      if (ctx.path === "/files" && ctx.method === "DELETE") {
        const folderName = ctx.request.body.folderName;
        const fileName = ctx.request.body.fileName;
        const filePath = `${folderName}/${fileName}`;
        if (files[filePath]) {
          delete files[filePath];
          ctx.status = 204;
        } else {
          ctx.body = "File not found";
          ctx.status = 404;
        }
      } else {
        await next();
      }
    });
    
    app.listen(3000, () => {
      console.log("Server is running on port 3000");
    });
    

    请注意,此代码仅供参考,可能需要根据具体需求进行修改和调整。

    62 条回复    2023-06-19 13:37:38 +08:00
    hamsterbase
        1
    hamsterbase  
    OP
       314 天前
    6. 他没把文件写到本地,所以改一下 prompt

    Q:

    我希望你能帮我编写一下服务端的代码。

    需要有以下四个接口接口为

    1. 获取文件夹列表

    2. 根据文件夹名字,文件名获取文件内容

    3. 根据文件夹名字,文件名删除文件

    4. 根据文件夹名字,文件名删除文件

    要求。

    1. 基于 koa 编写
    2. 使用 typescript 编写
    3. 文件名,文件名必须为 64 个字符,每个字符是 0-9 和 A-F 之间的一个 16 进制数字
    4. 文件夹名和文件名,文件内容都通过 body 传递。
    5. 我希望你能用 koa-router 编写
    6. 文件希望写入到本地的 data 文件夹下
    7. API 必须以 public/api/v1 开头

    Q:

    ```ts
    import Koa from "koa";
    import Router from "koa-router";
    import fs from "fs/promises";
    import path from "path";

    const app = new Koa();
    const router = new Router();

    const DATA_FOLDER = "data";

    // 获取文件夹列表
    router.get("/public/api/v1/folders", async (ctx) => {
    try {
    const folders = await fs.readdir(DATA_FOLDER);
    ctx.body = folders;
    } catch (err) {
    ctx.status = 500;
    ctx.body = "Internal Server Error";
    console.error(err);
    }
    });

    // 根据文件夹名字,文件名获取文件内容
    router.get(
    "/public/api/v1/folders/:folderName/files/:fileName",
    async (ctx) => {
    const { folderName, fileName } = ctx.params;
    const filePath = path.join(DATA_FOLDER, folderName, fileName);
    try {
    const fileContent = await fs.readFile(filePath, "utf-8");
    ctx.body = fileContent;
    } catch (err) {
    if (err.code === "ENOENT") {
    ctx.status = 404;
    ctx.body = "File Not Found";
    } else {
    ctx.status = 500;
    ctx.body = "Internal Server Error";
    console.error(err);
    }
    }
    }
    );

    // 根据文件夹名字,文件名删除文件
    router.del(
    "/public/api/v1/folders/:folderName/files/:fileName",
    async (ctx) => {
    const { folderName, fileName } = ctx.params;
    const filePath = path.join(DATA_FOLDER, folderName, fileName);
    try {
    await fs.unlink(filePath);
    ctx.body = "File Deleted";
    } catch (err) {
    if (err.code === "ENOENT") {
    ctx.status = 404;
    ctx.body = "File Not Found";
    } else {
    ctx.status = 500;
    ctx.body = "Internal Server Error";
    console.error(err);
    }
    }
    }
    );

    // 根据文件夹名字,文件名删除文件
    router.post(
    "/public/api/v1/folders/:folderName/files/:fileName",
    async (ctx) => {
    const { folderName, fileName } = ctx.params;
    const filePath = path.join(DATA_FOLDER, folderName, fileName);
    try {
    await fs.mkdir(path.join(DATA_FOLDER, folderName), { recursive: true });
    await fs.writeFile(filePath, ctx.request.body);
    ctx.body = "File Created";
    } catch (err) {
    ctx.status = 500;
    ctx.body = "Internal Server Error";
    console.error(err);
    }
    }
    );

    app.use(router.routes());
    app.use(router.allowedMethods());

    app.listen(3000, () => {
    console.log("Server listening on port 3000");
    });
    ```

    接口说明:

    获取文件夹列表:GET /public/api/v1/folders
    根据文件夹名字,文件名获取文件内容:GET /public/api/v1/folders/:folderName/files/:fileName
    根据文件夹名字,文件名删除文件:DELETE /public/api/v1/folders/:folderName/files/:fileName
    根据文件夹名字,文件名删除文件:POST /public/api/v1/folders/:folderName/files/:fileName
    其中,文件名和文件夹名都是 64 个字符的 16 进制数字字符串。文件内容通过 Request Body 传递,文件保存在本地的 data 文件夹下。如果出现错误,会返回适当的 HTTP 状态码和错误信息。
    hamsterbase
        2
    hamsterbase  
    OP
       314 天前
    7. 之后不贴 chatgpt 的回答了,复制粘贴太麻烦啦。

    Q:

    我希望你能帮我编写一下服务端的代码。

    参数命名规则为 folder , file , content 。

    需要有以下 4 个接口为。

    1. 获取 folder 获取文件列表。

    2. 根据 folder ,file 获取 content

    3. 根据 folder ,file 删除文件

    4. 根据 folder ,file, content 写入文件

    要求。

    1. 基于 koa 编写,使用 typescript 编写
    2. folder , file 必须为 64 个字符,每个字符是 0-9 和 A-F 之间的一个 16 进制数字.
    3. 我希望你能用 koa-router 编写
    4. 文件希望写入到本地的 data 文件夹下
    5. 规则为 /data/folder/file
    6. API 必须以 public/api/v1 开头
    7. content 限制最大 1mb
    8. folder, file, content 都通过 body 获取,不要用 query,path 获取。
    hamsterbase
        3
    hamsterbase  
    OP
       314 天前
    8. koa-router 好像没有类型,换 @koa/router. 初始化项目,把 chatgpt 回答拷贝一下

    https://github.com/hamsterbase/memo-talk/commit/2cc9318a7255e2dafef2716e78b2e844953042cc
    hamsterbase
        4
    hamsterbase  
    OP
       314 天前
    9 。chatgpt 没有写文件名校验逻辑,让他单独写一个
    帮我用 ts 写一个字符串检测函数。 字符串长度必须为 64 个字符,每个字符是 0-9 和 A-F 之间的一个 16 进制数字.
    忽略大小写。 最终输出转换为小写后的结果。 如果不满足,则抛错。
    hamsterbase
        5
    hamsterbase  
    OP
       314 天前
    10. 白嫖 render.com , 服务器上线了 https://memo-talk.onrender.com
    hamsterbase
        6
    hamsterbase  
    OP
       314 天前
    11. 帮我想一下 memo-talk 前端仓库的名字

    以下是一些可能的 Memo-Talk 前端仓库的名字:

    - memo-talk-frontend:一个简单而明确的仓库名称,指示该仓库包含 Memo-Talk 的前端代码。
    - memo-talk-web:一个更简洁的仓库名称,指示该仓库包含 Memo-Talk 的 Web 界面代码。
    - memo-talk-ui:一个仓库名称,指示该仓库包含 Memo-Talk 的用户界面部分。
    - memo-talk-client:一个仓库名称,指示该仓库包含 Memo-Talk 的客户端代码。
    - memo-talk-portal:一个仓库名称,指示该仓库包含 Memo-Talk 的 Web 门户代码。
    你可以根据你的喜好和项目的具体需求选择其中任何一个名称。无论你选择哪个名称,都需要确保它能够准确地描述该仓库的内容,并且易于理解和记忆。此外,你可能还需要考虑使用标准的命名约定和格式,以便其他开发人员能够轻松地找到和使用你的仓库。
    hamsterbase
        7
    hamsterbase  
    OP
       314 天前
    hamsterbase
        8
    hamsterbase  
    OP
       314 天前
    13. 使用 vite 的 ts + react 模板,数据核心准备使用 yjs
    hamsterbase
        9
    hamsterbase  
    OP
       314 天前
    14. 前端上线啦,可以访问了。 开始写前端逻辑

    https://memo-talk-web.onrender.com/
    hamsterbase
        10
    hamsterbase  
    OP
       314 天前
    15.

    Q: 帮我设计一个 ts interface. NoteCore

    1. 新建笔记,返回 ID
    2. 根据 ID 返回笔记内容。
    3. 获取笔记列表.
    4. 根据 ID 删除笔记
    hamsterbase
        11
    hamsterbase  
    OP
       314 天前
    16. 设计一下核心的数据结构。 这个是我自己设计的


    export interface MemoTalk {
    id: string;
    content: string;
    createTime: number;
    }

    /**
    * MemoTalkCore 核心为一个 Y.Doc 对象
    * 保存的时候 encode 为字符串
    * 可以合并别的 MemoTalkCore, 入参为字符串
    */
    export interface MemoTalkCore {
    createMemoTalk(content: string): string;

    getMemoTalkById(id: string): MemoTalk;

    getMemoTalkList(): MemoTalk[];

    deleteMemoTalkById(id: string): void;

    /**
    * 把整个数据库序列化为字符串
    */
    encode(): string;

    merge(data: string): void;
    }
    hamsterbase
        12
    hamsterbase  
    OP
       314 天前
    17. 在 copilot 的帮助下,完成了核心逻辑的实现


    const enum YDocKey {
    /**
    * 避免 id 重复
    */
    id = "id",
    memoTalks = "memoTalks",
    }

    export class MemoTalkCore implements IMemoTalkCore {
    private ydoc: Y.Doc;
    constructor() {
    this.ydoc = new Y.Doc();
    }

    createMemoTalk(content: string): string {
    if (typeof content !== "string") {
    throw new Error("content must be string");
    }
    const idMap = this.ydoc.getMap(YDocKey.id);
    let id: string = nanoid();
    while (idMap.has(id)) {
    id = nanoid();
    }
    const memoTalksArray = this.ydoc.getArray<string>(YDocKey.memoTalks);

    const memoTalk = this.ydoc.getMap(id);
    memoTalk.set("content", content ?? "");
    memoTalk.set("createTime", Date.now());
    memoTalksArray.push([id]);
    return id;
    }

    getMemoTalkById(id: string): MemoTalk {
    const memoTalk = this.ydoc.getMap(id);
    return {
    id,
    content: memoTalk.get("content") as string,
    createTime: memoTalk.get("createTime") as number,
    };
    }

    getMemoTalkList(): MemoTalk[] {
    const memoTalksArray = this.ydoc.getArray<string>(YDocKey.memoTalks);
    return memoTalksArray.map((id) => this.getMemoTalkById(id));
    }

    deleteMemoTalkById(id: string): void {
    const memoTalksArray = this.ydoc.getArray<string>(YDocKey.memoTalks);
    memoTalksArray.delete(memoTalksArray.toArray().indexOf(id));
    }

    encode(): string {
    return fromUint8Array(Y.encodeStateAsUpdate(this.ydoc));
    }

    merge(data: string): void {
    Y.applyUpdate(this.ydoc, toUint8Array(data));
    }
    }
    hamsterbase
        13
    hamsterbase  
    OP
       314 天前
    18 。 帮我为 IMemoTalkCore 编写集成单元测试。 使用 jest


    ```ts
    /**
    * MemoTalkCore 核心为一个 Y.Doc 对象
    * 保存的时候 encode 为字符串
    * 可以合并别的 MemoTalkCore, 入参为字符串
    */
    export interface IMemoTalkCore {
    createMemoTalk(content: string): string;

    getMemoTalkById(id: string): MemoTalk;

    getMemoTalkList(): MemoTalk[];

    deleteMemoTalkById(id: string): void;

    /**
    * 把整个数据库序列化为字符串
    */
    encode(): string;

    merge(data: string): void;
    }
    ```
    hamsterbase
        14
    hamsterbase  
    OP
       314 天前
    19. 在 chatgpt 的帮助下测出了一个 bug, 如果删除了,再次获取的时候应该返回 null 。 所有测试都通过啦
    hamsterbase
        15
    hamsterbase  
    OP
       314 天前
    20. 完善一下单元测试

    Q: 帮我用 jest 编写测试。

    如果重复删除,应该抛错
    hamsterbase
        16
    hamsterbase  
    OP
       314 天前
    21 Q: 帮我写一个 react 的页面,需要用 typescript 开发。


    最顶上是一个提示框。 红色的字 “此项目还在开发中,请不要使用”

    中间为一个消息窗口。 消息都靠右。

    消息为圆角。消息的背景为蓝色。 参考 imessage

    页面背景为白色。

    最下面是一个 input 框。 里面有一个按钮。 按钮上面的字为 “发送”


    颜色要好看一点。 圆角保存统一。

    props 为 memotalks:MemoTalk, onCreateMemoTalk(content:string):void

    export interface MemoTalk {
    id: string;
    content: string;
    createTime: number;
    }
    hamsterbase
        18
    hamsterbase  
    OP
       314 天前
    23: 前端最基础的功能开发玩了,还没接入服务器。


    1. 支持新增消息了, 消息更新后会储存到 local storage 里。
    2. 刷新页面后支持恢复消息

    在线地址: https://memo-talk-web.onrender.com/

    明天晚上继续开始直播。

    1. 接入服务
    2. 支持设置密码和账户
    3. 支持下载当前数据库,支持上传数据库。
    findex
        19
    findex  
       314 天前 via iPhone
    关注一下
    ShuaiYH
        20
    ShuaiYH  
       314 天前
    @hamsterbase #5 来试试 zeabur.com 哈哈
    llsongll
        21
    llsongll  
       314 天前
    感觉不错~
    hamsterbase
        22
    hamsterbase  
    OP
       314 天前 via Android
    @findex

    建议改一下默认授权。 上来就要求我私有库权限。 我选择拒绝。
    hamsterbase
        23
    hamsterbase  
    OP
       314 天前 via Android
    @ShuaiYH

    你们默认的权限要的太多了。 建议只授权 public 项目 + 邮箱。 等用户需要部署私有项目的时候,再请求进一步的授权。
    之前我就是被这个方案劝退了。
    HulkLee
        24
    HulkLee  
       313 天前
    哈哈哈 今天晚上的几点开始,蹲一波
    hamsterbase
        25
    hamsterbase  
    OP
       313 天前
    @HulkLee 开始开始。~~~
    hamsterbase
        26
    hamsterbase  
    OP
       313 天前
    1. 今天的目标: 发布 1.0 !!!

    1. 接入 antd mobile ,重写 UI
    2. 接入后端,实现登录功能.
    1. 服务器不储存任何账户密码。只储存用户的 token ,token 由前端生成。
    2. 把 token 作为文件夹名
    3. token 的生成规则:sha256( `${sha256(user)} ${sha256(password)}`)
    4. 文件储存之前使用 aes 加密。 加密 为 sha256(password)
    3. 接入 PWA , 让软件可离线使用
    hamsterbase
        27
    hamsterbase  
    OP
       313 天前
    2. localstorage 存储量有限,准备基于 IndexedDB 实现本地存储


    Q: 帮我基于 IndexedDB 实现一个 ISettingService 吧,我要把所有的配置都存在 IndexedDB 里面。

    export type SettingsValue = string | boolean;

    export enum StorageKeys {
    backend = "backend",
    "backend.hamsterbase.entrypoint" = "backend.hamsterbase.entrypoint",
    "backend.hamsterbase.token" = "backend.hamsterbase.token",
    "backend.notion.token" = "backend.notion.token",
    "backend.notion.databaseId" = "backend.notion.databaseId",
    "backend.notion.license" = "backend.notion.license",
    autoOn = "autoOn",
    autoOnBlockList = "autoOnBlockList",
    }

    export interface ISettingService {
    _serviceBrand: undefined;

    get<V extends SettingsValue>(key: string, defaultValue: V): Promise<V>;

    set(key: string, value: SettingsValue): Promise<void>;

    readConfig<T extends Record<StorageKeys, SettingsValue>>(
    defaultValue: T
    ): Promise<T>;
    }
    hamsterbase
        28
    hamsterbase  
    OP
       313 天前
    好的,我可以帮您实现一个基于 IndexedDB 的 ISettingService 。以下是实现代码示例:

    typescript
    Copy
    export interface ConfigDatabase {
    settings: Record<string, SettingsValue>;
    }

    export class IndexedDBSettingService implements ISettingService {
    private readonly dbName: string = "my-settings-db";
    private readonly dbVersion: number = 1;
    private readonly storeName: string = "settings";
    private db: IDBDatabase;

    constructor() {
    this.openDB();
    }

    private async openDB(): Promise<void> {
    return new Promise<void>((resolve, reject) => {
    const request = indexedDB.open(this.dbName, this.dbVersion);

    request.onerror = (event) => {
    console.error("Error opening database", event);
    reject(event);
    };

    request.onsuccess = (event) => {
    this.db = request.result;
    resolve();
    };

    request.onupgradeneeded = (event) => {
    const db = request.result;
    if (!db.objectStoreNames.contains(this.storeName)) {
    db.createObjectStore(this.storeName);
    }
    };
    });
    }

    public async get<V extends SettingsValue>(
    key: string,
    defaultValue: V
    ): Promise<V> {
    return new Promise<V>((resolve, reject) => {
    const transaction = this.db.transaction(this.storeName, "readonly");
    const objectStore = transaction.objectStore(this.storeName);

    const request = objectStore.get(key);

    request.onerror = (event) => {
    console.error(`Error getting value for key "${key}"`, event);
    reject(event);
    };

    request.onsuccess = (event) => {
    const value = request.result !== undefined ? request.result : defaultValue;
    resolve(value);
    };
    });
    }

    public async set(key: string, value: SettingsValue): Promise<void> {
    return new Promise<void>((resolve, reject) => {
    const transaction = this.db.transaction(this.storeName, "readwrite");
    const objectStore = transaction.objectStore(this.storeName);

    const request = objectStore.put(value, key);

    request.onerror = (event) => {
    console.error(`Error setting value for key "${key}"`, event);
    reject(event);
    };

    request.onsuccess = (event) => {
    resolve();
    };
    });
    }

    public async readConfig<T extends Record<StorageKeys, SettingsValue>>(
    defaultValue: T
    ): Promise<T> {
    const config: T = {} as T;

    // Read all values from the database and merge with default values
    for (const key in defaultValue) {
    if (defaultValue.hasOwnProperty(key)) {
    const value = await this.get(key, defaultValue[key]);
    config[key as keyof T] = value as T[keyof T];
    }
    }

    return config;
    }

    public async clearAll(): Promise<void> {
    return new Promise<void>((resolve, reject) => {
    const transaction = this.db.transaction(this.storeName, "readwrite");
    const objectStore = transaction.objectStore(this.storeName);

    const request = objectStore.clear();

    request.onerror = (event) => {
    console.error("Error clearing settings", event);
    reject(event);
    };

    request.onsuccess = (event) => {
    resolve();
    };
    });
    }
    }
    在这个实现中,我们创建了一个名为 my-settings-db 的 IndexedDB 数据库,并创建了一个名为 settings 的对象存储。在 get 和 set 方法中,我们分别使用事务来读取和写入数据。在 readConfig 方法中,我们遍历默认配置对象中的所有键,并使用 get 方法来获取每个键的值。最后,我们还为 IndexedDBSettingService 类添加了一个清除所有设置的 clearAll 方法。

    请注意,由于 IndexedDB 是异步的,因此所有方法都返回一个 Promise 。此外,如果 IndexedDB 连接失败或出现错误,这些方法将会拒绝 Promise 并输出错误信息。
    hamsterbase
        29
    hamsterbase  
    OP
       313 天前
    3. 对 chatgpt 写的代码不太放心,准备写一个单元测试

    Q: 帮我用 mocha 写单元测试

    ```ts
    export interface ISettingService {
    get<V extends SettingsValue>(key: string, defaultValue: V): Promise<V>;

    set(key: string, value: SettingsValue): Promise<void>;

    readConfig<T extends Record<string, SettingsValue>>(
    defaultValue: T
    ): Promise<T>;
    }
    ```
    hamsterbase
        30
    hamsterbase  
    OP
       313 天前
    4. 本来准备在 vite 里跑 mocha ,试了一下发现不太行,准备换一条路。 用假的 indexedDB 测试。 测试继续在 vitest 里跑。
    hamsterbase
        31
    hamsterbase  
    OP
       313 天前
    5. 逻辑和测试都让 chatgpt 写好了, 看了一下测试似乎没问题

    他给的是在 IndexedDBSettingService 构造函数里调用 openDB, 我改成了每次 set , get 的时候初始化,似乎没问题。

    https://github.com/hamsterbase/memo-talk-web/commit/31cc5a97a31d69f015f6ecfd089fb3147c1db7dd
    hamsterbase
        32
    hamsterbase  
    OP
       313 天前
    6. 使用 vite 的多页面模式开发。 初始化一下 settings 页面。 配置一下 vite 的构建配置


    https://github.com/hamsterbase/memo-talk-web/commit/d7024d70c6c63cca47aa550175504aa95e22f3eb
    echoless
        33
    echoless  
       313 天前
    https://github.com/swuecho/chat

    你用我这个 gpt 套🉑️, 可以直接导出 markdown 或者分享对话. 好看多了
    hamsterbase
        34
    hamsterbase  
    OP
       313 天前
    7. 因为是极简的稍后读工具,所以在不校验账户密码。 只需要确保账户密码非空就行了

    简单完成了账户密码的保存功能。 未来会加上警告,用户协议。 目前暂时先不加.

    因为 antdmobile 是在 2019 年之后出的,chatgpt 估计不认识,就手写了

    https://github.com/hamsterbase/memo-talk-web/commit/c5559845d27cdeba278c9969330c474e26db9ac6
    hamsterbase
        35
    hamsterbase  
    OP
       313 天前
    8. 简单更新了一下首页的 UI , 然后把储存从 localstorage 换到了 IndexedDB
    开始真正对接服务器了。

    第一版做的简单一点,在页面加载的时候异步合并一下服务器的数据,合并后把数据上传到服务器。
    hamsterbase
        36
    hamsterbase  
    OP
       313 天前
    9 。 开始编写服务器需要的一些工具函数


    Q:
    帮我用 crypto-js 编写函数。

    输入账户,密码。 生成两个 key

    第一个 key sha256(memo:sha256(username):sha256(password):talk);
    第二 key 。sha256(memo:password:talk

    用 ts 编写

    第一个 key 名字为 userToken , 第二个 key 名字为 加密密钥(用英文)
    hamsterbase
        37
    hamsterbase  
    OP
       313 天前
    10. 帮我编写一个 aes 加密函数. 输入 key 和 data, 返回加密后的数据. 输入 data 和 key , 返回解密后的数据

    基于 crypto-js 和 ts
    hamsterbase
        38
    hamsterbase  
    OP
       313 天前
    11: 帮上面的 encryptedData ,decryptedData 写测试。 随机的 key 和 data 。 加密和解密后数据不变
    hamsterbase
        39
    hamsterbase  
    OP
       313 天前
    12:帮我写一个 sha256 的函数。 基于 ts . crypto-js. 输入字符串,输出 hex
    hamsterbase
        40
    hamsterbase  
    OP
       313 天前
    13: POE 一天一次的 gpt4 体验卡

    帮我基于下面的代码,写一个 js sdk 出来。


    https://github.com/hamsterbase/memo-talk/blob/main/server/index.ts
    hamsterbase
        41
    hamsterbase  
    OP
       313 天前
    ```
    class FileAPISDK {
    constructor(baseUrl = "http://localhost:9999") {
    this.baseUrl = baseUrl;
    }

    async getList(folder) {
    const response = await fetch(`${this.baseUrl}/public/api/v1/folder`, {
    method: "POST",
    headers: {
    "Content-Type": "application/json",
    },
    body: JSON.stringify({ folder }),
    });

    this._handleErrors(response);
    return await response.json();
    }

    async getFile(folder, file) {
    const response = await fetch(`${this.baseUrl}/public/api/v1/file`, {
    method: "GET",
    headers: {
    "Content-Type": "application/json",
    },
    body: JSON.stringify({ folder, file }),
    });

    this._handleErrors(response);
    return await response.text();
    }

    async deleteFile(folder, file) {
    const response = await fetch(`${this.baseUrl}/public/api/v1/file`, {
    method: "DELETE",
    headers: {
    "Content-Type": "application/json",
    },
    body: JSON.stringify({ folder, file }),
    });

    this._handleErrors(response);
    return await response.text();
    }

    async createFile(folder, file, content) {
    const response = await fetch(`${this.baseUrl}/public/api/v1/file`, {
    method: "POST",
    headers: {
    "Content-Type": "application/json",
    },
    body: JSON.stringify({ folder, file, content }),
    });

    this._handleErrors(response);
    return await response.text();
    }

    _handleErrors(response) {
    if (!response.ok) {
    throw new Error(`API request failed with status ${response.status}`);
    }
    }
    }
    ```
    hamsterbase
        42
    hamsterbase  
    OP
       313 天前
    14. 发现服务器忘记配置跨域了。

    q:koajs 如何开启跨区请求。
    hamsterbase
        43
    hamsterbase  
    OP
       313 天前
    11. 服务器问题修复后,开始对接。 核心的云同步逻辑非常简单


    const sdk = new FileAPISDK(url);

    // 生成加密密钥
    // userToken 用来标记用户身份,也是服务器文件夹的名字
    // encryptionKey 是文件加密密码,encryptionKey 不会发送到服务
    const { userToken, encryptionKey } = generateKeys(username, passowrd);

    // 获取服务器数据库列表
    const files: string[] = await sdk.getList(userToken);

    const mergedFiles: string[] = [];

    // 把服务器上的文件和本地合并,合并后加入等待删除列表
    for (const file of files) {
    try {
    const remoteData = await sdk.getFile(userToken, file);
    const database = decryptData(remoteData, encryptionKey);
    memoTalkCore.merge(database);
    mergedFiles.push(file);
    } catch (error) {
    console.log(error);
    }
    }

    // 获取本地最新数据,加密
    const currentData = encryptData(memoTalkCore.encode(), encryptionKey);
    const fileName = sha256(currentData);

    // 把加密后的文档存到服务器
    await sdk.createFile(userToken, fileName, currentData);

    // 因为数据已经合并到本地了,所以把服务器的删掉
    for (const mergedFile of mergedFiles) {
    try {
    await sdk.deleteFile(userToken, mergedFile);
    } catch (error) {
    console.log(error);
    }
    }
    }
    hamsterbase
        44
    hamsterbase  
    OP
       313 天前
    12. 经过 2 天 6 小时的开发。 一个本地优先的极简卡片笔记就开发完了。(只有最核心功能)

    可以访问 https://memo-talk-web.onrender.com 体验

    注意: 项目还没正式发布,只能拿来玩。,不要在这里储存任何数据。

    1. 纯 web 实现, 压缩后不到 173 k 。
    2. 支持离线编辑,再次联网后自动同步。
    3. 两台设备输入相同的账户密码。 刷新页面刷新后,他们会自动同步到同一个状态。
    4. 所有数据在本地加密,服务器不储存明文数据。
    HulkLee
        45
    HulkLee  
       312 天前
    牛,面向 chatgpt 编程。
    llsongll
        46
    llsongll  
       311 天前
    nb ,我就想着有这种笔记,跟微信聊天似的,能搜索,快捷输入,(最好能匹配像是 alfred ,utools 这种工具,安卓可以一键输入。)
    temberature
        47
    temberature  
       311 天前
    @llsongll 直接用微信不行吗 :)
    llsongll
        48
    llsongll  
       310 天前
    @temberature #47 微信不是有监测吗。。。而且微信以前有插件可以用 alfred 输入,现在好像不行了。
    temberature
        49
    temberature  
       309 天前
    @llsongll 这些确实也都是问题。但反过来说,为什么要像微信聊天呢~没有这个要求有很多应用差不多吧
    llsongll
        50
    llsongll  
       309 天前
    @temberature #49 嗯呢,我也有用 Obsidian 的 memos ,也有类似的效果,alfred 也可以在不打开 ob 的情况下快捷输入。就是虽然界面体验比微信的感觉差了好多
    temberature
        51
    temberature  
       309 天前
    @llsongll 具体能说出来界面差在哪里吗 :)
    dvbs2000
        52
    dvbs2000  
       309 天前
    能麻烦直接把对话的 url 共享出来么 谢谢~ gpt 现在是有共享功能的。直接就可以共享
    coolcfan
        53
    coolcfan  
       309 天前
    @llsongll 之前推很大的 Flomo 是不是就是这种
    llsongll
        54
    llsongll  
       309 天前
    @coolcfan #53 类似,我说的 memos 就是 flome 差不多的开源版
    llsongll
        55
    llsongll  
       309 天前
    @temberature #51 我其实也说不出来个具体,可以简单描述下我需要的功能。比如 1 、微信的联系人可以看做 ob 不同的库,例:inbox ;剪藏;日记; note 等等,这个栏可折叠可隐藏可快捷键切换,我以前用 typora 时,就是分文件夹的,打标签的话,搜索的时候会多输入,再一个限制自己少分类,我一般都是用 alfred 搜索的,倒是无所谓,但是手机端搜索就会麻烦,这样会好点。
    2 、输入框在下面,焦点永远在输入框内,在写长文的时候可放大输入框,隐藏聊天记录,写完发送自动显示聊天记录,恢复刚才的界面。
    3 、消息框,右键复制、删除、引用、转发到其他库、多选
    4 、图片、文件、文字,标签和多选批量打标签
    5 、搜索:可在单独库搜索,可全部库搜索。可搜文件,图片,视频,链接,可日期排序。可定位到当时聊天的地方,可定位在 finder 中。微信或者 notion 那种弹出式搜索就非常舒服,ob 侧边栏搜索超级难受。
    6 、显示问题,我是用笔记本的,比较少用台式,我认为屏幕太大,会分散焦点,在我 13 寸屏幕上,显示三分之二或者五分之三会是一个比较合适的大小。
    ---
    以上这些应该是基本功能,下面我是常用的功能,每个笔记软件我都期待的。
    1 、速度,我调教后的 ob ,使用了 6 个插件,一个小主题,因为老电脑所以打开还是比较慢,但是常驻后台会占用我为数不多的内存,所以,要么打开速度快,要么占用低,比较个人,穷逼的苦楚。
    2 、快捷输入,mac:alfred 可以快捷输入到任何一个 md 文件中。手机端,启动软件后直接弹出输入框,因为要查看的话,不建议多点一下输入框外的部分,输入的话更需要快速,否则对于我这种老年人,怕不是过一会就忘了。。。
    3 、mac 和手机端剪藏,这个感觉 md 的都不太好,所以我现在都手动复制,不太了解。
    4 、本地。同步对我来说不是刚需,md 的同步都会被检测到,如何要同步反而数据库其实更安全。
    5 、界面简洁好看,我的 ob 比微信界面还小,没有侧边栏,标签栏,全部都隐藏了,光秃秃的。
    ---
    大概想的就是这些,我可能说的不太有逻辑性,你凑乎看看。。。其实刚需就只有快捷输入,和快捷搜索。其他的都是体验类,能忍受,但会随时想换一个试试。
    temberature
        56
    temberature  
       308 天前
    @llsongll 我还在用 12 年的 mac :)
    基础功能主要考验细节把控能力,微信到今天打磨的确实挺不错的。
    我主要关注的还是核心需求,快分为两种,一种绝对值的快,类似技术性能上的参数;一种是能产生质变的快,笔记这件事,根本限制还是来自大脑的认知能力,人又不擅长同时处理多件事情,所以快可以让人集中应对更丰富内聚的内容。
    llsongll
        57
    llsongll  
       305 天前
    @temberature #56 哈哈哈,我在用 13 年的 mac 。你说的对,ob 、vc 等对于老电脑来说,速度确实不丝滑。我想要的快捷输入和小界面,就像你说的集中应对。不过我是个设计师,所以可能对界面美观度要求更高一点。我找过一些软件,比如 nvALT ; FSnotes ;打开速度都很快,只是一些细节,样式不太好,所以我只拿来记录一些摘抄。你可以试一试,后者基于前者开发
    chenyduan
        58
    chenyduan  
       305 天前
    歪个楼,求一个可以上 ChatGPT 不封号的梯子
    temberature
        59
    temberature  
       304 天前
    @chenyduan 最近好多了,网络一般只是不让用,疯号好多是因为黑卡
    temberature
        60
    temberature  
       304 天前
    @llsongll 感谢推荐,其实我在开发一个 IM 架构的给自己用,就是好奇为什么你也有这种需求,样式我喜欢简洁的那种,一般都不会太丑。
    llsongll
        61
    llsongll  
       304 天前
    @temberature #60 哇~厉害,希望有机会我可以体验一下
    temberature
        62
    temberature  
       304 天前
    @llsongll 等我再调整调整 :)
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5548 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 06:38 · PVG 14:38 · LAX 23:38 · JFK 02:38
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.