File size: 6,020 Bytes
37db941
 
 
 
654165a
 
c056ab3
654165a
 
c056ab3
 
 
654165a
 
 
 
 
 
 
 
c056ab3
654165a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c056ab3
654165a
 
 
 
 
 
 
 
 
 
 
 
 
1f66b27
654165a
 
1f66b27
654165a
 
1f66b27
654165a
 
 
 
 
 
 
 
 
 
 
 
1f66b27
654165a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c056ab3
654165a
 
 
 
c056ab3
 
 
 
 
 
 
 
654165a
 
 
 
 
 
 
 
 
 
 
 
 
c056ab3
654165a
c056ab3
 
 
654165a
c056ab3
654165a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c056ab3
654165a
 
 
 
 
 
 
c056ab3
654165a
 
 
 
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/**
 * Tool calling 开关:开启后注入 tool schema(单轮);可选「多轮 & mock」做回灌。
 * 「Config tools」打开 toolConfigDialog。
 */
import { tr } from '../../shared/lang/i18n-lite';
import { lsReadBool, lsWriteBool } from '../../shared/storage/localStorageHelpers';
import { showEditableToolConfigDialog } from './toolConfigDialog';
import {
    cloneToolConfig,
    EMPTY_TOOL_CONFIG,
    readToolConfigFromLs,
    writeToolConfigToLs,
    type ToolConfig,
} from './toolConfig';

const MOUNT_ID = 'tool_calling_options_mount';

export type ToolCallingOptionsRowOptions = {
    enableToolCallingStorageKey: string;
    multiTurnStorageKey: string;
    toolConfigStorageKey: string;
    onStateChange: () => void;
};

/** 缓存 draft 还原;`multiTurnMockEnabled` 为 Chat draft 字段别名。 */
export type ToolCallingDraftRestore = {
    toolCallingEnabled?: boolean;
    multiTurnEnabled?: boolean;
    multiTurnMockEnabled?: boolean;
    toolConfig?: ToolConfig;
};

export type ToolCallingOptionsRowApi = {
    isToolCallingEnabled: () => boolean;
    isMultiTurnEnabled: () => boolean;
    getCurrentToolConfig: () => ToolConfig;
    isToolCallingConfigReady: () => boolean;
    restoreFromDraft: (draft: ToolCallingDraftRestore) => void;
    syncSubUi: () => void;
};

type ToolCallingRowElements = {
    enableInput: HTMLInputElement;
    multiTurnInput: HTMLInputElement;
    multiTurnRow: HTMLLabelElement;
    configBtn: HTMLButtonElement;
};

function mountToolCallingOptionsRow(mount: HTMLElement): ToolCallingRowElements {
    const row = document.createElement('div');
    row.className = 'options-row chat-enable-tool-calling-row';

    const group = document.createElement('span');
    group.className = 'options-group';

    const enableLabel = document.createElement('label');
    enableLabel.className = 'options-label';

    const enableInput = document.createElement('input');
    enableInput.type = 'checkbox';
    enableInput.id = 'tool_calling_enable';
    enableLabel.htmlFor = enableInput.id;

    const enableText = document.createElement('span');
    enableText.textContent = tr('Tool use');

    enableLabel.append(enableInput, enableText);

    const multiTurnRow = document.createElement('label');
    multiTurnRow.className = 'options-label';
    multiTurnRow.id = 'tool_calling_multi_turn_row';
    multiTurnRow.hidden = true;

    const multiTurnInput = document.createElement('input');
    multiTurnInput.type = 'checkbox';
    multiTurnInput.id = 'tool_calling_multi_turn';
    multiTurnInput.checked = true;
    multiTurnRow.htmlFor = multiTurnInput.id;

    const multiTurnText = document.createElement('span');
    multiTurnText.textContent = tr('Multi-turn');

    multiTurnRow.append(multiTurnInput, multiTurnText);

    const configBtn = document.createElement('button');
    configBtn.type = 'button';
    configBtn.id = 'tool_calling_config_btn';
    configBtn.className = 'text-action-btn';
    configBtn.hidden = true;
    configBtn.textContent = tr('Config tools');

    group.append(enableLabel, multiTurnRow, configBtn);
    row.append(group);
    mount.replaceChildren(row);

    return { enableInput, multiTurnInput, multiTurnRow, configBtn };
}

export function createToolCallingOptionsRow(
    options: ToolCallingOptionsRowOptions
): ToolCallingOptionsRowApi {
    const mount = document.getElementById(MOUNT_ID);
    if (!mount) {
        throw new Error(`createToolCallingOptionsRow: missing #${MOUNT_ID}`);
    }

    const { enableInput, multiTurnInput, multiTurnRow, configBtn } =
        mountToolCallingOptionsRow(mount);

    let currentToolConfig: ToolConfig = readToolConfigFromLs(options.toolConfigStorageKey);

    const isToolCallingEnabled = (): boolean => enableInput.checked;
    const isMultiTurnEnabled = (): boolean => multiTurnInput.checked;
    const getCurrentToolConfig = (): ToolConfig => currentToolConfig;
    const isToolCallingConfigReady = (): boolean =>
        !isToolCallingEnabled() || currentToolConfig.entries.length > 0;

    const persistToolConfig = (config: ToolConfig): void => {
        currentToolConfig = cloneToolConfig(config);
        writeToolConfigToLs(options.toolConfigStorageKey, currentToolConfig);
        options.onStateChange();
    };

    const syncSubUi = (): void => {
        const on = isToolCallingEnabled();
        configBtn.hidden = !on;
        multiTurnRow.hidden = !on;
    };

    const restoreFromDraft = (draft: ToolCallingDraftRestore): void => {
        enableInput.checked = draft.toolCallingEnabled ?? false;
        lsWriteBool(options.enableToolCallingStorageKey, enableInput.checked);
        const multiTurn = draft.multiTurnEnabled ?? draft.multiTurnMockEnabled ?? true;
        multiTurnInput.checked = multiTurn;
        lsWriteBool(options.multiTurnStorageKey, multiTurnInput.checked);
        const next = draft.toolConfig
            ? cloneToolConfig(draft.toolConfig)
            : cloneToolConfig(EMPTY_TOOL_CONFIG);
        currentToolConfig = next;
        writeToolConfigToLs(options.toolConfigStorageKey, next);
        syncSubUi();
        options.onStateChange();
    };

    enableInput.checked = lsReadBool(options.enableToolCallingStorageKey, false);
    enableInput.addEventListener('change', () => {
        lsWriteBool(options.enableToolCallingStorageKey, enableInput.checked);
        syncSubUi();
        options.onStateChange();
    });

    multiTurnInput.checked = lsReadBool(options.multiTurnStorageKey, true);
    multiTurnInput.addEventListener('change', () => {
        lsWriteBool(options.multiTurnStorageKey, multiTurnInput.checked);
        options.onStateChange();
    });

    configBtn.addEventListener('click', () => {
        showEditableToolConfigDialog(getCurrentToolConfig(), persistToolConfig);
    });
    syncSubUi();

    return {
        isToolCallingEnabled,
        isMultiTurnEnabled,
        getCurrentToolConfig,
        isToolCallingConfigReady,
        restoreFromDraft,
        syncSubUi,
    };
}