Bruce MacDonald il y a 6 mois
Parent
commit
58d3d37041
3 fichiers modifiés avec 234 ajouts et 64 suppressions
  1. 157 10
      package-lock.json
  2. 3 1
      package.json
  3. 74 53
      src/index.ts

+ 157 - 10
package-lock.json

@@ -14,12 +14,14 @@
       "devDependencies": {
         "@swc/core": "^1.3.14",
         "@types/glob": "^8.1.0",
+        "@types/node": "^22.10.5",
         "@types/whatwg-fetch": "^0.0.33",
         "@typescript-eslint/eslint-plugin": "^5.42.1",
         "@typescript-eslint/parser": "^5.42.1",
         "eslint": "^8.29.0",
         "prettier": "^3.2.4",
-        "typescript": "^5.3.2",
+        "ts-node": "^10.9.2",
+        "typescript": "^5.7.3",
         "unbuild": "^2.0.0",
         "vitest": "^2.1.6"
       }
@@ -485,6 +487,28 @@
         "node": ">=6.9.0"
       }
     },
+    "node_modules/@cspotcode/source-map-support": {
+      "version": "0.8.1",
+      "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+      "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/trace-mapping": "0.3.9"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.9",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+      "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.0.3",
+        "@jridgewell/sourcemap-codec": "^1.4.10"
+      }
+    },
     "node_modules/@esbuild/aix-ppc64": {
       "version": "0.19.12",
       "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
@@ -1514,6 +1538,30 @@
         "node": ">=10.13.0"
       }
     },
+    "node_modules/@tsconfig/node10": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
+      "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
+      "dev": true
+    },
+    "node_modules/@tsconfig/node12": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+      "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+      "dev": true
+    },
+    "node_modules/@tsconfig/node14": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+      "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+      "dev": true
+    },
+    "node_modules/@tsconfig/node16": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
+      "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+      "dev": true
+    },
     "node_modules/@types/estree": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
@@ -1543,11 +1591,12 @@
       "dev": true
     },
     "node_modules/@types/node": {
-      "version": "20.11.0",
+      "version": "22.10.5",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz",
+      "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==",
       "dev": true,
-      "license": "MIT",
       "dependencies": {
-        "undici-types": "~5.26.4"
+        "undici-types": "~6.20.0"
       }
     },
     "node_modules/@types/resolve": {
@@ -1928,6 +1977,18 @@
         "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
       }
     },
+    "node_modules/acorn-walk": {
+      "version": "8.3.4",
+      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+      "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+      "dev": true,
+      "dependencies": {
+        "acorn": "^8.11.0"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/ajv": {
       "version": "6.12.6",
       "dev": true,
@@ -1951,6 +2012,12 @@
         "node": ">=8"
       }
     },
+    "node_modules/arg": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+      "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+      "dev": true
+    },
     "node_modules/argparse": {
       "version": "2.0.1",
       "dev": true,
@@ -2218,6 +2285,12 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/create-require": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+      "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+      "dev": true
+    },
     "node_modules/cross-spawn": {
       "version": "7.0.3",
       "dev": true,
@@ -2453,6 +2526,15 @@
       "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
       "dev": true
     },
+    "node_modules/diff": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+      "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.3.1"
+      }
+    },
     "node_modules/dir-glob": {
       "version": "3.0.1",
       "dev": true,
@@ -3301,6 +3383,12 @@
         "@jridgewell/sourcemap-codec": "^1.5.0"
       }
     },
+    "node_modules/make-error": {
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+      "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+      "dev": true
+    },
     "node_modules/mdn-data": {
       "version": "2.0.30",
       "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.30.tgz",
@@ -4509,6 +4597,49 @@
         "node": ">=8.0"
       }
     },
+    "node_modules/ts-node": {
+      "version": "10.9.2",
+      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
+      "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
+      "dev": true,
+      "dependencies": {
+        "@cspotcode/source-map-support": "^0.8.0",
+        "@tsconfig/node10": "^1.0.7",
+        "@tsconfig/node12": "^1.0.7",
+        "@tsconfig/node14": "^1.0.0",
+        "@tsconfig/node16": "^1.0.2",
+        "acorn": "^8.4.1",
+        "acorn-walk": "^8.1.1",
+        "arg": "^4.1.0",
+        "create-require": "^1.1.0",
+        "diff": "^4.0.1",
+        "make-error": "^1.1.1",
+        "v8-compile-cache-lib": "^3.0.1",
+        "yn": "3.1.1"
+      },
+      "bin": {
+        "ts-node": "dist/bin.js",
+        "ts-node-cwd": "dist/bin-cwd.js",
+        "ts-node-esm": "dist/bin-esm.js",
+        "ts-node-script": "dist/bin-script.js",
+        "ts-node-transpile-only": "dist/bin-transpile.js",
+        "ts-script": "dist/bin-script-deprecated.js"
+      },
+      "peerDependencies": {
+        "@swc/core": ">=1.2.50",
+        "@swc/wasm": ">=1.2.50",
+        "@types/node": "*",
+        "typescript": ">=2.7"
+      },
+      "peerDependenciesMeta": {
+        "@swc/core": {
+          "optional": true
+        },
+        "@swc/wasm": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/tslib": {
       "version": "1.14.1",
       "dev": true,
@@ -4551,9 +4682,9 @@
       }
     },
     "node_modules/typescript": {
-      "version": "5.3.3",
-      "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.3.3.tgz",
-      "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
+      "version": "5.7.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
+      "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
       "dev": true,
       "bin": {
         "tsc": "bin/tsc",
@@ -4647,9 +4778,10 @@
       }
     },
     "node_modules/undici-types": {
-      "version": "5.26.5",
-      "dev": true,
-      "license": "MIT"
+      "version": "6.20.0",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
+      "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
+      "dev": true
     },
     "node_modules/universalify": {
       "version": "2.0.1",
@@ -4721,6 +4853,12 @@
       "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
       "dev": true
     },
+    "node_modules/v8-compile-cache-lib": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+      "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+      "dev": true
+    },
     "node_modules/vite": {
       "version": "5.4.11",
       "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
@@ -5394,6 +5532,15 @@
       "dev": true,
       "license": "ISC"
     },
+    "node_modules/yn": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+      "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/yocto-queue": {
       "version": "0.1.0",
       "dev": true,

+ 3 - 1
package.json

@@ -35,12 +35,14 @@
   "devDependencies": {
     "@swc/core": "^1.3.14",
     "@types/glob": "^8.1.0",
+    "@types/node": "^22.10.5",
     "@types/whatwg-fetch": "^0.0.33",
     "@typescript-eslint/eslint-plugin": "^5.42.1",
     "@typescript-eslint/parser": "^5.42.1",
     "eslint": "^8.29.0",
     "prettier": "^3.2.4",
-    "typescript": "^5.3.2",
+    "ts-node": "^10.9.2",
+    "typescript": "^5.7.3",
     "unbuild": "^2.0.0",
     "vitest": "^2.1.6"
   },

+ 74 - 53
src/index.ts

@@ -4,11 +4,11 @@ import { AbortableAsyncIterator } from './utils.js'
 import fs, { createReadStream, promises } from 'fs'
 import { join, resolve, basename } from 'path'
 import { createHash } from 'crypto'
-import { homedir } from 'os'
 import glob from 'glob'
 import { Ollama as OllamaBrowser } from './browser.js'
-
 import type { CreateRequest, ProgressResponse } from './interfaces.js'
+import { stat } from 'fs/promises';
+import { pipeline } from 'stream/promises';
 
 export class Ollama extends OllamaBrowser {
   async encodeImage(image: Uint8Array | Buffer | string): Promise<string> {
@@ -44,55 +44,75 @@ export class Ollama extends OllamaBrowser {
     }
   }
 
-  private async createBlob(path: string): Promise<string> {
-    if (typeof ReadableStream === 'undefined') {
-      // Not all fetch implementations support streaming
-      // TODO: support non-streaming uploads
-      throw new Error('Streaming uploads are not supported in this environment.')
-    }
-
-
-    const hash = createHash('sha256')
-    const stream = createReadStream(path)
-    for await (const chunk of stream) {
-      hash.update(chunk)
-    }
-    const digest = `sha256:${hash.digest('hex')}`
-
+  async createBlob(path: string) {
     try {
-      await utils.head(this.fetch, `${this.config.host}/api/blobs/${digest}`)
-    } catch (e) {
-      if (e instanceof Error && e.message.includes('404')) {
-        const fileStream = createReadStream(path)
-        // Create a new readable stream for the fetch request
-        const readableStream = new ReadableStream({
-          start(controller) {
-            fileStream.on('data', (chunk) => {
-              controller.enqueue(chunk) // Enqueue the chunk directly
-            })
-
-            fileStream.on('end', () => {
-              controller.close() // Close the stream when the file ends
-            })
-
-            fileStream.on('error', (err) => {
-              controller.error(err) // Propagate errors to the stream
-            })
-          },
-        })
-
-        await utils.post(
-          this.fetch,
-          `${this.config.host}/api/blobs/${digest}`,
-          readableStream,
-        )
-      } else {
-        throw e
-      }
+        console.log(`[DEBUG] Starting blob creation for file: ${path}`);
+        
+        // Get file stats
+        const fileStats = await stat(path);
+        console.log(`[DEBUG] File size: ${fileStats.size} bytes`);
+
+        // First pass: calculate hash
+        const hash = createHash('sha256');
+        const hashStream = fs.createReadStream(path);
+        
+        for await (const chunk of hashStream) {
+            hash.update(chunk);
+        }
+        
+        const digest = `sha256:${hash.digest('hex')}`;
+        console.log(`[DEBUG] Digest calculated: ${digest}`);
+        console.log(`[DEBUG] Starting upload...`);
+
+        // Second pass: upload file
+        const uploadStream = fs.createReadStream(path);
+        let bytesUploaded = 0;
+
+        // Convert Node.js ReadStream to Web ReadableStream
+        const webStream = new ReadableStream({
+          async start(controller) {
+              uploadStream.on('data', (chunk) => {
+                  bytesUploaded += chunk.length;
+                  const percentage = Math.round((bytesUploaded / fileStats.size) * 100);
+                  console.log(percentage);
+                  controller.enqueue(chunk);
+              });
+
+              uploadStream.on('end', () => {
+                  controller.close();
+              });
+
+              uploadStream.on('error', (error) => {
+                  controller.error(error);
+              });
+          }
+      });
+
+        const response = await fetch(`http://127.0.0.1:11435/api/blobs/${digest}`, {
+            method: 'POST',
+            // In Node.js, we can directly pass the readable stream as the body
+            body: webStream,
+            headers: {
+                'Content-Type': 'application/octet-stream',
+                'Accept': 'application/json',
+                'Content-Length': fileStats.size.toString(),
+                'User-Agent': `node-client/1.0`
+            },
+        });
+
+        if (!response.ok) {
+            const errorData = await response.text();
+            throw new Error(`Upload failed: ${errorData}`);
+        }
+
+        console.log(`[DEBUG] Upload completed successfully`);
+        return { digest, size: fileStats.size };
+
+    } catch (error) {
+        console.error('[ERROR] Operation failed:', error);
+        throw error;
     }
-
-    return digest
-  }
+}
   
   async findModelFiles(path: string): Promise<string[]> {
     const files: string[] = []
@@ -161,10 +181,11 @@ export class Ollama extends OllamaBrowser {
         files = [from]
       }
   
-      for (const file of files) {
-        const digest = await this.createBlob(file)
-        fileMap[basename(file)] = digest
-      }
+    console.log(files)
+    for (const file of files) {
+      await this.createBlob(file)
+      // fileMap[basename(file)] = digest
+    }
   
     return fileMap
   }