File size: 3,285 Bytes
811d51e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import * as ort from "onnxruntime-web";
import "./style.css";

const TASK_LABELS = ["general_chat", "writing", "translation", "summarization", "research", "coding", "mathematics", "document_analysis", "high_stakes"];
const COMPLEXITY_LABELS = ["low", "medium", "high"];
const WORD_BINS = 1024;
const CHAR_BINS = 512;
const INPUT_SIZE = 1544;

function fnv1a(text) {
  let hash = 2166136261;
  for (let i = 0; i < text.length; i += 1) {
    hash ^= text.charCodeAt(i);
    hash = Math.imul(hash, 16777619);
  }
  return hash >>> 0;
}

function buildFeatures(text) {
  const source = String(text || "");
  const lower = source.toLocaleLowerCase();
  const values = new Float32Array(INPUT_SIZE);
  const words = lower.match(/[\p{L}\p{N}_]+/gu) || [];
  const grams = [...words];
  for (let i = 0; i + 1 < words.length; i += 1) grams.push(`${words[i]}_${words[i + 1]}`);
  for (const gram of grams) values[fnv1a(gram) % WORD_BINS] += 1;

  const compact = lower.replace(/\s+/g, " ");
  for (let i = 0; i + 2 < compact.length; i += 1) {
    values[WORD_BINS + (fnv1a(compact.slice(i, i + 3)) % CHAR_BINS)] += 0.25;
  }

  const sparseCount = WORD_BINS + CHAR_BINS;
  let norm = 0;
  for (let i = 0; i < sparseCount; i += 1) norm += values[i] * values[i];
  norm = Math.sqrt(norm);
  if (norm > 0) for (let i = 0; i < sparseCount; i += 1) values[i] /= norm;

  const base = sparseCount;
  values[base] = Math.min(source.length, 4000) / 4000;
  values[base + 1] = Math.min(words.length, 800) / 800;
  values[base + 2] = Math.min((source.match(/\?/g) || []).length, 10) / 10;
  values[base + 3] = Math.min((source.match(/\n/g) || []).length, 30) / 30;
  values[base + 4] = Math.min((source.match(/[{}[\]();=<>/]/g) || []).length, 100) / 100;
  values[base + 5] = /https?:\/\/|www\./i.test(source) ? 1 : 0;
  values[base + 6] = /\b(pdf|document|dokument|file|súbor|attachment|príloha)\b/i.test(source) ? 1 : 0;
  values[base + 7] = 1;
  return values;
}

function argMax(values) {
  let index = 0;
  for (let i = 1; i < values.length; i += 1) if (values[i] > values[index]) index = i;
  return index;
}

const result = document.querySelector("#result");
const modelUrl = new URL("../../../model.onnx", import.meta.url).href;
ort.env.wasm.numThreads = 1;
const session = await ort.InferenceSession.create(modelUrl, { executionProviders: ["wasm"] });
result.textContent = "Model ready.";

document.querySelector("#classify").addEventListener("click", async () => {
  const text = document.querySelector("#prompt").value;
  const features = buildFeatures(text);
  const outputs = await session.run({ features: new ort.Tensor("float32", features, [1, INPUT_SIZE]) });
  const task = Array.from(outputs.task_probabilities.data);
  const complexity = Array.from(outputs.complexity_probabilities.data);
  const taskIndex = argMax(task);
  const complexityIndex = argMax(complexity);
  result.textContent = JSON.stringify({
    task: { label: TASK_LABELS[taskIndex], confidence: task[taskIndex], probabilities: Object.fromEntries(TASK_LABELS.map((label, i) => [label, task[i]])) },
    complexity: { label: COMPLEXITY_LABELS[complexityIndex], confidence: complexity[complexityIndex], probabilities: Object.fromEntries(COMPLEXITY_LABELS.map((label, i) => [label, complexity[i]])) }
  }, null, 2);
});