Jianhao Tian
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Value Canvas Agent - API Documentation This document provides comprehensive technical guidance for integrating with the `@value_canvas` agent, which is designed to guide users through the creation of a Value Canvas. The agent operates as a standalone service, communicating with the front-end via an HTTP API and interacting with application data through a dedicated back-end API. ## Table of Contents 1. **Interacting with the Agent: A Front-end Guide** * [High-Level Conversational Flow](#high-level-conversational-flow) * [API for Agent Interaction](#api-for-agent-interaction) * [Core Interaction Pattern with IDs](#core-interaction-pattern-with-ids) * [API Endpoints](#api-endpoints) * [Agent-UI Collaboration: Database as the Single Source of Truth](#agent-ui-collaboration-database-as-the-single-source-of-truth) * [Future Enhancement: Structured UI Interactions](#future-enhancement-structured-ui-interactions) 2. **Agent's Internal Logic: Database Interaction** * [Collaboration with the Backend Team](#collaboration-with-the-backend-team) * [Database Operations for Data Interaction](#api-endpoints-for-database-interaction) * [Key Identifiers (`user_id`, `doc_id`)](#key-identifiers-user_id-doc_id) 3. **AI-Driven Dynamic Testing Framework** * [Core Capabilities of the 'AI Examiner'](#core-capabilities-of-the-ai-examiner) * [Traceable Test Reports](#traceable-test-reports) * [Framework Value: Beyond the Ideal Scenario](#framework-value-beyond-the-ideal-scenario) 4. **Appendix** * [Understanding the Core Concepts: The Thread and `thread_id`](#appendix-understanding-the-core-concepts-the-thread-and-thread_id) --- ## 1. Interacting with the Agent: A Front-end Guide This section provides a direct guide to building a user interface that communicates with the Value Canvas agent's back-end API. ### <a id="high-level-conversational-flow"></a>High-Level Conversational Flow The agent guides the user through a structured, multi-step process to build their Value Canvas. The conversation is broken down into distinct sections: 1. Initial Interview 2. Ideal Customer Persona (ICP) 3. The Pain (x3) 4. The Deep Fear 5. The Payoffs (x3) 6. Signature Method 7. The Mistakes 8. The Prize ### <a id="api-for-agent-interaction"></a>API for Agent Interaction The front-end interacts with this agent service via HTTP endpoints. The entire interaction pattern is built around the `user_id` and `thread_id`. For a conceptual understanding of how the agent manages conversations and threads, please refer to the [Appendix](#appendix-understanding-the-core-concepts-the-thread-and-thread_id). #### <a id="core-interaction-pattern-with-ids"></a>Core Interaction Pattern with IDs This is the fundamental lifecycle for handling `user_id` and `thread_id` between the front-end and the various agent services: 1. **Get it**: The front-end receives the `thread_id` from the API response after its first `invoke` call to the agent, which starts a new working session. 2. **Store it**: The front-end should then save the new `thread_id` and associate it with the current user's project data in the database. 3. **Use it**: For any subsequent message in a session, the front-end must include **both the `user_id` and the `thread_id`** when calling this agent service. #### <a id="api-endpoints"></a>API Endpoints **1. Getting a Single Response (`/value-canvas/invoke`)** This endpoint handles both sending messages to an existing conversation and starting a new one. To start a new thread, send the first message with a `user_id` but **omit the `thread_id`**. The agent service will create a new thread and return its unique `thread_id` in the response. For all subsequent messages in that thread, include the `thread_id` you received. - **Endpoint**: `POST /value-canvas/invoke` - **Request Body (`UserInput`)**: ```json { "message": "I'm a consultant for tech startups.", "user_id": "some-unique-user-id", "thread_id": "optional-thread-uuid-for-an-existing-thread" } ``` - **Success Response (200 OK) (`InvokeResponse`)**: ```json { "output": { "type": "ai", "content": "Hello! Let's begin by gathering some information for your Value Canvas. What's your full name, and what would you like me to call you during our conversation?", "tool_calls": [], "tool_call_id": null, "run_id": "c66481a3-c7d9-441e-9f96-8a522babf5ec", "response_metadata": {}, "custom_data": {} }, "thread_id": "15442997-03b4-40e3-b3f6-27bd41a10ab1", "user_id": "b6c48d70-c44f-446f-99e1-8423a7dbd7c2" } ``` **2. Streaming a Response (`/value-canvas/stream`)** - **Endpoint**: `POST /value-canvas/stream` - **Request Body (`StreamInput`)**: Same as `/value-canvas/invoke`. - **Success Response (200 OK)**: A stream of `text/event-stream` data. #### Rationale: Why Server-Sent Events (SSE) When implementing real-time output we use **Server-Sent Events (SSE)**, also known as `text/event-stream`. It offers the following advantages: 1. **One-way push** – the backend can continuously push multiple chunks over a single HTTP connection, eliminating the need for polling. 2. **Lightweight connection** – compared with WebSocket, SSE runs over HTTP/1.1, is easy to deploy, and naturally supports proxy traversal and resumable connections. 3. **Automatic reconnection** – the browser’s `EventSource` automatically reconnects after network interruptions, reducing front-end maintenance overhead. The core SSE flow is: - The client issues `POST /value-canvas/stream` and keeps the header `Accept: text/event-stream` (most libraries set this automatically). - The server immediately replies with status 200, sets `Content-Type: text/event-stream`, and then streams chunked text in **`data: ...\n\n`** frames. - Frames are separated by a **double newline** `\n\n`; once the backend finishes, it closes the connection. > Note: Each frame wraps a **JSON string** identical to the `output` object returned by `/invoke`, only without the outer wrapper. Example: > ```json > {"type":"ai","content":"Thinking...","tool_calls":[],"tool_call_id":null} > ``` #### How the Front-End Consumes the Stream (Technology Agnostic) The guidelines below apply to any stack (React, Vue, Svelte, plain HTML, etc.). The key requirement is to **read frames incrementally** and **render promptly**. 1. **Send the request in streaming mode** - Using `fetch`: open the `ReadableStream`; see the sample below. - If you rely on a native `EventSource` but need custom headers (POST + body), use a helper such as `eventsource-parser`. 2. **Parse frames** - Whenever you receive `\n\n`, you have a complete frame; strip the `data:` prefix and run `JSON.parse`. 3. **Render immediately** - Append the new segment to the chat window; if you implement a typing animation, throttle it according to chunk speed. 4. **Interrupt / cancel** - Hold an `AbortController` (or equivalent) and call `abort()` when the user presses “stop”. ##### Example: Full Streaming with Fetch ```javascript // Example: streaming request with fetch (ES6) const controller = new AbortController(); export async function sendStreamMessage(payload) { const response = await fetch("/value-canvas/stream", { method: "POST", headers: { "Content-Type": "application/json", "Accept": "text/event-stream" }, body: JSON.stringify(payload), signal: controller.signal }); if (!response.ok) throw new Error(`HTTP ${response.status}`); const reader = response.body.getReader(); const decoder = new TextDecoder("utf-8"); let buffer = ""; while (true) { const { value, done } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); let boundaryIndex; while ((boundaryIndex = buffer.indexOf("\n\n")) !== -1) { const frame = buffer.slice(0, boundaryIndex).trim(); buffer = buffer.slice(boundaryIndex + 2); if (frame.startsWith("data:")) { const jsonStr = frame.replace(/^data:\s*/, ""); if (jsonStr === "[DONE]") return; // stream finished const message = JSON.parse(jsonStr); handleIncomingChunk(message); // update your UI } } } } function handleIncomingChunk(msg) { // Append msg.content to chat window, handle tool_calls if needed console.log(msg.content); } // To cancel the stream elsewhere in your code: // controller.abort(); ``` ###### Interrupting / Canceling a Stream If the user clicks a “Stop” button or navigates away, call `controller.abort()` to close the connection and free server resources. ##### Best-Practice Tips - **Backend heartbeat** – if no output is sent for an extended period, emit `data: {"ping":true}\n\n` every 20-30 s to keep proxies alive. - **Error handling** – catch `response.status !== 200` and frames that contain an `error` field. - **Progressive merge** – keep a `currentMessage` state in the UI, append new characters to it, and move it to the history list once complete to prevent flicker. **3. Retrieving a Specific Thread's History (`/history`)** - **Endpoint**: `POST /history` - **Request Body (`ChatHistoryInput`)**: ```json { "thread_id": "thread-uuid-1" } ``` - **Success Response (200 OK) (`ChatHistory`)**: ```json { "messages": [ { "type": "human", "content": "Hi!" }, { "type": "ai", "content": "Hello! How can I help you?" } ] } ``` **4. Section State Notification (`/section_states/{agent_id}/{section_id}`)** - **Endpoint**: `GET /section_states/{agent_id}/{section_id}` - **Description**: Notify the agent that the specified section was updated and trigger a minimal sync run. Returns one AI message plus the latest section status and draft. - **Path Parameters**: - `agent_id`: The agent identifier (e.g., `value-canvas`) - `section_id`: The section identifier (e.g., `interview`) - **Query Parameters**: - `user_id`: User identifier (required) - `thread_id`: Thread identifier (required) - **Success Response (200 OK)**: ```json { "success": true } ``` ### <a id="agent-ui-collaboration-database-as-the-single-source-of-truth"></a>Agent-UI Collaboration: Database as the Single Source of Truth To ensure seamless collaboration between the user and the agent, the system employs a decoupled architecture where the database serves as the **single source of truth** for all generated content, especially for drafts displayed in a rich-text editor. This design avoids direct API calls from the agent to the front-end, leading to a more robust and scalable interaction model. The collaboration workflow is as follows: 1. **Agent Generates Content**: When the agent generates a draft or makes updates, it does **not** send this content directly back to the front-end via an API response. Instead, its sole action is to save the new data directly into the database using the backend APIs documented below (e.g., the save-section functionality). 2. **Front-end Listens for Changes**: The front-end's responsibility is to actively monitor the database for any changes to the content. This can be achieved using real-time subscription services (e.g., Supabase Realtime, Laravel Broadcasting) or by polling relevant endpoints. When a change is detected, the front-end fetches the latest content and re-renders the rich-text editor to display it. 3. **User Edits and Saves**: When the user edits the draft in the rich-text editor, their changes are saved back to the **same location in the database** via the application's backend. This ensures the database always holds the most current version of the content. 4. **Agent Stays Synchronized**: Before performing its next action, the agent will read the latest state from the database using the backend APIs (e.g., the get-context functionality). This allows it to work with the user's most recent edits, ensuring both parties are always synchronized. This database-centric approach guarantees data consistency and allows the front-end and the agent to operate independently, communicating asynchronously through the shared database state. ### <a id="future-enhancement-structured-ui-interactions"></a>Future Enhancement: Structured UI Interactions While the current implementation relies on text-based `message` exchanges, the agent's underlying design document outlines a more advanced interaction model using structured UI components like buttons and predefined choices. This functionality is planned as a future, non-core enhancement. The envisioned implementation will follow a front-end/agent contract: 1. **Agent-side**: The agent will send a response in a specific, agreed-upon format (e.g., a JSON object within the `ChatMessage`'s `custom_data` field) that describes the UI elements to be displayed. ```json { "type": "ai", "content": "What's your name? Or should I just call you Master of the Universe?", "custom_data": { "ui_elements": [ { "type": "button", "label": "Yes, that's right", "payload": "confirm_name" }, { "type": "button", "label": "Needs correction", "payload": "correct_name" } ] } } ``` 2. **Front-end-side**: The front-end will be responsible for parsing this structured response and dynamically rendering the appropriate UI components (e.g., buttons). When a user clicks a button, the front-end will send the associated `payload` back to the agent as a standard `message` to continue the conversation. This approach will allow for a richer, more guided user experience. However, for the initial version, all interactions are handled through the simple `message` field in the `UserInput` and `InvokeResponse` objects. --- ## 2. Agent's Internal Logic: Database Interaction ### <a id="collaboration-with-the-backend-team"></a>Collaboration with the Backend Team To support the agent's functionality, the agent directly integrates with Supabase as the backend database. The agent uses internal database tools and functions to read and write user data, which allows direct database access while maintaining proper data structure and validation. The collaboration involves the following areas: 1. **Database Credentials for Conversation State**: To enable conversation persistence, the agent's environment requires Supabase connection variables (URL, API keys). The underlying `LangGraph` framework will automatically create and manage its own tables for thread history, so the provided credentials should have permissions for these operations. 2. **Supabase Database Schema**: The agent requires specific database tables and functions to store and retrieve Value Canvas data. The database operations are handled internally by the agent through Supabase client libraries. ### <a id="api-endpoints-for-database-interaction"></a>Database Operations for Data Interaction #### <a id="key-identifiers-user_id-doc_id"></a>Key Identifiers (`user_id`, `doc_id`) To ensure data is correctly scoped, all API calls use two primary identifiers: - `user_id`: Identifies **who** is making the request. Crucial for authentication. - `doc_id`: The **unique key** to a specific document project. All of a document's configuration, sections, and content are tied to this ID in the database. The backend is responsible for managing all the business logic associated with this `doc_id`. **Example:** A user (`user_id: "joe"`) might have multiple, distinct document projects: - A Value Canvas for a client: `doc_id: "doc_123"` - A Mission Pitch for another project: `doc_id: "doc_456"` **1. Get Context for a Section** - **Description**: Retrieves all necessary data for the agent to work on a specific section of a document. The agent uses the Supabase database to fetch the correct set of prompts, rules, and existing draft content. - **Database Operation**: Supabase RPC call or table query via the agent's internal database tools - **Request Body Fields**: - `user_id` (string, required): The user's unique identifier. - `doc_id` (string, required): The unique identifier for the document project. - `section_id` (string, required): The identifier for the specific section (e.g., "icp", "pain_1"). - `canvas_data` (object, optional): Key-value pairs of already completed sections' data. Used for personalizing prompts. - **Success Response (200 OK) Fields**: - `section_id`, `status`, `system_prompt`, `validation_rules`, `required_fields`: Self-descriptive fields providing the agent with rules and instructions. - `draft` (object | null): The user's previously saved work for this section. - `draft.content` (object): A structured Tiptap JSON object for UI rendering. - `draft.plain_text` (string): A plain text version for the agent's processing. **2. Save Section Content** - **Description**: Saves the results of a specific section of a document. - **Database Operation**: Supabase table insert/update via the agent's internal database tools - **Request Body Fields**: - `user_id`, `doc_id`, `section_id`: Identifiers for user, document, and section. - `content` (object, required): The Tiptap JSON content to be saved. - `score` (integer, optional): User-provided satisfaction score. - `status` (string, required): New completion status (e.g., "done"). - **Success Response (200 OK)**: Returns the complete, updated state of the section. **3. Get All Sections Status** - **Description**: Retrieves a progress overview for a specific document. - **Database Operation**: Supabase table query via the agent's internal database tools - **Request Body Fields**: - `user_id`, `doc_id`: Identifiers for the user and document. - **Success Response (200 OK)**: An array of objects, one for each section defined for that document. **4. Export Value Canvas Data** - **Description**: Triggers the final export process for a completed document. - **Database Operation**: Supabase table query and data aggregation via the agent's internal database tools - **Request Body Fields**: - `user_id`, `doc_id`: Identifiers for the user and document. - `canvas_data` (object, required): A complete snapshot of all sections' data for generating the export. - **Success/Failure Responses**: As previously defined, indicating the result of the export process. --- ## <a id="ai-driven-dynamic-testing-framework"></a>AI-Driven Dynamic Testing Framework To address the complexity and variability of real-world conversations, we employ a dynamic testing model based on an **'AI Examiner'** to complement the limitations of traditional, script-based testing in simulating authentic user interactions. **Core Concept: Using one AI (the Examiner) to intelligently test our asset agents (e.g., the Value Canvas Agent).** ### <a id="core-capabilities-of-the-ai-examiner"></a>Core Capabilities of the 'AI Examiner' Our specialized **AI Examiner** possesses the following key capabilities: 1. **Rules Proficiency**: The Examiner is well-versed in the business agent's design documents and predefined instructions, serving as the gold standard for judging its compliance. For instance, it loads and comprehends the entire `value_canvas_agent_docs.md` file to master every ideal step of the process, from the initial interview to the final Value Canvas generation. 2. **Realistic Simulation**: The Examiner is assigned a complete and consistent user persona, enabling it to engage in natural, logical conversations as a real client would. 3. **Customized Edge Case Testing**: A key advantage of our framework is the ability to configure the AI Examiner to proactively simulate challenging scenarios. This tests the business agent's robustness against various edge cases. 4. **Real-Time Evaluation**: In every turn of the conversation, the Examiner assesses the business agent's performance against the predefined workflow in real-time and logs any deviations. ### <a id="traceable-test-reports"></a>Traceable Test Reports We believe in the value of transparency. Every dynamic test automatically generates a detailed, timestamped report in Markdown format. This report documents the **complete conversation** between the AI Examiner and the business agent, and includes a **flow compliance analysis** for each turn. Clients can review these reports at any time for a clear understanding of the agent's performance in various scenarios, ensuring the entire testing process is fully transparent and traceable. You can view a sample test report here: [Sample Test Report Demo](https://hackmd.io/@QycwKnwqQoWdQHCbGzCSdg/rk4nHNr_xg). ### <a id="framework-value-beyond-the-ideal-scenario"></a>Framework Value: Beyond the Ideal Scenario By simulating these complex, non-ideal scenarios, we can thoroughly inspect the business agent's **robustness** and **fault tolerance**. This ensures our AI not only excels in the 'happy path' but can also gracefully handle real-world unpredictability, delivering a truly reliable and intelligent service. Furthermore, this process creates an invaluable feedback loop for prompt engineering. By analyzing the detailed logs where the agent deviates or fails, we can make precise, data-driven adjustments to its underlying prompts. This allows us to systematically refine the agent's performance, ensuring continuous improvement and alignment with business goals. --- ## Appendix ### <a id="appendix-understanding-the-core-concepts-the-thread-and-thread_id"></a>Understanding the Core Concepts: The Thread and `thread_id` * **What a Thread Is**: A Thread is the complete record of one working session to create a Value Canvas. It's not the final project file itself, but rather the **entire conversation history and the agent's internal state** (like its memory and progress) throughout that creation process. Each Thread is identified by a unique `thread_id`. * **`user_id` vs. `thread_id`**: It's crucial to understand the role of both identifiers. * The `thread_id` identifies **which specific working session** to load. * The `user_id` identifies **who is making the request**. * The back-end uses the `user_id` for **security and authorization**, ensuring that a user can only access their own threads. Therefore, both identifiers are required in most API calls. * **Benefits of Multiple Threads for a User**: The system is designed for a user to have many threads. This is powerful because it allows them to: * **Manage different projects**: Create separate Value Canvasses for different products or clients. Each project is a unique thread. * **Restart without losing history**: Start a new thread to begin a project from scratch, while the old thread is preserved as a record.

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully