mirror of
https://github.com/typemill/typemill.git
synced 2025-08-05 05:37:45 +02:00
add examples to prompts
This commit is contained in:
2
cache/timer.yaml
vendored
2
cache/timer.yaml
vendored
@@ -1 +1 @@
|
||||
licenseupdate: 1746647584
|
||||
licenseupdate: 1747254971
|
||||
|
@@ -39,3 +39,11 @@ promptlist:
|
||||
content: 'Return pure mermaid syntax for a [pie, x, y] diagram.'
|
||||
active: true
|
||||
system: true
|
||||
example:
|
||||
title: example
|
||||
content: 'Please use the following and write an article.'
|
||||
active: true
|
||||
system: false
|
||||
errors:
|
||||
title: false
|
||||
body: false
|
||||
|
@@ -34,17 +34,15 @@ class ControllerApiAuthorMeta extends Controller
|
||||
|
||||
$metadata = $meta->getMetaData($item);
|
||||
|
||||
if(!$metadata or !isset($metadata['meta']['owner']) OR !$metadata['meta']['owner'])
|
||||
if(
|
||||
!$metadata or
|
||||
!isset($metadata['meta']['owner']) OR
|
||||
!$metadata['meta']['owner']
|
||||
)
|
||||
{
|
||||
$metadata = $meta->addMetaDefaults($metadata, $item, $this->settings['author'], $request->getAttribute('c_username'));
|
||||
}
|
||||
|
||||
#fix for version 1 because owner in meta is often 'false'
|
||||
if(!isset($metadata['meta']['owner']) OR !$metadata['meta']['owner'])
|
||||
{
|
||||
$metadata = $meta->addMetaDefaults($metadata, $item, $this->settings['author'], $request->getAttribute('c_username'));
|
||||
}
|
||||
|
||||
|
||||
# if user is not allowed to perform this action (e.g. not admin)
|
||||
if(!$this->userroleIsAllowed($request->getAttribute('c_userrole'), 'content', 'read'))
|
||||
{
|
||||
|
@@ -5,6 +5,8 @@ namespace Typemill\Controllers;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Typemill\Models\Validation;
|
||||
use Typemill\Models\Navigation;
|
||||
use Typemill\Models\Content;
|
||||
use Typemill\Models\License;
|
||||
use Typemill\Models\Settings;
|
||||
use Typemill\Models\User;
|
||||
@@ -19,7 +21,7 @@ class ControllerApiKixote extends Controller
|
||||
{
|
||||
$system = 'You are a content editor and writing assistant.'
|
||||
. ' If the user prompt does not explicitly specify otherwise,'
|
||||
. ' apply the prompt to the provided article and return only the updated article in Markdown syntax,'
|
||||
. ' apply the prompt to the provided article inside the <article></article> tag and return only the updated article in Markdown syntax,'
|
||||
. ' without any extra comments or explanations.'
|
||||
. ' If you find the tag <focus></focus>,'
|
||||
. ' modify only the content inside these tags and leave everything else unchanged.'
|
||||
@@ -261,7 +263,7 @@ class ControllerApiKixote extends Controller
|
||||
|
||||
public function prompt(Request $request, Response $response)
|
||||
{
|
||||
$params = $request->getParsedBody();
|
||||
$params = $request->getParsedBody();
|
||||
|
||||
if (empty($params['prompt']) || !is_string($params['prompt']))
|
||||
{
|
||||
@@ -282,8 +284,35 @@ class ControllerApiKixote extends Controller
|
||||
$promptname = $params['name'] ?? '';
|
||||
$prompt = $params['prompt'] ?? '';
|
||||
$article = $params['article'] ?? '';
|
||||
$tone = $params['tone'] ?? '';
|
||||
|
||||
$example = $params['link'] ?? false;
|
||||
|
||||
if($example)
|
||||
{
|
||||
$validate = new Validation();
|
||||
$validInput = $validate->articleUrl(['url' => $params['link']]);
|
||||
if($validInput === true)
|
||||
{
|
||||
$urlinfo = $this->c->get('urlinfo');
|
||||
$langattr = $this->settings['langattr'];
|
||||
$navigation = new Navigation();
|
||||
$item = $navigation->getItemForUrl($params['url'], $urlinfo, $langattr);
|
||||
if($item)
|
||||
{
|
||||
$content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher'));
|
||||
$markdown = $content->getDraftMarkdown($item);
|
||||
if($markdown)
|
||||
{
|
||||
if(is_array($markdown))
|
||||
{
|
||||
$markdown = $content->markdownArrayToText($markdown);
|
||||
}
|
||||
|
||||
$example = $markdown;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$aiservice = $this->settings['aiservice'] ?? false;
|
||||
if(!$aiservice)
|
||||
{
|
||||
@@ -295,11 +324,11 @@ class ControllerApiKixote extends Controller
|
||||
|
||||
switch ($aiservice) {
|
||||
case 'chatgpt':
|
||||
$answer = $this->promptChatGPT($promptname, $prompt, $article, $tone);
|
||||
$answer = $this->promptChatGPT($promptname, $prompt, $article, $example);
|
||||
break;
|
||||
|
||||
case 'claude':
|
||||
$answer = $this->promptClaude($promptname, $prompt, $article, $tone);
|
||||
$answer = $this->promptClaude($promptname, $prompt, $article, $example);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -324,7 +353,7 @@ class ControllerApiKixote extends Controller
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
|
||||
}
|
||||
|
||||
public function promptChatGPT($promptname, $prompt, $article, $tone)
|
||||
public function promptChatGPT($promptname, $prompt, $article, $example)
|
||||
{
|
||||
# check if user has accepted
|
||||
|
||||
@@ -341,6 +370,13 @@ class ControllerApiKixote extends Controller
|
||||
$url = 'https://api.openai.com/v1/chat/completions';
|
||||
$authHeader = "Authorization: Bearer $apikey";
|
||||
|
||||
$content = $prompt;
|
||||
if($example)
|
||||
{
|
||||
$content .= "\n<example>" . $example . "</example>";
|
||||
}
|
||||
$content .= "\n<article>" . $article . "<article>";
|
||||
|
||||
$postdata = [
|
||||
'model' => $model,
|
||||
'messages' => [
|
||||
@@ -350,7 +386,7 @@ class ControllerApiKixote extends Controller
|
||||
],
|
||||
[
|
||||
'role' => 'user',
|
||||
'content' => $prompt . "\n" . $article
|
||||
'content' => $content
|
||||
],
|
||||
],
|
||||
'temperature' => 0.7,
|
||||
@@ -391,7 +427,7 @@ class ControllerApiKixote extends Controller
|
||||
return $answer;
|
||||
}
|
||||
|
||||
public function promptClaude($promptname, $prompt, $article, $tone)
|
||||
public function promptClaude($promptname, $prompt, $article, $example)
|
||||
{
|
||||
# Check if user has accepted
|
||||
$settingsModel = new Settings();
|
||||
@@ -410,13 +446,20 @@ class ControllerApiKixote extends Controller
|
||||
"anthropic-version: 2023-06-01"
|
||||
];
|
||||
|
||||
$content = $prompt;
|
||||
if($example)
|
||||
{
|
||||
$content .= "\n<example>" . $example . "</example>";
|
||||
}
|
||||
$content .= "\n<article>" . $article . "<article>";
|
||||
|
||||
$postdata = [
|
||||
'model' => $model,
|
||||
'system' => $this->getSystemMessage(),
|
||||
'messages' => [
|
||||
[
|
||||
'role' => 'user',
|
||||
'content' => $prompt . "\n" . $article
|
||||
'content' => $content
|
||||
],
|
||||
],
|
||||
'temperature' => 0.7,
|
||||
|
@@ -39,6 +39,7 @@ const kixote = Vue.createApp({
|
||||
:is = "currentTabComponent"
|
||||
:command = "command"
|
||||
:content = "content"
|
||||
:navigation = "navigation"
|
||||
:item = "item"
|
||||
:useragreement = "useragreement"
|
||||
:aiservice = "aiservice"
|
||||
@@ -149,6 +150,7 @@ const kixote = Vue.createApp({
|
||||
loading: false,
|
||||
item: data.item,
|
||||
content: data.content,
|
||||
navigation: data.navigation,
|
||||
urlinfo: data.urlinfo,
|
||||
labels: data.labels,
|
||||
settings: data.settings,
|
||||
@@ -319,19 +321,19 @@ const kixote = Vue.createApp({
|
||||
})
|
||||
|
||||
kixote.component('tab-generate', {
|
||||
props: ['content', 'item', 'labels', 'urlinfo', 'settings', 'kixoteSettings', 'settingsSaved', 'aiservice', 'useragreement', 'tokenstats'],
|
||||
props: ['content', 'navigation', 'item', 'labels', 'urlinfo', 'settings', 'kixoteSettings', 'settingsSaved', 'aiservice', 'useragreement', 'tokenstats'],
|
||||
data: function () {
|
||||
return {
|
||||
tabs: [
|
||||
{ value: 'article', name: 'Article' },
|
||||
{ value: 'prompts', name: 'Prompts' },
|
||||
/* { value: 'tone', name: 'Tone' }, */
|
||||
],
|
||||
currentTab: 'article',
|
||||
originalmd: '',
|
||||
activeversion: 0,
|
||||
versions: [],
|
||||
prompt: '',
|
||||
promptlink: null,
|
||||
promptError: false,
|
||||
showFocusButton: false,
|
||||
buttonPosition: { top: 0, left: 0 },
|
||||
@@ -342,13 +344,15 @@ kixote.component('tab-generate', {
|
||||
title: '',
|
||||
content: '',
|
||||
active: true,
|
||||
system: false
|
||||
system: false,
|
||||
link: null,
|
||||
},
|
||||
titleError: false,
|
||||
bodyError: false,
|
||||
currentFilter: 'all',
|
||||
article: '',
|
||||
index: '',
|
||||
flatnavi: false,
|
||||
};
|
||||
},
|
||||
template: `<section class="dark:bg-stone-700 dark:text-stone-200">
|
||||
@@ -508,7 +512,7 @@ kixote.component('tab-generate', {
|
||||
<div class="flex justify-between py-2 px-2">
|
||||
<button @click.prevent="addNewPrompt = !addNewPrompt">
|
||||
<span v-if="addNewPrompt">-</span>
|
||||
<span v-else>+</span> add
|
||||
<span v-else>+</span> add prompt
|
||||
</button>
|
||||
<div class="flex space-x-2">
|
||||
<span class="px-1">Filter:</span>
|
||||
@@ -545,6 +549,7 @@ kixote.component('tab-generate', {
|
||||
<span v-if="titleError" class="text-red-500 text-sm">{{ titleError }}</span>
|
||||
<textarea
|
||||
class = "w-full p-2 my-1 font-mono bg-stone-600 no-outline text-white caret-white focus:outline-none"
|
||||
rows = "5"
|
||||
@input = "validateBody(newPrompt.content)"
|
||||
@focus = "editPrompt = newPrompt.name"
|
||||
placeholder = "Enter a prompt"
|
||||
@@ -552,6 +557,17 @@ kixote.component('tab-generate', {
|
||||
>
|
||||
</textarea>
|
||||
<span v-if="bodyError" class="text-red-500 text-sm">{{ bodyError }}</span>
|
||||
|
||||
<div class="space-y-2 my-2">
|
||||
<select v-model="newPrompt.link"
|
||||
class="w-full p-2 font-mono bg-stone-600 text-white caret-white focus:outline-none">
|
||||
<option :value="null" class="text-stone-400 italic">Select example article</option>
|
||||
<option v-for="naviitem in flatnavi" :key="naviitem.urlWoF" :value="naviitem.urlRelWoF">
|
||||
{{ naviitem }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button v-if="!titleError && !bodyError"
|
||||
@click.prevent="saveNewPrompt"
|
||||
class="px-1 text-teal-300 hover:text-teal-500 transition-colors"
|
||||
@@ -565,6 +581,7 @@ kixote.component('tab-generate', {
|
||||
:key="name"
|
||||
class = "py-2 px-2"
|
||||
>
|
||||
{{ prompttemplate }}
|
||||
<fieldset class="border border-stone-700 p-4">
|
||||
<div class="flex w-full justify-between">
|
||||
<input
|
||||
@@ -608,6 +625,7 @@ kixote.component('tab-generate', {
|
||||
<span v-if="prompttemplate.errors?.title" class="text-red-500 text-sm">{{ prompttemplate.errors.title }}</span>
|
||||
<textarea
|
||||
class = "w-full p-2 my-1 font-mono bg-stone-600 no-outline text-white caret-white focus:outline-none"
|
||||
rows = "5"
|
||||
v-model = "prompttemplate.content"
|
||||
:readonly = "prompttemplate.system"
|
||||
@focus = "editPrompt = name"
|
||||
@@ -615,14 +633,72 @@ kixote.component('tab-generate', {
|
||||
>
|
||||
</textarea>
|
||||
<span v-if="prompttemplate.errors?.body" class="text-red-500 text-sm">{{ prompttemplate.errors.body }}</span>
|
||||
|
||||
<div class="space-y-2 my-2">
|
||||
<select v-model="prompttemplate.link"
|
||||
class="w-full p-2 font-mono bg-stone-600 text-white caret-white focus:outline-none">
|
||||
<option :value="null">Select example article</option>
|
||||
<option v-for="naviitem in flatnavi" :key="naviitem.urlWoF" :value="naviitem.urlRelWoF">
|
||||
{{ naviitem }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="currentTab === 'tone'">
|
||||
<p>Mimic your tone tab content here.</p>
|
||||
</div>
|
||||
<div v-else-if="currentTab === 'tone'">
|
||||
<div class="w-full bg-stone-900 px-8 py-8">
|
||||
<div class="flex justify-between py-2 px-2">
|
||||
<button @click.prevent="addNewTone = !addNewTone">
|
||||
<span v-if="addNewTone">-</span>
|
||||
<span v-else>+</span> add tone
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<transition name="fade">
|
||||
<div v-if="addNewTone" class="py-2 px-2">
|
||||
<fieldset class="border border-stone-700 p-4">
|
||||
<input
|
||||
type="text"
|
||||
class="w-50 p-2 my-1 font-mono bg-stone-600 text-white caret-white focus:outline-none"
|
||||
placeholder="Enter a tone name"
|
||||
v-model="newTone.title"
|
||||
/>
|
||||
|
||||
|
||||
<!-- Tone description result -->
|
||||
<textarea
|
||||
rows="5"
|
||||
class="w-full p-2 my-1 font-mono bg-stone-600 text-white caret-white focus:outline-none"
|
||||
placeholder="Tone description"
|
||||
v-model="newTone.description"
|
||||
></textarea>
|
||||
|
||||
<div class="w-full flex justify-between">
|
||||
|
||||
<!-- Button to analyze tone -->
|
||||
<button
|
||||
@click.prevent="analyzeTone(newTone)"
|
||||
class="px-1 text-teal-300 hover:text-teal-500 transition-colors"
|
||||
>analyze tone</button>
|
||||
|
||||
<!-- Save button -->
|
||||
<button
|
||||
@click.prevent="saveNewTone"
|
||||
class="px-1 text-teal-300 hover:text-teal-500 transition-colors"
|
||||
>save</button>
|
||||
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -636,6 +712,8 @@ kixote.component('tab-generate', {
|
||||
{
|
||||
this.initAutosize();
|
||||
|
||||
this.createFlatNavi();
|
||||
|
||||
if(this.versions.length == 0)
|
||||
{
|
||||
this.initializeContent()
|
||||
@@ -668,22 +746,27 @@ kixote.component('tab-generate', {
|
||||
Object.entries(this.kixoteSettings?.promptlist || {}).filter(([key, prompt]) => !prompt.system)
|
||||
);
|
||||
},
|
||||
filteredPrompts()
|
||||
{
|
||||
if(this.currentFilter === 'system')
|
||||
{
|
||||
return this.promptlistsystem;
|
||||
}
|
||||
else if (this.currentFilter === 'user')
|
||||
{
|
||||
return this.promptlistuser;
|
||||
}
|
||||
else
|
||||
{
|
||||
return this.kixoteSettings.promptlist;
|
||||
}
|
||||
}
|
||||
filteredPrompts()
|
||||
{
|
||||
const list =
|
||||
this.currentFilter === 'system'
|
||||
? this.promptlistsystem
|
||||
: this.currentFilter === 'user'
|
||||
? this.promptlistuser
|
||||
: this.kixoteSettings.promptlist;
|
||||
|
||||
// Normalize `link` to `null` if not present
|
||||
const normalized = {};
|
||||
for (const [key, prompt] of Object.entries(list))
|
||||
{
|
||||
normalized[key] = {
|
||||
...prompt,
|
||||
link: prompt.link ?? null
|
||||
};
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initAutosize()
|
||||
@@ -737,6 +820,25 @@ kixote.component('tab-generate', {
|
||||
this.versions.push(markdown);
|
||||
this.resizeAiEditor();
|
||||
},
|
||||
createFlatNavi() {
|
||||
if (this.navigation && !this.flatnavi) {
|
||||
const nestedNavi = [];
|
||||
|
||||
const recurse = (items) => {
|
||||
items.forEach(item => {
|
||||
if (item.urlRelWoF) {
|
||||
nestedNavi.push(item.urlRelWoF);
|
||||
}
|
||||
if (item.folderContent && item.folderContent.length > 0) {
|
||||
recurse(item.folderContent);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
recurse(this.navigation);
|
||||
this.flatnavi = nestedNavi;
|
||||
}
|
||||
},
|
||||
resizeAiEditor()
|
||||
{
|
||||
this.$nextTick(() => {
|
||||
@@ -760,6 +862,7 @@ kixote.component('tab-generate', {
|
||||
usePrompt(index)
|
||||
{
|
||||
this.prompt = this.promptlistactive[index].content;
|
||||
this.promptlink = this.promptlistactive[index].link;
|
||||
this.resizePromptEditor();
|
||||
},
|
||||
switchVersion(index)
|
||||
@@ -776,7 +879,8 @@ kixote.component('tab-generate', {
|
||||
|
||||
tmaxios.post('/api/v1/prompt',{
|
||||
'prompt': this.prompt,
|
||||
'article': this.versions[this.activeversion]
|
||||
'article': this.versions[this.activeversion],
|
||||
'link': this.promptlink
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
@@ -788,6 +892,7 @@ kixote.component('tab-generate', {
|
||||
self.versions.push(answer);
|
||||
self.activeversion = self.versions.length-1;
|
||||
self.prompt = '';
|
||||
self.promptlink = null;
|
||||
self.resizePromptEditor();
|
||||
self.resizeAiEditor();
|
||||
}
|
||||
@@ -997,6 +1102,10 @@ kixote.component('tab-generate', {
|
||||
this.addNewPrompt = false;
|
||||
this.updateSettings(promptlist);
|
||||
eventBus.$emit('storeKixoteSettings');
|
||||
},
|
||||
getArticleMarkdown(url)
|
||||
{
|
||||
|
||||
},
|
||||
exit()
|
||||
{
|
||||
|
Reference in New Issue
Block a user