@mdgate/ai 0.1.0 → 0.4.0

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
@@ -8,16 +8,19 @@ import { image } from '@mdgate/image';
8
8
  import { ai } from '@mdgate/ai';
9
9
  import { pdf } from '@mdgate/pdf';
10
10
 
11
+ const vision = ai({
12
+ baseURL: 'https://api.example.com/v1',
13
+ apiKey: process.env.MY_KEY!,
14
+ model: 'my-vision-model',
15
+ });
16
+
11
17
  const convert = create([
12
18
  pdf(),
13
- image(
14
- ai({
15
- baseURL: 'https://api.example.com/v1',
16
- apiKey: process.env.MY_KEY!,
17
- model: 'my-vision-model',
18
- }).convertImage,
19
- ),
19
+ image(vision.convertImage),
20
20
  ]);
21
+
22
+ // Same chat/completions endpoint; audio is sent as a data URL.
23
+ await vision.convertAudio({ bytes, mime: 'audio/mpeg' });
21
24
  ```
22
25
 
23
- `baseURL` is the OpenAI-compatible API root (the same value the OpenAI SDK uses). Requests go to `{baseURL}/chat/completions`.
26
+ `baseURL` is the OpenAI-compatible API root (the same value the OpenAI SDK uses). Requests go to `{baseURL}/chat/completions`. Raster images include JPEG, PNG, WebP, GIF, TIFF, HEIC, and BMP. SVG is converted locally by `@mdgate/image`.
package/dist/ai.d.ts CHANGED
@@ -4,8 +4,14 @@ export type AiConfig = {
4
4
  apiKey: string;
5
5
  model: string;
6
6
  };
7
+ export type AudioInput = {
8
+ bytes: Uint8Array;
9
+ mime: string;
10
+ };
11
+ export type ConvertAudio = (audio: AudioInput) => Promise<string>;
7
12
  export type Ai = {
8
13
  convertImage: ConvertImage;
14
+ convertAudio: ConvertAudio;
9
15
  };
10
16
  /**
11
17
  * OpenAI-compatible vision (and later other media) helpers.
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export type { ConvertImage, ImageInput, ImageMime } from '@mdgate/image';
2
- export { type Ai, type AiConfig, ai } from './ai.js';
2
+ export { type Ai, type AiConfig, type AudioInput, ai, type ConvertAudio } from './ai.js';
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import{ConvertError as G}from"@mdgate/core";var W=new Set(["image/jpeg","image/png","image/webp"]),X=["You convert a document image into GitHub-Flavored Markdown.","Transcribe all readable text exactly, in reading order.","Preserve structure: headings, paragraphs, lists, tables, block quotes, and code.","Use GFM tables when the image shows a table.","Describe charts, diagrams, stamps, signatures, and photos only when they carry meaning.","Do not mention that the source is an image.","Do not wrap the entire answer in a markdown code fence.","Do not add a preamble or postscript."].join(" ");function Z(I){let A=N("baseURL",I.baseURL).replace(/\/+$/,""),D=N("apiKey",I.apiKey),j=N("model",I.model);return{convertImage:(z)=>$({baseURL:A,apiKey:D,model:j},z)}}async function $(I,A){if(!(A.bytes instanceof Uint8Array)||A.bytes.length===0)throw G.unsupported("image bytes are required");if(!W.has(A.mime))throw G.unsupported(`image mime: ${String(A.mime)}`);let D=A.page===void 0?"Extract the content of this document image as markdown.":`Extract page ${A.page} of this document as markdown.`,j;try{j=await fetch(`${I.baseURL}/chat/completions`,{method:"POST",headers:{authorization:`Bearer ${I.apiKey}`,"content-type":"application/json"},body:JSON.stringify({model:I.model,messages:[{role:"system",content:X},{role:"user",content:[{type:"text",text:D},{type:"image_url",image_url:{url:`data:${A.mime};base64,${_(A.bytes)}`}}]}]})})}catch(J){let V=J instanceof Error?J.message:String(J);throw G.io(Error(V))}let z=await j.text();if(!j.ok)throw G.io(Error(`HTTP ${j.status}: ${K(z)}`));let Q;try{Q=JSON.parse(z)}catch{throw G.io(Error("model response is not JSON"))}let H=Y(B(Q));if(H.length===0)throw G.io(Error("empty model response"));return H.endsWith(`
2
- `)?H:`${H}
3
- `}function N(I,A){if(typeof A!=="string"||A.trim()==="")throw G.unsupported(`${I} is required`);return A.trim()}function B(I){if(typeof I!=="object"||I===null||!("choices"in I))throw G.io(Error("model response is missing choices"));let A=I.choices;if(!Array.isArray(A)||A.length===0)throw G.io(Error("model response is missing choices"));let j=A[0]?.message?.content??A[0]?.text;if(typeof j==="string")return j;if(Array.isArray(j))return j.map((z)=>{if(typeof z==="string")return z;if(typeof z==="object"&&z!==null&&"text"in z)return typeof z.text==="string"?z.text:"";return""}).join("");throw G.io(Error("model response has no text content"))}function Y(I){let A=I.trim(),D=A.match(/^```(?:markdown|md)?\s*\n([\s\S]*?)\n```$/i);return(D?D[1]:A).trim()}function _(I){let A="",D=32768;for(let j=0;j<I.length;j+=D)A+=String.fromCharCode(...I.subarray(j,j+D));return btoa(A)}function K(I){let A=I.replace(/\s+/g," ").trim();return A.length>240?`${A.slice(0,237)}...`:A}export{Z as ai};
1
+ import{ConvertError as J}from"@mdgate/core";var G=new Set(["image/jpeg","image/png","image/webp","image/gif","image/tiff","image/heic","image/bmp"]),k=["You convert a document image into GitHub-Flavored Markdown.","Transcribe all readable text exactly, in reading order.","Preserve structure: headings, paragraphs, lists, tables, block quotes, and code.","Use GFM tables when the image shows a table.","Describe charts, diagrams, stamps, signatures, and photos only when they carry meaning.","Do not mention that the source is an image.","Do not wrap the entire answer in a markdown code fence.","Do not add a preamble or postscript."].join(" "),B=["You transcribe audio into GitHub-Flavored Markdown.","Transcribe all spoken words exactly, in listening order.","Preserve structure when the speaker dictates headings, lists, or paragraphs.","Do not mention that the source is audio.","Do not wrap the entire answer in a markdown code fence.","Do not add a preamble or postscript."].join(" ");function K(I){let A=V("baseURL",I.baseURL).replace(/\/+$/,""),z=V("apiKey",I.apiKey),H=V("model",I.model);return{convertImage:(j)=>Y({baseURL:A,apiKey:z,model:H},j),convertAudio:(j)=>q({baseURL:A,apiKey:z,model:H},j)}}async function Y(I,A){if(!(A.bytes instanceof Uint8Array)||A.bytes.length===0)throw J.unsupported("image bytes are required");if(!G.has(A.mime))throw J.unsupported(`image mime: ${String(A.mime)}`);let z=A.page===void 0?"Extract the content of this document image as markdown.":`Extract page ${A.page} of this document as markdown.`;return Z(I,k,z,`data:${A.mime};base64,${$(A.bytes)}`)}async function q(I,A){if(!(A.bytes instanceof Uint8Array)||A.bytes.length===0)throw J.unsupported("audio bytes are required");let z=typeof A.mime==="string"?A.mime.trim():"";if(z.length===0)throw J.unsupported("audio mime is required");return Z(I,B,"Transcribe this audio as markdown.",`data:${z};base64,${$(A.bytes)}`)}async function Z(I,A,z,H){let j;try{j=await fetch(`${I.baseURL}/chat/completions`,{method:"POST",headers:{authorization:`Bearer ${I.apiKey}`,"content-type":"application/json"},body:JSON.stringify({model:I.model,messages:[{role:"system",content:A},{role:"user",content:[{type:"text",text:z},{type:"image_url",image_url:{url:H}}]}]})})}catch(Q){let D=Q instanceof Error?Q.message:String(Q);throw J.io(Error(D))}let W=await j.text();if(!j.ok)throw J.io(Error(`HTTP ${j.status}: ${w(W)}`));let X;try{X=JSON.parse(W)}catch{throw J.io(Error("model response is not JSON"))}let N=O(L(X));if(N.length===0)throw J.io(Error("empty model response"));return N.endsWith(`
2
+ `)?N:`${N}
3
+ `}function V(I,A){if(typeof A!=="string"||A.trim()==="")throw J.unsupported(`${I} is required`);return A.trim()}function L(I){if(typeof I!=="object"||I===null||!("choices"in I))throw J.io(Error("model response is missing choices"));let A=I.choices;if(!Array.isArray(A)||A.length===0)throw J.io(Error("model response is missing choices"));let H=A[0]?.message?.content??A[0]?.text;if(typeof H==="string")return H;if(Array.isArray(H))return H.map((j)=>{if(typeof j==="string")return j;if(typeof j==="object"&&j!==null&&"text"in j)return typeof j.text==="string"?j.text:"";return""}).join("");throw J.io(Error("model response has no text content"))}function O(I){let A=I.trim(),z=A.match(/^```(?:markdown|md)?\s*\n([\s\S]*?)\n```$/i);return(z?z[1]:A).trim()}function $(I){let A="",z=32768;for(let H=0;H<I.length;H+=1)A+=String.fromCharCode(...I.subarray(H,H+z));return btoa(A)}function w(I){let A=I.replace(/\s+/g," ").trim();return A.length>240?`${A.slice(0,237)}...`:A}export{K as ai};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mdgate/ai",
3
- "version": "0.1.0",
4
- "description": "Optional AI helper for mdgate: ai({ baseURL, apiKey, model }).convertImage",
3
+ "version": "0.4.0",
4
+ "description": "Optional AI helper for mdgate: ai({ baseURL, apiKey, model }).convertImage / convertAudio",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "sideEffects": false,
@@ -22,8 +22,8 @@
22
22
  "prepublishOnly": "bun run build"
23
23
  },
24
24
  "dependencies": {
25
- "@mdgate/core": "0.1.2",
26
- "@mdgate/image": "0.1.0"
25
+ "@mdgate/core": "0.4.0",
26
+ "@mdgate/image": "0.4.0"
27
27
  },
28
28
  "publishConfig": {
29
29
  "access": "public"