Skip to content
Open
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b836ae5
feat: refactor ollama app to separate out ollama api calls into a sep…
klown Mar 28, 2025
8bf9fb4
feat: move `ollamaApi.ts` to `src/client` for use by adpative-palette
klown Mar 28, 2025
2fc8ec1
feat: add types for the typescript version of the ollama api
klown Apr 1, 2025
4369d09
Merge branch 'feat/standard-bliss-board' into feat/integrate-ollama-a…
klown Apr 1, 2025
a118ce6
feat: first attempt at component for activating sentence completion
klown Apr 1, 2025
c1e2ddc
feat: add dynamic sentence completions palette
klown Apr 4, 2025
63058c7
feat: actually commit the `SentenceCompletionsPalette` component
klown Apr 7, 2025
aa726b8
feat: add dialog for changing the system prompt for querying ollama
klown Apr 7, 2025
2faf0f0
feat: improve dialog for system prompt and add dialog for svg string …
klown Apr 9, 2025
94fb28f
fix: insure that `stream` constant is actually a boolean
klown Apr 9, 2025
a30918c
feat: properly update the prompt dialog when a new prompt is added
klown Apr 11, 2025
fb33aa3
feat: move styles of dialogs to their own .scss files
klown Apr 11, 2025
b670284
feat: add some unit tests for the new prompt dialog
klown Apr 14, 2025
14227d4
chore: use constants for form control ids and values
klown Apr 14, 2025
fc50c27
fix: indent typo
klown Apr 15, 2025
ef019df
feat: add tests for `ActionSvgEntryField` component
klown Apr 15, 2025
1e6ac7f
feat: add a way to follow the typing restrictions of ollama.ts when t…
klown Apr 15, 2025
64be8a7
feat: add tests for ollama api
klown Apr 16, 2025
e0b7d2d
Merge branch 'feat/standard-bliss-board' into feat/integrate-ollama-a…
klown Jun 4, 2025
07d5318
Merge branch 'main' into feat/integrate-ollama-api-with-palette
klown Jun 24, 2025
4e7d1d0
Merge branch 'main' into feat/integrate-ollama-api-with-palette
klown Jun 26, 2025
751372d
Merge branch 'main' into feat/integrate-ollama-api-with-palette
klown Oct 7, 2025
ac981d7
fix: improve contrast of text on sentence completion buttons
klown Oct 7, 2025
c44ebc6
Merge branch 'main' into feat/integrate-ollama-api-with-palette
klown Dec 15, 2025
1e10fe5
fix: add missing ","
klown Dec 15, 2025
c621a03
fix: remove svg entry UI and related code, and duplicate go-back button
klown Dec 15, 2025
8df804e
chore: minor fixes to align with ISAAC demo branch
klown Jan 9, 2026
f0b8d93
feat: copy main display differences from ISAAC demo branch
klown Jan 12, 2026
63427b1
fix; passing a boolean value to a preact component using ${...}
klown Jan 20, 2026
aab3678
fix: comments in the code
klown Jan 20, 2026
14aee33
feat: added tests for `ActionTextCell` and `SentenceCompletionsPalette`
klown Jan 21, 2026
bfd5478
fix: typo where "R" was errorneously used for "$" in an html template…
klown Jan 21, 2026
2a4b6c6
feat: use states for tracking prompt dialog ui
klown Jan 28, 2026
b5a59be
fix: remove unnecessary call to the palette store to remove the sente…
klown Jan 28, 2026
76ed06e
fix: styles of the telegraphic completions buttons
klown Jan 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 10 additions & 50 deletions apps/ollama/ollama.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 Inclusive Design Research Centre, OCAD University
* Copyright 2024-2025 Inclusive Design Research Centre, OCAD University
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copyright can be up to 2026 now. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RIght.

* All rights reserved.
*
* Licensed under the New BSD license. You may not use this file except in
Expand All @@ -8,31 +8,13 @@
* You may obtain a copy of the License at
* https://github.com/inclusive-design/adaptive-palette/blob/main/LICENSE
*/

import ollama from "ollama/browser";
import { getModelNames, queryChat } from "../../src/client/ollamaApi";

// Default name of model used (aka, none). Set in setSelectedModel() handler.
let nameOfModelToUse = "";
const USE_ALL_MODELS = "useAllModels";
const NO_AVAILABLE_MODELS = "No Available Models";

/**
* Retrieve a list of LLMs available from the service
* @return {Array} - Array of the names of the available models.
*/
async function getModelNames () {
let modelNames = [];
try {
const list = await ollama.list();
list.models.forEach( (model) => {
modelNames.push(model.name);
});
}
catch (error) {
console.debug(error);
}
return modelNames;
}
const STREAM_RESPONSE = true;

/**
* Initialize the model <select> element's options:
Expand Down Expand Up @@ -129,33 +111,6 @@ function askClicked(event) {
}
}

/**
* Function for passing the chat prompt to the ollama service.
* @param {String} query - The prompt string to query the service with.
* @param {String} modelName - The name of the LLM to query.
* @return {Object} - The response from the service.
*/
async function queryChat (query, modelName) {
let messageArray = [];
const textFromSystemPrompt = document.getElementById("systemPrompt").value.trim();
if (textFromSystemPrompt !== "") {
messageArray.push({
role: "system",
content: textFromSystemPrompt
});
console.debug(`queryChat(): system prompt is: "${textFromSystemPrompt}")`);
}
messageArray.push({ role: "user", content: query });
const response = await ollama.chat({
model: modelName,
messages: messageArray,
raw: true,
stream: true,
keep_alive: 15
});
return response;
}

/**
* Handle the "Ask" button press by contructing a prompt from the text area
* input and which of the "Ask" buttons was pressed. Query the service with
Expand All @@ -175,7 +130,11 @@ async function executeAsk (addSingleToPrompt) {
queryEachModel(promptText);
}
else if (modelInSelect !== NO_AVAILABLE_MODELS) {
const response = await queryChat(promptText, modelInSelect);
const textFromSystemPrompt = document.getElementById("systemPrompt").value.trim();
console.debug(`executeAsk(): promptText: ${promptText}`);
console.debug(` textFromSystemPrompt: ${textFromSystemPrompt}`);
console.debug(` modelInSelect: ${modelInSelect}`);
const response = await queryChat(promptText, modelInSelect, STREAM_RESPONSE, textFromSystemPrompt);
outputResult(response, document.getElementById("ollamaOutput"), "No Result");
}
else {
Expand Down Expand Up @@ -237,7 +196,8 @@ async function queryEachModel (promptText) {
const names = await getModelNames();
let count = 0;
names.forEach ((modelName) => {
queryChat(promptText, modelName)
const textFromSystemPrompt = document.getElementById("systemPrompt").value.trim();
queryChat(promptText, modelName, STREAM_RESPONSE, textFromSystemPrompt)
.then(async (response) => {
const outputEl = createOutputSection(modelName);
await outputResult(response, outputEl, "No Result");
Expand Down
19 changes: 15 additions & 4 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,32 @@
<title>Adaptive Palette</title>
</head>
<body>
<!--
Expandable form for the LLM prompts.
-->
<details style="margin-left: 0.5em; display:inline-grid;">
<summary style="color:white">Prompt Dialog</summary>
<div id="llm_prompt" style="display:inline-grid;"></div>
</details>
</div>
<div id="askForLlmSuggestions" style="margin-left: 0.5em;"></div>
<div id="llm_suggestions" style="color: white; background-color: black; margin-left:0.5em"></div>
<div id="inputArea">
<div id="input_palette"></div>
</div>
<div class="oneRow">
<div id="backup_palette"></div>
<div>
<div id="indicators"></div>
</div>
<div>
<div id="modifiers"></div>
</div>
<div>&nbsp;</div>
<div id="mainPaletteDisplayArea">
<div class="oneRow">
<div id="backup_palette"></div>
<div id="mainPaletteDisplayArea">
<script type="module">
import "./src/client/index.js";
</script>
</div>
<div>&nbsp;</div>
</body>
</html>
21 changes: 21 additions & 0 deletions src/client/ActionTextCell.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2025 Inclusive Design Research Centre, OCAD University
* All rights reserved.
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* You may obtain a copy of the License at
* https://github.com/inclusive-design/adaptive-palette/blob/main/LICENSE
*/

.actionTextCell {
border: 2px solid black;
color: black;
background-color: white;
margin-left: 1rem;
padding-inline: 0.5rem;
text-align: left;
font-size: 1rem;
width: 50%;
}
39 changes: 39 additions & 0 deletions src/client/ActionTextCell.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2023-2024 Inclusive Design Research Centre, OCAD University
* All rights reserved.
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* You may obtain a copy of the License at
* https://github.com/inclusive-design/adaptive-palette/blob/main/LICENSE
*/

import { VNode } from "preact";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There isn't a test for this component. Is it intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No there should be a test. It's possible I misplaced it. If not, I will write one.

import { html } from "htm/preact";

import { generateGridStyle, speak } from "./GlobalUtils";
import { BlissSymbolInfoType, LayoutInfoType } from "./index.d";
import "./ActionTextCell.scss";

type ActionTextCellPropsType = {
id: string,
options: BlissSymbolInfoType & LayoutInfoType
};

export function ActionTextCell (props: ActionTextCellPropsType): VNode {
const {
columnStart, columnSpan, rowStart, rowSpan, label
} = props.options;
const gridStyles = generateGridStyle(columnStart, columnSpan, rowStart, rowSpan);

const sentenceClicked = () => {
speak(label.replace(/^[0-9]+./,"").trim()); // remove any leading "1. "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably improve the code comment to: Remove a leading list number, e.g., "1. "

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, comment improved.

};

return html`
<button id="${props.id}" class="actionTextCell" style="${gridStyles}" onClick=${sentenceClicked}>
${label}
</button>
`;
}
16 changes: 16 additions & 0 deletions src/client/CommandTelegraphicCompletions.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright 2025 Inclusive Design Research Centre, OCAD University
* All rights reserved.
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* You may obtain a copy of the License at
* https://github.com/inclusive-design/adaptive-palette/blob/main/LICENSE
*/

.commandTelegraphicCompletions {
button {
font-size: 1rem;
}
}
34 changes: 34 additions & 0 deletions src/client/CommandTelegraphicCompletions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2025 Inclusive Design Research Centre, OCAD University
* All rights reserved.
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* You may obtain a copy of the License at
* https://github.com/inclusive-design/adaptive-palette/blob/main/LICENSE
*/

import { render, screen } from "@testing-library/preact";
import "@testing-library/jest-dom";
import { html } from "htm/preact";

import {
CommandTelegraphicCompletions, TELEGRPAHIC_BUTTON_LABEL, CANCEL_BUTTON_LABEL
} from "./CommandTelegraphicCompletions";

describe("CommandTelegraphicCompletions component", (): void => {

test("Render telegraphic buttons", async(): Promise<void> => {
render(html`
<${CommandTelegraphicCompletions} model="llama3.1:latest" stream=false />`
);
const triggerButton = await screen.findByText(TELEGRPAHIC_BUTTON_LABEL);
expect(triggerButton).toBeInTheDocument();
expect(triggerButton).toBeInstanceOf(HTMLButtonElement);

const cancelButton = await screen.findByText(CANCEL_BUTTON_LABEL);
expect(cancelButton).toBeInTheDocument();
expect(cancelButton).toBeInstanceOf(HTMLButtonElement);
});
});
76 changes: 76 additions & 0 deletions src/client/CommandTelegraphicCompletions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright 2025 Inclusive Design Research Centre, OCAD University
* All rights reserved.
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* You may obtain a copy of the License at
* https://github.com/inclusive-design/adaptive-palette/blob/main/LICENSE
*/

import { VNode } from "preact";
import { html } from "htm/preact";

import { changeEncodingContents, sentenceCompletionsSignal } from "./GlobalData";
import { TEXTAREA_ID } from "./DialogPromptEntries";
import { queryChat } from "./ollamaApi";

import "./CommandTelegraphicCompletions.scss";

export const TELEGRPAHIC_BUTTON_LABEL = "Telegraphic Completions";
export const CANCEL_BUTTON_LABEL = "Cancel";

type CommandTelegraphicCompletionsProps = {
id: string,
model: string,
// This should be a boolean type, but for Preact/html the type of the value is
// string. It should be "true" or "false".
stream: ("true" | "false")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems Preact has native boolean support by directly using stream: boolean, and the use of streamAsBoolean can be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making me revisit this @cindyli. While I couldn't solve it back in the spring, I finally found the solution.

While it was always possible to declare the "stream" property as "boolean" (stream: boolean), it doesn't matter in some sense. If the render call is done like so:

render( html`<${CommandTelegraphicCompletions} model="llama3.1:latest" stream=false />`, ...

Then typeof props.stream returns 'string', not 'boolean', even though props.stream is declared a boolean. That was mystifying to me, frankly. However, the correct way to render the component is:

render( html`<${CommandTelegraphicCompletions} model="llama3.1:latest" stream=${false} />`, ...

In that case typeof props.stream is 'boolean', and the conditional to change the string to an actual boolean using streamAsBoolean can be removed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Smart fix. Thanks for sharing. Now I know. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, there are so many little fiddly gotchas with Preact, html, TypeScript, and jest...

};

export function CommandTelegraphicCompletions (props: CommandTelegraphicCompletionsProps): VNode {

const { model, stream } = props;
const streamAsBoolean = ( stream === "true" ? true : false );

// Handler for getting completions from ollama and into the
// `sentenceCompletionsSignal` signal's value.
const getTelegraphicCompletions = async (event): Promise<void> => {
event.preventDefault();
// Empty out the response area
sentenceCompletionsSignal.value = ["Working ..."];

// Get the label texts from each symbol in `changeEncodingContents`
const labelText = [];
changeEncodingContents.value.payloads.forEach( (value) => {
labelText.push(value.label);
});
const systemPrompt = (document.getElementById(TEXTAREA_ID) as HTMLTextAreaElement).value;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same concern about using document.getElementById to reach into the real DOM rather than using Preact way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, I don't see what the Preact way is, exactly. The systemPrompt ig in another part of the DOM, or, from Preact's perspective, in another component, the DialogPromptEntries component. I thought of using useState(), but that is specific to the current CommandTelegraphicCompletions component. Another possiblity is to useContext(), but that seems to be for (re)rendering components, not handling the user clicking the "Telegraphic Completions" button. Perhaps using another signal is the appropriate route, but again, the user changing the prompt in the DialogPromptEntries component should not cause the CommandTelegraphicCompletions component to do anything, neither re-render nor ask for completions. It's only when the user asks for completions that the CommandTelegraphicCompletions do anything.

I'm leaving it as it is for now, but still thinking about it.

const response = await queryChat(
labelText.join(" "), model, streamAsBoolean, systemPrompt
);
// Parse the query response messages into an array of strings. Note that
// with Llama3.1 each message is one of the suggested sentence completions.
// That might just be luck.
sentenceCompletionsSignal.value = response.message.content.split("\n");
console.log(`getTelegraphicCompletions(), ${sentenceCompletionsSignal.value}`);
};

// Handler to remove all of the suggested completions.
const removeSuggestions = (): void => {
sentenceCompletionsSignal.value = [];
};

return html`
<p class="commandTelegraphicCompletions">
<button onClick=${getTelegraphicCompletions}>
${TELEGRPAHIC_BUTTON_LABEL}
</button>
<span style="visibility: hidden">F</span>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there a text "F" in the <span>? If it's for spacing, is it possible to use margin?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it was for debugging. But, removing it makes the buttons too close. Using margin ins the scss file is a good idea -- done.

<button onClick=${removeSuggestions}>
${CANCEL_BUTTON_LABEL}
</button>
</p>
`;
}
15 changes: 15 additions & 0 deletions src/client/DialogPromptEntries.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright 2025 Inclusive Design Research Centre, OCAD University
* All rights reserved.
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* You may obtain a copy of the License at
* https://github.com/inclusive-design/adaptive-palette/blob/main/LICENSE
*/

form.dialogPromptEntries {
display: inline-grid;
background:white;
}
Loading