File size: 3,709 Bytes
811916b
 
37fed31
 
 
 
 
 
 
0282567
811916b
0282567
638c2d6
2a0250a
 
0282567
 
811916b
5d0136a
37fed31
 
 
 
4dc14dc
 
37fed31
5d0136a
 
 
 
 
37fed31
 
 
c86c048
4dc14dc
 
37fed31
811916b
 
 
 
 
eeb4c01
811916b
 
 
 
 
 
 
 
 
638c2d6
 
 
 
 
 
 
 
2d06121
c86c048
 
 
 
4dc14dc
c86c048
 
 
 
0282567
 
 
 
2d06121
c86c048
 
 
5e0e4e8
c86c048
 
 
 
638c2d6
 
 
 
 
 
 
4dc14dc
5e0e4e8
4dc14dc
638c2d6
 
 
 
 
 
 
 
 
 
 
 
 
45a40b2
 
 
 
638c2d6
 
 
4dc14dc
811916b
c86c048
638c2d6
 
 
 
 
bca7b90
811916b
 
c86c048
 
811916b
 
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// @ts-check

import {
  defaultValueCtx,
  Editor,
  editorViewCtx,
  editorViewOptionsCtx,
  rootCtx
} from '@milkdown/core';
import { Crepe } from '@milkdown/crepe';
import { commonmark } from '@milkdown/kit/preset/commonmark';

import { createModelSlashPlugin } from './model-slash';
import { outputMessage } from './output-message';

import "@milkdown/crepe/theme/common/style.css";
import "@milkdown/crepe/theme/frame.css";

/**
 * @typedef {{
 *  chatLog: HTMLElement,
 *  chatInput: HTMLElement,
 *  inputPlugins?: any[],
 *  onSlashCommand?: (command: string) => void | boolean | Promise<void | boolean>,
 *  worker?: any
 * }} InitMilkdownOptions
 */

/**
 * @param {InitMilkdownOptions} options
 */
export async function initMilkdown({
  chatLog,
  chatInput,
  inputPlugins = [], // Keep for backward compatibility but not used for Crepe
  onSlashCommand,
  worker
}) {
  if (chatLog) chatLog.textContent = 'Loading Milkdown...';

  if (chatLog) chatLog.innerHTML = '';
  if (chatInput) chatInput.innerHTML = '';


  // Create read-only editor in .chat-log
  const chatLogEditor = await Editor.make()
    .config((ctx) => {
      ctx.set(rootCtx, chatLog);
      ctx.set(editorViewOptionsCtx, { editable: () => false });
    })
    .use(commonmark)
    .create();

  let availableModels = [];

  // Create the model slash plugin configuration
  const modelSlashSetup = createModelSlashPlugin({
    getModels: () => availableModels,
    onSlashCommand: onSlashCommand
  });

  // Create editable Crepe editor in .chat-input (without BlockEdit)
  const crepeInput = new Crepe({
    root: chatInput,
    defaultValue: '',
    features: {
      [Crepe.Feature.BlockEdit]: false,
      [Crepe.Feature.Placeholder]: true,
      [Crepe.Feature.Cursor]: true,
      [Crepe.Feature.ListItem]: true,
      [Crepe.Feature.CodeMirror]: true,
      [Crepe.Feature.ImageBlock]: true,
      [Crepe.Feature.Table]: true,
      [Crepe.Feature.Latex]: true,
      [Crepe.Feature.Toolbar]: true,
      [Crepe.Feature.LinkTooltip]: true,
    },
    featureConfigs: {
      [Crepe.Feature.Placeholder]: {
        text: 'Prompt or /model...',
        mode: 'block'
      }
    }
  });

  // Create input editor with model slash plugin
  // Apply the model slash plugin configuration before creating the editor
  const chatInputEditor = await crepeInput
    .editor.config(modelSlashSetup.config)
    .use(modelSlashSetup.plugin)
    .create();

  // Fetch models in background and add model slash plugin when ready
  (async () => {
    const { id, promise, cancel } = await worker.listChatModels({}, undefined);
    const out = await promise;

    // Normalize possible response shapes
    let entries = [];
    if (Array.isArray(out)) entries = out;
    else if (out && Array.isArray(out.models)) entries = out.models;
    else if (out && Array.isArray(out.results)) entries = out.results;
    else entries = [];

    availableModels = entries.map(e => ({
      id: e.id || e.modelId || '',
      name: e.name || (e.id || e.modelId || '').split('/').pop(),
      size:
        ((e.size_hint || '') + ' ' +
        (e.info?.params || '')).trim(),
      requiresAuth: e.classification === 'auth-protected' || e.requiresAuth,
    }));

    outputMessage('Models discovered: **' + availableModels.length + '**');
  })();

  // Auto-focus the Crepe input editor when ready
  // Crepe exposes the underlying milkdown editor through .editor property
  crepeInput.editor.action((ctx) => {
    const view = ctx.get(editorViewCtx);
    if (view && typeof view.focus === 'function') view.focus();
  });

  return {
    chatLogEditor,
    chatInputEditor,
    crepeInput // Return the crepe instance for additional control
  };
}