When you are at a tech meetup or conference, it is always awkward when you want to connect with another attendee.
I am referring here to the professional networking site Linkedin
Say you have just met someone cool at an event, and you want to connect with them professionally, and you ask them "can we connect on Linkedin", but first you have to:
This is all on top of the awkwardness of asking them in the first place. I suppose you can write the name down, or swap Business Cards (do we still do that?).
So while I was listening to the latest episode of The Sam & Ray Podcast, Sam mentioned that he likes to use the Linkedin QR scanner!
Now this made my ears perk up! "What scanner?" I asked to the empty room (and if you are reading this still, you probably asked the same thing, although you may be in a coffee shop instead of my lounge).
So time to expose this lesser known feature and how you access it!
Each registered user of Linkedin, has an automatically generated QR code (Unfortunately not a Lego QR Code) you can access quickly using the app (iPhone and Android).
But wait, there is another handy option, you can scan someones QR code in the app too!
The official documentation is here
So if you find yourself talking to me at either {dev.talk} or re:connect conference, you will know how to connect to me on Linkedin quickly!
I host my blog using GitHub Pages (repo here), and have the domain registered through GoDaddy. I wanted to experiment with adding some additional functionality to my static content, using DigitalOcean App Platform (where I can essentially throw a Docker container and have it appear on the internet).
I wanted this DigitalOcean-hosted app to be available through a productiverage.com subdomain, and I wanted it to be accessible as an API from JavaScript on the page. SSL* has long been a given, and I hoped that I would hit few (if any) snags with that.
There are instructions out there for doing what I wanted, but I still encountered so many confusions and gotchas, that I figured I'd try to document the process (along with a few ways to reassure yourself when things look bleak).. even if it's only for future-me!
* (Insert pedantic comment about how TLS has replaced SSL, and so we shouldn't refer to "SSL" or "SSL certificates" - for the rest of the post, I'll be saying "SSL" and hopefully that doesn't upset anyone too much despite it not being technically correct!)
So you have something deployed using DigitalOcean's App Platform solution. It will have an automatically generated unique url that you can access it on, that is a subdomain of "ondigitalocean.app" (something like. https://productiverage-search-58yr4.ondigitalocean.app). This will not change (unless you delete your app), and you can always use it to test your application.
You want to host the application on a subdomain of a domain that you own (hosted by GoDaddy, in my case).
To start the process, go into the application's details in DigitalOcean (the initial tab you should see if called "Overview") and click into the "Settings" tab.
Note: Do not click into the "Networking" section through the link in the left hand navigation bar (under "Manage), and then into "Domains" (some guides that I found online suggested this, and it only resulted in me getting lost and confused - see the section below as to why).
This tab has the heading "App Settings" and the second section should be "Domains", click "Edit" and then the "+Add Domain" button.
Here, enter the subdomain that you want to use for your application. Again, the auto-assigned ondigitalocean.app subdomain will never go away, and you can add multiple custom domains if you want (though I only needed a single one).
You don't actually have to own the domain at this point; DigitalOcean won't do any checks other than ensuring that you don't enter a domain that is registered by something else within DigitalOcean (either one of your own resources, or a resource owned by another DigitalOcean customer). If you really wanted to, you could enter a subdomain of a domain that you know that you can't own, like "myawesomeexperiment.google.com" - but it wouldn't make a lot of sense to do this, since you would never be able to connect that subdomain to your app!
In my case, I wanted to use "search.productiverage.com".
Note: It's only the domain or subdomain that you have to enter here, not the protocol ("http" or "https") because (thankfully) it's not really an option to operate without https these days. Back in the dim and distant past, SSL certificates were frustrating to purchase, and register, and renew - and they weren't free! Today, life is a lot easier, and DigitalOcean handles it for you automatically when you use a custom subdomain on your application; they register the certificate, and automatically renew it. When you have everything working, you can look up the SSL certificate of the subdomain to confirm this - eg. when I use sslshopper.com to look up productiverage.com then I see that the details include "Server Type: GitHub.com" (same if I look up "www.productiverage.com") because I have my domain configured to point at GitHub Pages, and they look after that SSL certificate. But if I use sslshopper.com to look up search.productiverage.com then I see "Server Type: cloudflare" (although it doesn't mention DigitalOcean, it's clearly a different certificate).
With your sub/domain entered (and with DigitalOcean having checked that it's of a valid form, and not already in use by another resource), you will be asked to select some DNS management options. Click "You manage your domain" and then the "Add Domain" button at the bottom of the page.
This will redeploy your app. After which, you should see the new domain listed in the table that opened after clicked "Edit" alongside "Domains" in the "Settings" tab of your app. It will probably show the status as "Pending". It might show the status as "Configuring" at this point - if it doesn't, then refreshing the page and clicking "Edit" again alongside the "Domains" section should result in it now showing "Configuring".#
There will be a "?" icon alongside the "Configuring" status - if you hover over it you will see the message "Your domain is not yet active because the CNAME record was not found". Once we do some work on the domain registrar side (eg. GoDaddy), this status will change!
I read some explanations of this process that said that you should configure your custom domain not by starting with the app settings, but by clicking the "Networking" link in the left hand nav (under "Manage") and then clicking into "Domains". I spent an embarrassing amount of time going down this route, and getting frustrated when I reached a step that would say something like "using the dropdown in the 'Directs to' column, select where the custom domain should be used" - I never had a dropdown, and couldn't find an explanation why!
When you configure a custom sub/domain this way, it can only be connected to (iirc) Load Balancers (which "let you distribute traffic between multiple Droplets either regionally or globally") or, I think, Reserved IPs (which you can associate with any individual Droplet, or with a DigitalOcean's managed Kubernetes service - referred to as "DOKS"). You can not select an App Platform instance in a 'Directs To' dropdown in the "Networking" / "Domains" section, and that is what was causing me to stumble since I only have my single App Platform instance right now (I don't have a load balancer or any other, more complicated infrastructure).
Final note on this; if you configure a custom domain as I'm describing, you won't see that custom domain shown in the "Networking" / "Domains" list. That is nothing to worry about - everything will still work!
Long ago, I registered my domain with GoDaddy and hosted my blog with them as an ASP.NET site. I wasn't happy with the performance of it - it was fast much of the time, but would intermittently serve requests very slowly. I had a friend who had purchased a load of hosting capacity somewhere, so I shifted my site over to that (where it was still hosted as an ASP.NET site) and configured GoDaddy to send requests that way.
Back in 2016, I shifted over to serving the blog through GitHub Pages as static content. The biggest stumbling block to this would have been the site search functionality, which I had written for my ASP.NET app in C# - but I had put together a way to push that all to JS in the client in 2013 when I got excited about Neocities being released (I'm of an age where I remember the often-hideous, but easy-to-build-and-experiment-with, Geocities pages.. back before the default approaches to publishing content seemed to within walled gardens or on pay-to-access platforms).
As my blog is on GitHub Page, I have A
records configured in the DNS settings for my domain within GoDaddy that point to GitHub servers, and a CNAME
record that points "www" to my GitHub subdomain "productiverage.github.io".
The GitHub documentation page "Managing a custom domain for your GitHub Pages site" describes the steps that I followed to end up in this position - see the section "Configuring an apex domain and the www subdomain variant". The redirect from "productiverage.com" to "www.productiverage.com" is managed by GitHub, as is the SSL certificate, and the redirection from "http" to "https".
Until I created my DigitalOcean app, GoDaddy's only role was to ensure that when someone tried to visit my blog that the DNS lookup resulted in them going to GitHub, who would pick up the request and serve my content.
Within the GoDaddy "cPanel" (ie. their control panel), click into your domain, then into the "DNS" tab, and then click the "Add New Record" button. Select CNAME
in the "Type" dropdown, type the subdomain segment into the "Name" text (in my case, I want DigitalOcean to use the subdomain "search.productiverage.com" so I entered "search" into that textbox, since I was managing my domain "productiverage.com"), paste the DigitalOcean-generated domain into the "Value" textbox ("productiverage-search-58yr4.ondigitalocean.app" for my app), and click "Save".
You should see a message informing you that DNS changes may take up to 48 hours to propagate, but that it usually all happens in less than an hour.
In my experience, it often only takes a few minutes for everything to work.
If you want to get an idea about how things are progressing, there are a couple of things you can do -
If you don't want to mess about with these steps, you are free to go and make a cup of tea, and everything should sort itself out on its own!
I had gone round and round so many times trying to make it work that I was desperate to have some additional insight into whether it was working or not, but now that I'm confident in the process I would probably just wait five minutes if I did this again, and jump straight to the final step..
At this point, you should be able to hit your DigitalOcean app in the browser! Hurrah!
Depending upon your needs, you may be done by this point.
After I'd finished whooping triumphantly, however, I realised that I wasn't done..
My app exposes a html form that will perform a semantic search across my blog content (it's essentially my blog's Semantic Search Demo project, except that - depending upon when you read this post and when I update that code - it uses a smaller embedding model and it adds a call to a Cohere Reranker to better remove poor matches from the result set). That html form works fine in isolation.
However, the app also supports application/json
requests, because I wanted to improve my blog's search by incorporating semantic search results into my existing lexical search. This meant that I would be calling the app from JS on my blog. And that would be a problem, because trying to call https://search.productiverage.com from JS code executed within the context of https://www.productiverage.com would be rejected due to the "Cross-Origin Resource Sharing" (CORS) mechanism, which exists for security purposes - essentially, to ensure that potentially-malicious JS can't send content from a site to another domain (even if the sites are on subdomains of the same domain).
To make a request through JS within the context of one domain (eg. "www.productiverage.com") to another (eg. "search.productiverage.com"), the second domain must be explicitly configured to allow access from the first. This configuration is done against the DigitalOcean app -
Access-Control-Allow-Origins
header, that has a "Match Type" of "Exact" and an "Origin" of "https://www.productiverage.com"Now, you should be able to access your DigitalOcean app on the custom subdomain from another domain through JS code, without the browser giving you an error about CORS restrictions denying your attempt!
To see an example of this in action, you can go to www.productiverage.com, open the dev tools in your browser, go to the "Network" tab and filter requests to "Fetch/XHR", type something into the "Site Search" text box on the site and click "Search", and you should see requests for content SearchIndex-{something}.lz.txt
(which is used for lexical searching) and a single request that looks like ?q={what you searched for}
which (if you view the Headers for) you should see comes from search.productiverage.com. Woo, success!!
In the past three posts about working with AI on Windows, we covered connecting to Phi Silica, getting progress response, and building a chat experience. In this post we’re going to improve the styling of the chat by using data templates and including a system message to setup the chat interaction.
At the end of the previous post, the layout was relatively basic, with the formatting of each item in the ListView being the ToString representation of the PhiMessage object.
To make it easier to distinguish between User input and the Assistant response, we’ll use two data templates that simply align the text to the left, for the Assistant, or the right, for the User. This will use a data template selector to pick which template to use based on the User property on the PhiMessage.
In the following video we create an ItemTemplate for the ListView, which will show the User and Message for the PhiMessage. We’ll also create a HeaderTemplate that will be data bound to a SystemMessage. For this, we’ll update the MainViewModel to include the SytemMessage property, which will be combined with the message history in forming the Phi Silica prompt.
[ObservableProperty]
private string _systemMessage = "You are an assistant for a 5 year old child";
public ObservableCollection<PhiMessage> Messages { get; } = new();
[ObservableProperty]
private string _response = string.Empty;
[RelayCommand]
public async Task SendMessage(string message)
{
Messages.Add(new PhiMessage("User", message));
#if WINDOWS
if (!LanguageModel.IsAvailable())
{
var op = await LanguageModel.MakeAvailableAsync();
}
var sysMessage = new PhiMessage("System", SystemMessage);
var prompt = string.Join(Environment.NewLine, [sysMessage.ToString(), ..(from m in Messages
select m.ToString())]);
using LanguageModel languageModel = await LanguageModel.CreateAsync();
var progressTask = languageModel.GenerateResponseWithProgressAsync(prompt);
progressTask.Progress +=
(s, progress) => Dispatch(() => Response += progress);
var result = await progressTask;
Response = result.Response;
#else
Response = "Design-time response....";
await Task.Delay(1000);
Response = "Design-time response....(updating)";
await Task.Delay(1000);
Response = "Final design-time response";
#endif
Messages.Add(new PhiMessage("Assistant", Response));
Response = string.Empty;
}
And now the video for creating the ItemTemplate and HeaderTemplate.
Now that we have an ItemTemplate, we can duplicate the template for the User template where the text is right aligned. In order to use these templates we’ll need a template selector.
public class MessageTypeSelector : DataTemplateSelector
{
public DataTemplate UserTemplate { get; set; }
public DataTemplate AssistantTemplate { get; set; }
public DataTemplate SystemTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
if (item is PhiMessage message)
{
return message.User switch
{
"System" => SystemTemplate,
"Assistant" => AssistantTemplate,
_ => UserTemplate,
};
}
return base.SelectTemplateCore(item, container);
}
}
The two templates and an instance of the template selector, which references the two templates, need to be added to the Resources of the page.
<Page.Resources>
<DataTemplate x:Key="UserDataTemplate">
<Grid>
<StackPanel>
<TextBlock Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding User}"
TextAlignment="Right" />
<TextBlock Style="{StaticResource BaseTextBlockStyle}"
Text="{Binding Message}"
TextAlignment="Right" />
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate x:Key="AssistanctDataTemplate">
<Grid>
<StackPanel>
<TextBlock Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding User}" />
<TextBlock Style="{StaticResource BaseTextBlockStyle}"
Text="{Binding Message}" />
</StackPanel>
</Grid>
</DataTemplate>
<local:MessageTypeSelector x:Key="MessageTypeSelector"
UserTemplate="{StaticResource UserDataTemplate}"
AssistantTemplate="{StaticResource AssistanctDataTemplate}" />
</Page.Resources>
Using Hot Design, we can update the ListView to remove the locally defined ItemTemplate and select the MessageTypeSelector resource.
Now when we run the application on Window, we can customize the system message in order to change how Phi Silica responds.
The post AI on Windows: Chat Styling appeared first on Nick's .NET Travels.
Hundreds of thousands of people signed up to attend over 1,300 “Hands Off!” protests against President Donald Trump and Elon Musk yesterday. Today, estimates from groups involved in planning the protests suggest the protesters in the US and abroad may have actually numbered in the millions.
Activist group MoveOn is “estimating millions of attendees” went to the 1,300-plus scheduled events, with more than 100,000 turning out for the Washington, DC protest, Britt Jacovich, the group’s communications director, told The Verge via email. A press release published on the official Hands Off! website yesterday tells the same story:
Millions of people flooded the streets today at over 1,300 “Hands Off!” peaceful protests across all 50 states, U.S. territories, and a dozen locations globally, demanding an end to the authoritarian overreach by Trump and Musk.
The protests were laser-focused on Musk and Trump, but the concerns that drove yesterday’s demonstrations are wide-ranging, covering everything from Trump’s trade war and DOGE’s relentless federal agency cuts and layoffs, to LGBTQ+ and other civil rights issues, to the war in Ukraine. More than 150 groups participated in their organization, including those mentioned in this story, as well as the American Civil Liberties Union, the League of Women Voters, and labor unions like the AFL-CIO and those representing federal workers, such as the National Treasury Employees Union.
Indivisible, another of the more than 150 organizations involved in planning the protests, gives a similar estimate to MoveOn’s in a statement reported by Common Dreams, in which it says that “at virtually every single event the crowds eclipsed our estimates.” From Common Dreams:
“This is the largest day of protest since Trump retook office,” the group added. “And in many small towns and cities, activists are reporting the biggest protests their communities have ever seen as everyday people send a clear, unmistakable message to Trump and Musk: Hands off our healthcare, hands off our civil rights, hands off our schools, our freedoms, and our democracy.”
Other reported estimates from yesterday are smaller. The Guardian, The Hill, and Al Jazeera each put the number in the hundreds of thousands. Even so, millions doesn’t seem implausible. According to Axios, over 45,000 people gathered in Raleigh, North Carolina, and the outlet reports more than 100,000 people demonstrated both in Washington, DC and New York City. Organizers say more than 30,000 showed up in Chicago, writes WBEZ Chicago.
One of the most specific numbers reported so far comes from the social media accounts of 50501, one of the most prominent protest movements that have sprung up in the wake of Musk’s actions as the head of the Department of Government Efficiency (DOGE). The group posted late yesterday that “over 3 million people across the country stood up to say HANDS OFF our democracy.”