jp.keijiro.ai.assistant.extensions 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.attestation.p7m +0 -0
- package/CHANGELOG.md +10 -0
- package/CHANGELOG.md.meta +7 -0
- package/Editor/AssistantFontModifier.cs +70 -0
- package/Editor/AssistantFontModifier.cs.meta +2 -0
- package/Editor/AssistantFontOverride.uss +3 -0
- package/Editor/AssistantFontOverride.uss.meta +12 -0
- package/Editor/AssistantImeProxy.cs +171 -0
- package/Editor/AssistantImeProxy.cs.meta +2 -0
- package/Editor/AssistantImeProxy.uss +58 -0
- package/Editor/AssistantImeProxy.uss.meta +12 -0
- package/Editor/AssistantImeProxy.uxml +8 -0
- package/Editor/AssistantImeProxy.uxml.meta +10 -0
- package/Editor/NotoSansJP-Regular.ttf +0 -0
- package/Editor/NotoSansJP-Regular.ttf.meta +21 -0
- package/Editor.meta +8 -0
- package/Extras~/extract_conversations.py +127 -0
- package/LICENSE +24 -0
- package/LICENSE.meta +7 -0
- package/README.md +20 -0
- package/README.md.meta +7 -0
- package/package.json +23 -0
- package/package.json.meta +7 -0
package/.attestation.p7m
ADDED
|
Binary file
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
6
|
+
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2025-04-23
|
|
9
|
+
|
|
10
|
+
- Initial version.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
using UnityEditor;
|
|
2
|
+
using UnityEngine;
|
|
3
|
+
using UnityEngine.UIElements;
|
|
4
|
+
|
|
5
|
+
namespace AIAssistantExtensions {
|
|
6
|
+
|
|
7
|
+
[InitializeOnLoad]
|
|
8
|
+
static class AssistantFontModifier
|
|
9
|
+
{
|
|
10
|
+
const string OverrideStylePath =
|
|
11
|
+
"Packages/jp.keijiro.ai.assistant.extensions/Editor/AssistantFontOverride.uss";
|
|
12
|
+
|
|
13
|
+
const string TargetWindowTypeName =
|
|
14
|
+
"Unity.AI.Assistant.UI.Editor.Scripts.AssistantWindow";
|
|
15
|
+
|
|
16
|
+
const string TargetRootElementName = "root-panel";
|
|
17
|
+
|
|
18
|
+
//
|
|
19
|
+
// Constructor and event handlers
|
|
20
|
+
//
|
|
21
|
+
|
|
22
|
+
static AssistantFontModifier()
|
|
23
|
+
{
|
|
24
|
+
// Subscribe to window focus change events
|
|
25
|
+
EditorWindow.windowFocusChanged += OnWindowFocusChanged;
|
|
26
|
+
|
|
27
|
+
// Double delayCall to wait for Assistant UI to be fully constructed after domain reload
|
|
28
|
+
EditorApplication.delayCall += () =>
|
|
29
|
+
{ EditorApplication.delayCall += () => ApplyToAllOpenWindows(); };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static void OnWindowFocusChanged()
|
|
33
|
+
=> EditorApplication.delayCall += () =>
|
|
34
|
+
CheckAndApplyCustomFont(EditorWindow.focusedWindow);
|
|
35
|
+
|
|
36
|
+
static void ApplyToAllOpenWindows()
|
|
37
|
+
{
|
|
38
|
+
foreach (var window in Resources.FindObjectsOfTypeAll<EditorWindow>())
|
|
39
|
+
CheckAndApplyCustomFont(window);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//
|
|
43
|
+
// Font application logic
|
|
44
|
+
//
|
|
45
|
+
|
|
46
|
+
static StyleSheet _styleSheet;
|
|
47
|
+
|
|
48
|
+
static bool CheckWindowType(EditorWindow window)
|
|
49
|
+
=> window?.GetType()?.FullName == TargetWindowTypeName;
|
|
50
|
+
|
|
51
|
+
static void ApplyCustomFont(EditorWindow window)
|
|
52
|
+
{
|
|
53
|
+
var root = window.rootVisualElement;
|
|
54
|
+
var target = root?.Q<VisualElement>(TargetRootElementName);
|
|
55
|
+
if (target == null) return;
|
|
56
|
+
|
|
57
|
+
if (_styleSheet == null)
|
|
58
|
+
_styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>(OverrideStylePath);
|
|
59
|
+
|
|
60
|
+
if (!target.styleSheets.Contains(_styleSheet))
|
|
61
|
+
target.styleSheets.Add(_styleSheet);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static void CheckAndApplyCustomFont(EditorWindow window)
|
|
65
|
+
{
|
|
66
|
+
if (CheckWindowType(window)) ApplyCustomFont(window);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
} // namespace AIAssistantExtensions
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
fileFormatVersion: 2
|
|
2
|
+
guid: 728a4d733b053455383a3ad0494cb2f5
|
|
3
|
+
ScriptedImporter:
|
|
4
|
+
internalIDToNameTable: []
|
|
5
|
+
externalObjects: {}
|
|
6
|
+
serializedVersion: 2
|
|
7
|
+
userData:
|
|
8
|
+
assetBundleName:
|
|
9
|
+
assetBundleVariant:
|
|
10
|
+
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
|
11
|
+
disableValidation: 0
|
|
12
|
+
unsupportedSelectorAction: 0
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
using UnityEditor;
|
|
2
|
+
using UnityEngine;
|
|
3
|
+
using UnityEngine.UIElements;
|
|
4
|
+
|
|
5
|
+
namespace AIAssistantExtensions {
|
|
6
|
+
|
|
7
|
+
class AssistantImeProxyWindow : EditorWindow
|
|
8
|
+
{
|
|
9
|
+
const string UxmlPath =
|
|
10
|
+
"Packages/jp.keijiro.ai.assistant.extensions/Editor/AssistantImeProxy.uxml";
|
|
11
|
+
|
|
12
|
+
const string StyleSheetPath =
|
|
13
|
+
"Packages/jp.keijiro.ai.assistant.extensions/Editor/AssistantImeProxy.uss";
|
|
14
|
+
|
|
15
|
+
VisualElement _placeholder;
|
|
16
|
+
VisualElement _inputRoot;
|
|
17
|
+
VisualElement _textFieldContainer;
|
|
18
|
+
TextField _textField;
|
|
19
|
+
|
|
20
|
+
[MenuItem("Window/AI/IME Proxy")]
|
|
21
|
+
static void Open()
|
|
22
|
+
{
|
|
23
|
+
var window = GetWindow<AssistantImeProxyWindow>();
|
|
24
|
+
window.titleContent = new GUIContent("IME Proxy");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static VisualTreeAsset _uxml;
|
|
28
|
+
static StyleSheet _styleSheet;
|
|
29
|
+
|
|
30
|
+
void CreateGUI()
|
|
31
|
+
{
|
|
32
|
+
_uxml ??= AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(UxmlPath);
|
|
33
|
+
_styleSheet ??= AssetDatabase.LoadAssetAtPath<StyleSheet>(StyleSheetPath);
|
|
34
|
+
|
|
35
|
+
rootVisualElement.Clear();
|
|
36
|
+
if (_styleSheet != null && !rootVisualElement.styleSheets.Contains(_styleSheet))
|
|
37
|
+
rootVisualElement.styleSheets.Add(_styleSheet);
|
|
38
|
+
|
|
39
|
+
if (_uxml == null)
|
|
40
|
+
{
|
|
41
|
+
rootVisualElement.Add(new Label("Failed to load AssistantImeProxy.uxml"));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_uxml?.CloneTree(rootVisualElement);
|
|
46
|
+
|
|
47
|
+
_placeholder = rootVisualElement.Q<VisualElement>("placeholder");
|
|
48
|
+
_inputRoot = rootVisualElement.Q<VisualElement>("input-root");
|
|
49
|
+
if (_placeholder == null || _inputRoot == null)
|
|
50
|
+
{
|
|
51
|
+
rootVisualElement.Clear();
|
|
52
|
+
rootVisualElement.Add(new Label("Assistant IME Proxy UI is invalid."));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
_placeholder.RegisterCallback<ClickEvent>(_ => ActivateTextField());
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
void ActivateTextField()
|
|
60
|
+
{
|
|
61
|
+
// Hide placeholder
|
|
62
|
+
_placeholder.style.display = DisplayStyle.None;
|
|
63
|
+
|
|
64
|
+
// Container with rounded corners and relative positioning for the hint label
|
|
65
|
+
_textFieldContainer = new VisualElement();
|
|
66
|
+
_textFieldContainer.AddToClassList("ime-input-container");
|
|
67
|
+
|
|
68
|
+
// Create a fresh TextField each time — no stale cursor or IME state.
|
|
69
|
+
_textField = new TextField { multiline = true };
|
|
70
|
+
_textField.AddToClassList("ime-input-field");
|
|
71
|
+
|
|
72
|
+
// Also round the inner TextInput element
|
|
73
|
+
var textInput = _textField.Q("unity-text-input");
|
|
74
|
+
if (textInput != null) textInput.AddToClassList("ime-input-field__input");
|
|
75
|
+
|
|
76
|
+
_textField.RegisterCallback<KeyDownEvent>(OnKeyDown, TrickleDown.TrickleDown);
|
|
77
|
+
_textFieldContainer.Add(_textField);
|
|
78
|
+
|
|
79
|
+
// Hint label overlaid at the bottom-right corner
|
|
80
|
+
var hint = new Label("\u2318+Enter to send");
|
|
81
|
+
hint.AddToClassList("ime-input-hint");
|
|
82
|
+
hint.pickingMode = PickingMode.Ignore;
|
|
83
|
+
_textFieldContainer.Add(hint);
|
|
84
|
+
|
|
85
|
+
_inputRoot.style.display = DisplayStyle.Flex;
|
|
86
|
+
_inputRoot.Add(_textFieldContainer);
|
|
87
|
+
|
|
88
|
+
// Focus after the element is attached to the panel
|
|
89
|
+
_textField.schedule.Execute(() => _textField.Focus());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
void DeactivateTextField()
|
|
93
|
+
{
|
|
94
|
+
// Destroy the TextField entirely — all internal state is discarded.
|
|
95
|
+
if (_textFieldContainer != null)
|
|
96
|
+
{
|
|
97
|
+
_textFieldContainer.RemoveFromHierarchy();
|
|
98
|
+
_textFieldContainer = null;
|
|
99
|
+
_textField = null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Show placeholder again
|
|
103
|
+
_inputRoot.style.display = DisplayStyle.None;
|
|
104
|
+
_placeholder.style.display = DisplayStyle.Flex;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
void OnKeyDown(KeyDownEvent evt)
|
|
108
|
+
{
|
|
109
|
+
if (evt.keyCode is not (KeyCode.Return or KeyCode.KeypadEnter)) return;
|
|
110
|
+
if (!evt.commandKey) return; // Cmd+Enter to send
|
|
111
|
+
|
|
112
|
+
var text = _textField.value?.Trim();
|
|
113
|
+
if (string.IsNullOrEmpty(text)) return;
|
|
114
|
+
|
|
115
|
+
evt.StopImmediatePropagation();
|
|
116
|
+
#pragma warning disable CS0618
|
|
117
|
+
evt.PreventDefault();
|
|
118
|
+
#pragma warning restore CS0618
|
|
119
|
+
|
|
120
|
+
SendToAssistant(text);
|
|
121
|
+
|
|
122
|
+
// Destroy the TextField after the current event processing completes.
|
|
123
|
+
_textField.schedule.Execute(DeactivateTextField);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
void SendToAssistant(string text)
|
|
127
|
+
{
|
|
128
|
+
// Find the AI Assistant window's chat input and action button.
|
|
129
|
+
TextField chatInput = null;
|
|
130
|
+
Button actionButton = null;
|
|
131
|
+
|
|
132
|
+
foreach (var w in Resources.FindObjectsOfTypeAll<EditorWindow>())
|
|
133
|
+
{
|
|
134
|
+
if (w == this) continue;
|
|
135
|
+
var typeName = w.GetType().FullName;
|
|
136
|
+
if (typeName == null || !typeName.Contains("Assistant")) continue;
|
|
137
|
+
|
|
138
|
+
var root = w.rootVisualElement;
|
|
139
|
+
chatInput = root?.Q<TextField>("input");
|
|
140
|
+
actionButton = root?.Q<Button>("actionButton");
|
|
141
|
+
|
|
142
|
+
if (chatInput != null && actionButton != null) break;
|
|
143
|
+
chatInput = null;
|
|
144
|
+
actionButton = null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (chatInput == null || actionButton == null) return;
|
|
148
|
+
|
|
149
|
+
// Set the text on the chat input field.
|
|
150
|
+
// This triggers OnTextFieldValueChanged -> OnChatValueChanged,
|
|
151
|
+
// which saves the prompt and enables the submit button.
|
|
152
|
+
chatInput.value = text;
|
|
153
|
+
|
|
154
|
+
// Click the action button after the value change has propagated.
|
|
155
|
+
// Using PointerUpEvent avoids the KeyDownEvent -> BubbleUp crash
|
|
156
|
+
// path entirely.
|
|
157
|
+
chatInput.schedule.Execute(() =>
|
|
158
|
+
{
|
|
159
|
+
var guiEvent = new Event
|
|
160
|
+
{
|
|
161
|
+
type = EventType.MouseUp,
|
|
162
|
+
button = 0,
|
|
163
|
+
mousePosition = actionButton.worldBound.center
|
|
164
|
+
};
|
|
165
|
+
using var ptrUp = PointerUpEvent.GetPooled(guiEvent);
|
|
166
|
+
actionButton.SendEvent(ptrUp);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
} // namespace AIAssistantExtensions
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
.ime-proxy-root {
|
|
2
|
+
flex-grow: 1;
|
|
3
|
+
padding: 4px;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.ime-proxy-panel {
|
|
7
|
+
flex-grow: 1;
|
|
8
|
+
border-top-left-radius: 8px;
|
|
9
|
+
border-top-right-radius: 8px;
|
|
10
|
+
border-bottom-left-radius: 8px;
|
|
11
|
+
border-bottom-right-radius: 8px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.ime-proxy-placeholder {
|
|
15
|
+
justify-content: center;
|
|
16
|
+
align-items: center;
|
|
17
|
+
background-color: rgb(51, 51, 51);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.ime-proxy-placeholder__label {
|
|
21
|
+
color: rgb(153, 153, 153);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.ime-proxy-input-root {
|
|
25
|
+
flex-grow: 1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.ime-input-container {
|
|
29
|
+
flex-grow: 1;
|
|
30
|
+
position: relative;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.ime-input-field {
|
|
34
|
+
flex-grow: 1;
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
-unity-font-definition: url("project://database/Packages/jp.keijiro.ai.assistant.extensions/Editor/NotoSansJP-Regular.ttf?fileID=12800000&guid=f7d2dd1a328194df9b71491ff2d07e2b&type=3#NotoSansJP-Regular");
|
|
37
|
+
border-top-left-radius: 8px;
|
|
38
|
+
border-top-right-radius: 8px;
|
|
39
|
+
border-bottom-left-radius: 8px;
|
|
40
|
+
border-bottom-right-radius: 8px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.ime-input-field__input {
|
|
44
|
+
-unity-font-definition: url("project://database/Packages/jp.keijiro.ai.assistant.extensions/Editor/NotoSansJP-Regular.ttf?fileID=12800000&guid=f7d2dd1a328194df9b71491ff2d07e2b&type=3#NotoSansJP-Regular");
|
|
45
|
+
white-space: normal;
|
|
46
|
+
border-top-left-radius: 8px;
|
|
47
|
+
border-top-right-radius: 8px;
|
|
48
|
+
border-bottom-left-radius: 8px;
|
|
49
|
+
border-bottom-right-radius: 8px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.ime-input-hint {
|
|
53
|
+
position: absolute;
|
|
54
|
+
right: 10px;
|
|
55
|
+
bottom: 6px;
|
|
56
|
+
font-size: 11px;
|
|
57
|
+
color: rgba(128, 128, 128, 0.6);
|
|
58
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
fileFormatVersion: 2
|
|
2
|
+
guid: 73fe18140135455489bef80074f4cbca
|
|
3
|
+
ScriptedImporter:
|
|
4
|
+
internalIDToNameTable: []
|
|
5
|
+
externalObjects: {}
|
|
6
|
+
serializedVersion: 2
|
|
7
|
+
userData:
|
|
8
|
+
assetBundleName:
|
|
9
|
+
assetBundleVariant:
|
|
10
|
+
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
|
11
|
+
disableValidation: 0
|
|
12
|
+
unsupportedSelectorAction: 0
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True">
|
|
2
|
+
<ui:VisualElement name="ime-proxy-root" class="ime-proxy-root">
|
|
3
|
+
<ui:VisualElement name="placeholder" class="ime-proxy-panel ime-proxy-placeholder">
|
|
4
|
+
<ui:Label text="Click here to type a prompt" class="ime-proxy-placeholder__label" />
|
|
5
|
+
</ui:VisualElement>
|
|
6
|
+
<ui:VisualElement name="input-root" class="ime-proxy-input-root" style="display: none;" />
|
|
7
|
+
</ui:VisualElement>
|
|
8
|
+
</ui:UXML>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
fileFormatVersion: 2
|
|
2
|
+
guid: c54216f5e4e64d86b94a600411cef6eb
|
|
3
|
+
ScriptedImporter:
|
|
4
|
+
internalIDToNameTable: []
|
|
5
|
+
externalObjects: {}
|
|
6
|
+
serializedVersion: 2
|
|
7
|
+
userData:
|
|
8
|
+
assetBundleName:
|
|
9
|
+
assetBundleVariant:
|
|
10
|
+
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
|
Binary file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
fileFormatVersion: 2
|
|
2
|
+
guid: f7d2dd1a328194df9b71491ff2d07e2b
|
|
3
|
+
TrueTypeFontImporter:
|
|
4
|
+
externalObjects: {}
|
|
5
|
+
serializedVersion: 4
|
|
6
|
+
fontSize: 16
|
|
7
|
+
forceTextureCase: -2
|
|
8
|
+
characterSpacing: 0
|
|
9
|
+
characterPadding: 1
|
|
10
|
+
includeFontData: 1
|
|
11
|
+
fontNames:
|
|
12
|
+
- Noto Sans JP
|
|
13
|
+
fallbackFontReferences: []
|
|
14
|
+
customCharacters:
|
|
15
|
+
fontRenderingMode: 0
|
|
16
|
+
ascentCalculationMode: 1
|
|
17
|
+
useLegacyBoundsCalculation: 0
|
|
18
|
+
shouldRoundAdvanceValue: 1
|
|
19
|
+
userData:
|
|
20
|
+
assetBundleName:
|
|
21
|
+
assetBundleVariant:
|
package/Editor.meta
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
MESSAGE_RE = re.compile(
|
|
13
|
+
r'"\$type":"(?P<type>[^"]+)".*?'
|
|
14
|
+
r'"message_id":"(?P<message_id>[^"]+)".*?'
|
|
15
|
+
r'(?:"last_message":(?P<last_message>true|false).*?)?'
|
|
16
|
+
r'"markdown":"(?P<markdown>(?:\\.|[^"\\])*)"',
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ConversationPair:
|
|
22
|
+
prompt: str
|
|
23
|
+
prompt_line: int
|
|
24
|
+
prompt_message_id: str
|
|
25
|
+
response_parts: list[str] = field(default_factory=list)
|
|
26
|
+
response_lines: list[int] = field(default_factory=list)
|
|
27
|
+
response_message_id: Optional[str] = None
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def response(self) -> str:
|
|
31
|
+
return "".join(self.response_parts).strip()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def decode_markdown(value: str) -> str:
|
|
35
|
+
return json.loads(f'"{value}"')
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def parse_line(line: str):
|
|
39
|
+
match = MESSAGE_RE.search(line)
|
|
40
|
+
if not match:
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
groups = match.groupdict()
|
|
44
|
+
return {
|
|
45
|
+
"type": groups["type"],
|
|
46
|
+
"message_id": groups["message_id"],
|
|
47
|
+
"last_message": groups["last_message"] == "true",
|
|
48
|
+
"markdown": decode_markdown(groups["markdown"]),
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def extract_pairs(path: Path) -> list[ConversationPair]:
|
|
53
|
+
pairs: list[ConversationPair] = []
|
|
54
|
+
current_pair: Optional[ConversationPair] = None
|
|
55
|
+
active_response_id: Optional[str] = None
|
|
56
|
+
|
|
57
|
+
for line_number, raw_line in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1):
|
|
58
|
+
parsed = parse_line(raw_line)
|
|
59
|
+
if not parsed:
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
message_type = parsed["type"]
|
|
63
|
+
markdown = parsed["markdown"]
|
|
64
|
+
|
|
65
|
+
if message_type == "CHAT_ACKNOWLEDGMENT_V1" and markdown:
|
|
66
|
+
if current_pair and current_pair.response_parts:
|
|
67
|
+
pairs.append(current_pair)
|
|
68
|
+
|
|
69
|
+
current_pair = ConversationPair(
|
|
70
|
+
prompt=markdown.strip(),
|
|
71
|
+
prompt_line=line_number,
|
|
72
|
+
prompt_message_id=parsed["message_id"],
|
|
73
|
+
)
|
|
74
|
+
active_response_id = None
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
if message_type != "CHAT_RESPONSE_V1" or current_pair is None:
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
if markdown and not markdown.startswith("<"):
|
|
81
|
+
if active_response_id is None:
|
|
82
|
+
active_response_id = parsed["message_id"]
|
|
83
|
+
current_pair.response_message_id = active_response_id
|
|
84
|
+
|
|
85
|
+
if parsed["message_id"] == active_response_id:
|
|
86
|
+
current_pair.response_parts.append(markdown)
|
|
87
|
+
current_pair.response_lines.append(line_number)
|
|
88
|
+
|
|
89
|
+
if parsed["last_message"] and active_response_id == parsed["message_id"]:
|
|
90
|
+
pairs.append(current_pair)
|
|
91
|
+
current_pair = None
|
|
92
|
+
active_response_id = None
|
|
93
|
+
|
|
94
|
+
if current_pair:
|
|
95
|
+
pairs.append(current_pair)
|
|
96
|
+
|
|
97
|
+
return pairs
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def build_markdown(pairs: list[ConversationPair], include_empty: bool) -> str:
|
|
101
|
+
sections = []
|
|
102
|
+
|
|
103
|
+
for pair in pairs:
|
|
104
|
+
if not include_empty and not pair.response:
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
response = pair.response or "(No extracted response)"
|
|
108
|
+
sections.append(f"## User\n\n{pair.prompt}\n\n## Assistant\n\n{response}")
|
|
109
|
+
|
|
110
|
+
return "\n\n".join(sections)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def main():
|
|
114
|
+
parser = argparse.ArgumentParser(
|
|
115
|
+
description="Extract user prompts and natural-language assistant replies from Unity AI relay logs."
|
|
116
|
+
)
|
|
117
|
+
parser.add_argument("logfile", nargs="?", default="relay.txt", help="Path to relay log file")
|
|
118
|
+
parser.add_argument("--include-empty", action="store_true", help="Include prompts without extracted replies")
|
|
119
|
+
args = parser.parse_args()
|
|
120
|
+
|
|
121
|
+
path = Path(args.logfile)
|
|
122
|
+
pairs = extract_pairs(path)
|
|
123
|
+
print(build_markdown(pairs, args.include_empty))
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
if __name__ == "__main__":
|
|
127
|
+
main()
|
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
|
2
|
+
|
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
4
|
+
distribute this software, either in source code form or as a compiled
|
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
|
6
|
+
means.
|
|
7
|
+
|
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
|
9
|
+
of this software dedicate any and all copyright interest in the
|
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
|
11
|
+
of the public at large and to the detriment of our heirs and
|
|
12
|
+
successors. We intend this dedication to be an overt act of
|
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
|
14
|
+
software under copyright law.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
For more information, please refer to <https://unlicense.org/>
|
package/LICENSE.meta
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# AIA Extensions
|
|
2
|
+
|
|
3
|
+
**AIA Extensions** is a custom Unity package that provides extensions for Unity
|
|
4
|
+
AI Assistant.
|
|
5
|
+
|
|
6
|
+
## IME Proxy
|
|
7
|
+
|
|
8
|
+
<img width="400" height="284" alt="IME Proxy" src="https://github.com/user-attachments/assets/a6b5146d-1b63-47dd-ab4d-633357996974" />
|
|
9
|
+
|
|
10
|
+
**IME Proxy** is a custom window that works around several IME-related issues,
|
|
11
|
+
especially when entering Japanese text. You can open it from
|
|
12
|
+
`Window > AI > IME Proxy`. It automatically copies the contents of its text
|
|
13
|
+
field to the AI Assistant chat field and sends the message when you press
|
|
14
|
+
`⌘ + Enter`.
|
|
15
|
+
|
|
16
|
+
## Font Modifier
|
|
17
|
+
|
|
18
|
+
This package includes `AssistantFontModifier` that replaces fonts used in the
|
|
19
|
+
AI Assistant chat window with the Noto Sans JP font. It suppresses a font atlas
|
|
20
|
+
overflow issue that occurs in the Unity Editor on macOS.
|
package/README.md.meta
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jp.keijiro.ai.assistant.extensions",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"displayName": "AI Assistant Extensions",
|
|
5
|
+
"description": "Custom Unity package that provides extensions for Unity AI Assistant.",
|
|
6
|
+
"unity": "6000.0",
|
|
7
|
+
"author": "Keijiro Takahashi",
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"com.unity.ai.assistant": "2.6.0-pre.1"
|
|
10
|
+
},
|
|
11
|
+
"changelogUrl": "https://github.com/keijiro/AIA-Extensions/blob/master/CHANGELOG.md",
|
|
12
|
+
"documentationUrl": "https://github.com/keijiro/AIA-Extensions",
|
|
13
|
+
"licensesUrl": "https://github.com/keijiro/AIA-Extensions/blob/master/LICENSE",
|
|
14
|
+
"license": "Unlicense",
|
|
15
|
+
"_upm": {
|
|
16
|
+
"changelog": "- Initial release."
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"url": "git@github.com:keijiro/AIA-Extensions.git",
|
|
20
|
+
"type": "git",
|
|
21
|
+
"revision": "5972c2dd0d5b6a7ad01c2de12f17396f77b1cfc7"
|
|
22
|
+
}
|
|
23
|
+
}
|